Fix AWS Lambda RDS Connection Timeout: Too Many Connections, RDS Proxy & Pooling Best Practices
A Lambda function that times out connecting to RDS is almost never actually a slow database — it's Lambda opening more connections than your database can hold, or a VPC route that was never wired up to let the traffic out in the first place. The fix is not a longer timeout. Here's the counterintuitive part: on a small database instance, the default connection limit can be as low as about 60 total connections, and a single burst of Lambda invocations can burn through that in under a second, timing out every single one of them even though the database itself is sitting idle and healthy.
Jake's phone repair shop runs a small internal tool built on Lambda — customers scan a QR code at the counter, and a function looks up their repair ticket in an RDS for MySQL database. It's worked fine for eight months. Then, on a Saturday when three staff members were checking in walk-ins at the same time as a marketing text blast drove people to check their ticket status online, the function started timing out. Not slow. Not erroring on bad input. Just gone, for eight straight minutes, while a line formed at the counter.
That's the pattern this article is built around, because it's the pattern almost everyone who searches "Lambda timed out connecting to RDS" is actually living through: a system that works fine under light load and falls over exactly when load shows up, which is exactly when you can least afford it.
The one-question diagnostic: is this a networking problem or a pooling problem?
Before you touch anything, answer this: does the function time out every single time, or only sometimes — and if it's sometimes, is it correlated with traffic? That one question splits almost every case into one of two buckets, and the fix for each bucket is completely different.
| What you're seeing | Likely cause | Where to look |
|---|---|---|
| Times out on every invocation, including a single manual test | Network path — subnet, security group, or NAT gateway is not letting the traffic through at all | VPC configuration, security group rules |
| Works fine at low volume, then times out in bursts | Connection exhaustion — Lambda scaled out faster than the database's connection limit | RDS max_connections, RDS Proxy |
| Times out only right after a database failover or maintenance window | DNS/connection storm after the writer endpoint moved | RDS Proxy, DNS caching in the application code |
| Works from your laptop, times out only from Lambda | Lambda isn't in a VPC that can reach the database, or the DB's security group doesn't allow the Lambda security group | Lambda VPC configuration, DB inbound rules |
♂️ Jake's Reality Check
"So which one is it for me? I don't even know what a VPC is, and I definitely don't want to guess and break the working parts."
Test one invocation manually first, outside of any load. If that single test times out, it's networking — nothing about connection limits matters yet, because you can't even open one connection. If a single test succeeds but a burst of ten at once fails, skip the networking section entirely and go straight to the pooling fix.
A quick vocabulary check before we go further, because both branches use it constantly: a VPC (Virtual Private Cloud) is your own private, walled-off slice of the AWS network — think of it as the locked stockroom behind Jake's shop counter, versus the open sales floor out front. Resources inside a VPC can't be reached from outside it unless you deliberately open a door. A security group is a firewall attached to a specific resource, like your database, that controls exactly which other resources are allowed to knock on its door. Neither of those is optional reading if your function times out on every single call — that's precisely where the problem lives, and skipping past it to chase pooling settings would be solving the wrong half of the puzzle.
Why Lambda times out connecting to RDS in the first place
A "timeout" is a very specific kind of failure, and it's worth being precise about what it means before you go chasing the wrong thing. A timeout is not an error message from the database saying "no." It's silence. Your Lambda function sent a connection request and nothing came back — no rejection, no refusal, nothing — until either your function's own timeout setting or the database driver's own connection timeout gave up waiting and reported failure on your behalf.
That silence almost always has one of three causes:
1. The network path genuinely doesn't exist
Lambda functions run inside an AWS-managed network by default, with no route into your private VPC at all. If your RDS instance sits inside a VPC, which almost every production database does, and your Lambda function hasn't been explicitly configured to also run inside that VPC, there's no path between them. Not a slow path — no path. The connection attempt hangs until it times out because there's genuinely nothing on the other end to respond, in either direction.
2. A security group or route is silently dropping the packets
This one is sneakier because it looks identical to cause #1 from the outside. A security group that blocks traffic doesn't send back a rejection — it just drops the packet on the floor with no reply of any kind. Your function waits, and waits, and eventually times out, with zero information in the response about why. This is the single most common reason a Lambda-to-RDS connection fails at a fixed, painful pace every single time it's attempted.
3. The database has run out of room for more connections
This is the one almost nobody expects, and it's the shock at the top of this article. Every RDS database instance has a hard ceiling on how many simultaneous connections it will accept — the max_connections setting — and that ceiling is calculated directly from how much memory the instance class has. On a small instance class, that number can be surprisingly low. When the request that opens a new connection arrives after the database has already hit that ceiling, the database can't even get to the point of saying "no" gracefully — the connection attempt just queues, stalls, and eventually the caller gives up and reports a plain timeout, with no useful error text attached to point you toward the real cause.
Here's why this matters more than it looks like it should: Lambda is designed to scale out aggressively. If ten requests hit your function at the same moment, AWS can spin up ten separate execution environments to handle them in parallel, each one potentially opening its own database connection. A traditional server-based application usually keeps a small, fixed pool of database connections no matter how much traffic it gets, because a person configured it that way up front. Lambda has no such built-in restraint by default — every concurrent invocation is a brand-new potential connection, and nothing about the default setup stops that number from climbing straight into the database's ceiling the moment traffic spikes.
Route one: checking the network path
If your single manual test timed out, start here. Everything downstream — pooling, proxies, tuning — is irrelevant until a lone connection attempt actually succeeds once.
Is your database private or public?
An RDS instance can be set to publicly accessible or not, under its connectivity settings. A private database — the recommended setup for anything holding real customer data — can only be reached from inside its own VPC, or a VPC connected to it. A public database has a public endpoint that can, in principle, be reached from the open internet, though its security group still has to explicitly allow the connection before that endpoint answers anyone.
⚠️ What this actually breaks
Making a production database publicly accessible just to work around a connectivity problem is trading a networking headache for a real security exposure. It widens the attack surface to the entire internet, and it's the kind of shortcut that gets forgotten and never revisited once the immediate fire is out. If your Lambda function needs to reach a private database, the correct fix is putting the function in the same VPC — not opening the database up to the world.
Fixing the network path, step by step
- Put the Lambda function inside the same VPC as the database. In the function's configuration, under the VPC settings, attach it to the VPC that contains your RDS instance, and select at least two subnets across different Availability Zones for resilience.
- Give the function's security group an outbound rule for the database port. This is the security group attached to the Lambda function itself — it needs to be allowed to send traffic out, typically on port 3306 for MySQL or 5432 for PostgreSQL.
- Give the database's security group an inbound rule that allows the Lambda function's security group. Rather than opening a raw IP range, reference the Lambda function's security group directly as the source. This is the step people miss most often — the outbound rule on Lambda's side means nothing if the database's own security group still rejects the connection on arrival.
- If the function needs internet access too, for anything outside the VPC, route outbound traffic through a NAT gateway. Once a Lambda function is attached to a VPC, all of its outbound traffic goes through that VPC by default — including calls to other AWS services or third-party APIs it used to reach directly without any extra setup. A NAT gateway placed in a public subnet, with a route from the function's private subnet, restores that internet path.
- Confirm the subnet's route table actually points somewhere. A subnet with no route to the database's subnet, or no route to a NAT gateway for internet-bound traffic, will silently swallow every packet with no error at all — which looks, from the Lambda side, exactly like a plain timeout.
Security groups are what AWS calls stateful, which is a piece of jargon worth unpacking because it trips people up constantly: a stateful firewall automatically allows the return traffic for a connection you already permitted going out, without needing a separate rule written for the reply. You don't have to write a matching inbound rule just to let a response back in — the security group remembers the conversation was allowed to start and lets it finish on its own. If you're instead using a network ACL — a separate, subnet-level firewall that's stateless, meaning it does not remember anything and must be told about both directions explicitly — you need rules for both the outbound request and the inbound reply, and those rules must cover the full range of ephemeral ports, not just the database's own port number.
✅ Why this is the one to use
Reference security groups by ID instead of by IP range wherever you can. A rule that says "allow traffic from this Lambda function's security group" keeps working automatically if the function's underlying IP addresses change — which they will, since Lambda manages its own network interfaces behind the scenes. An IP-range rule has to be manually re-checked forever, and it's the first thing that quietly goes stale in a growing setup.
If you've done all five of those steps and it still hangs on a single manual test, the fastest way to isolate the problem is to launch a small EC2 instance in the exact same VPC and subnet configuration as your Lambda function, and try connecting to the database from there with a plain database client. If that EC2 test connects fine, the problem is specific to how Lambda itself is configured — go back and re-check the VPC and subnet settings on the function directly. If the EC2 test also fails, the problem lives on the database side or the route table, not with Lambda's configuration at all, and it's worth confirming the database's own security group and public-access setting before touching Lambda again.
Route two: the connection storm nobody warned you about
This is the branch Jake's shop landed in. A single test connected fine. It only fell apart under real traffic. And it's worth being honest about why the popular advice — "just bump up the Lambda timeout" — is actively wrong here, not just unhelpful.
♂️ Jake's Reality Check
"Can't I just crank the Lambda timeout up from 3 seconds to 30 and call it a day?"
No — and this one costs money to get wrong. A connection that's stuck because the database has no room for it isn't going to suddenly succeed just because you give it longer to wait. You'll just pay for 30 seconds of a Lambda function sitting idle instead of 3, for every single failed attempt, while customers still see nothing but a spinner at the counter. A Lambda function's timeout can go up to a maximum of 900 seconds — fifteen minutes — and raising it here doesn't fix a full connection pool any more than standing in a longer line fixes a restaurant with no open tables left.
Ethan puts it more bluntly when Jake asks about it: "You're not debugging your code. You're debugging arithmetic. If your database allows sixty connections and your app just tried to open two hundred at once, a hundred and forty of those requests were never going to succeed no matter how patient you make them." That's the whole problem in one sentence — and it's the reason a longer timeout is, in Ethan's own words, "the worst fix on this list, because it looks like it's working while it's quietly costing you more."
Every RDS database instance has a max_connections setting that's calculated automatically from how much memory the instance class has — more memory means a higher ceiling. On the small end, that ceiling is genuinely low: for a MySQL DB instance running on a db.t3.micro instance class, the default maximum number of connections works out to approximately 60. Bump the instance size up and that ceiling rises with it, but it never rises fast enough to keep pace with how quickly Lambda can scale out under a real traffic spike.
Should you just raise max_connections instead?
It's the fix that feels instantaneous, because it's one parameter group edit and no code changes at all. But it deserves a serious warning attached to it. Because max_connections is tied directly to available memory, don't push the value beyond the instance class's own default — every additional connection reserves memory for itself, and an instance that's already tight on memory can genuinely become unstable or crash under the extra load rather than simply accepting more traffic gracefully. The recommended approach, before changing anything, is to track your peak concurrent connections using the DatabaseConnections CloudWatch metric over a week or two, and only then decide whether you actually need more room or simply need to stop wasting the room you already have. If you do decide you need a genuinely higher ceiling, the better sequence is to scale the instance up to a class with more memory first — which raises the calculated default automatically — rather than forcing the existing instance past a limit it was never sized for.
Here's the part that connects directly back to this whole article, though: if your actual problem is that Lambda opens and closes a large number of short-lived connections rather than that your application genuinely needs hundreds of connections held open at once, raising max_connections doesn't address the underlying issue at all — it just delays the same wall by a fixed, finite amount. The workloads that most need a higher connection ceiling are the ones that don't have a pooling mechanism in front of them, and Lambda, by default, doesn't have one.
The free fix: stop opening a new connection on every single invocation
Before reaching for any new AWS resource, check one thing in your own code: is the database connection being created inside the function handler, or outside it? This distinction matters because of how Lambda actually runs your code behind the scenes, and it's the cheapest possible thing to check before spending money on anything else.
When Lambda finishes handling an invocation, it doesn't necessarily tear the whole environment down afterward. It often freezes that execution environment and reuses it for the next invocation that comes in, to avoid the overhead of starting completely from scratch every time — this is usually called a "warm start." Anything you defined outside your handler function survives a warm start, including an already-open database connection sitting there ready to go. Anything defined inside the handler gets recreated from zero on every single call, warm or not, whether it needed to be or not.
| Where the connection is opened | What happens on a warm start |
|---|---|
| Inside the handler function | A brand-new connection is opened every time, even if the last invocation just closed one thirty milliseconds earlier |
| Outside the handler function | The existing connection survives and is reused, as long as it's still valid |
- Move the connection setup above your handler function — at the top level of the file, so it runs once when the execution environment initializes, not every single time the handler is invoked.
- Check whether the connection is still valid before using it, rather than assuming it survived untouched. A frozen execution environment can sit idle long enough for the database, or a firewall somewhere in between, to have already dropped the underlying connection. If it's dead, open a fresh one before continuing — don't just let the call crash on a stale handle.
- Don't explicitly close the connection at the end of the handler. Let it persist for the next possible invocation instead of tearing it down and rebuilding it every single time out of habit.
This fix costs nothing and takes ten minutes, but it has a hard ceiling of its own: it only helps invocations that land on a reused, warm environment. It does nothing for the first invocation of a burst of brand-new, cold execution environments, because each of those still opens its own fresh connection regardless of what your code does afterward. If Jake's Saturday rush spins up eighty new environments at once, this fix reduces how many additional connections pile up over time, but it doesn't stop the initial spike from slamming into the database's ceiling all at once. For that, you need something that manages the pool centrally, across every environment, instead of one connection at a time per function.
The real fix: Amazon RDS Proxy
Ethan's analogy for this one, told to Jake over the counter: "Picture your database as a restaurant with exactly sixty tables. Right now, every customer who walks in demands their own permanent table for the whole day, even if they only eat for two minutes. RDS Proxy is the host stand — it seats people at whichever table just freed up, instead of everyone fighting over their own reserved spot that sits empty ninety percent of the time it's held."
Technically: RDS Proxy sits between your Lambda function and the actual database instance, as a fully managed layer you don't have to run or patch yourself. Your Lambda function connects to the proxy's own endpoint, and the proxy maintains a smaller, warm pool of real connections to the database underneath it. Multiple client connections from Lambda get multiplexed onto that shared pool — the proxy performs all the operations for one transaction using a single underlying database connection, and can reuse a different one for the next transaction that comes in. You can have hundreds of Lambda-side connections open to the proxy while the database itself only ever sees a manageable handful at any given moment.
Jake's follow-up question is the one everyone eventually asks: "So this fixes the too-many-connections thing... but does it also fix the part where I'm not sure my code is even handling credentials right?" Ethan's answer, in his own words: "That's actually the part I like best about it. You can hand RDS Proxy your Lambda function's existing IAM execution role and let it authenticate that way, instead of the credentials living in your function's code or environment variables at all. Fewer secrets floating around is never a bad trade, and it's one less thing you have to remember to rotate."
Beyond raw connection pooling, RDS Proxy also helps with database failovers. It bypasses the DNS caching that normally slows down how quickly clients discover a new writer instance after a failover, which can cut failover-related connection delays substantially for Multi-AZ RDS instances, and it automatically routes traffic to the new instance while preserving the client-side connection — making the failover far less visible to your application than a raw, unpooled connection would ever experience it.
RDS instance vs. Aurora cluster: does the setup change?
Jake's setup is a single RDS for MySQL instance, so this section is skippable for him — but it's a real fork in the road for anyone running Aurora instead, and it's worth naming plainly rather than assuming one setup covers both.
For a single RDS instance, there's one endpoint, so there's one proxy endpoint to swap your connection string over to, and that's the whole decision. For an Aurora DB cluster, there are several different endpoints in play, and the proxy doesn't automatically replace all of them at once. The proxy's own endpoint is what you connect to instead of the cluster's direct read/write endpoint when you want pooling — but you can still connect straight to the cluster endpoint for read/write traffic without any pooling at all, still connect to the reader endpoint for load-balanced read-only traffic across replicas, and still connect to individual instance endpoints when you specifically need to diagnose or troubleshoot one node in the cluster rather than the cluster as a whole.
In practice, that means a Lambda function reading and writing through RDS Proxy should be pointed specifically at the proxy's endpoint — not the cluster endpoint, and not an instance endpoint — because those other endpoints skip the pooling layer entirely, even if the proxy technically exists somewhere in the same account. It's an easy mistake to make when a setup grows over time: one function gets migrated to the proxy correctly, a second function gets added later by someone copying an old connection string from documentation or a teammate's notes, and it ends up connecting straight to the cluster instead, quietly reintroducing the exact connection-storm risk the proxy was supposed to eliminate for the whole application. If you're troubleshooting a timeout on an Aurora-backed function, checking which endpoint the connection string actually points at is worth doing before anything else on this list.
Setting up RDS Proxy for a Lambda function
The fastest path is the guided console wizard, which links a Lambda function and a DB instance in one flow rather than configuring both sides by hand from separate screens.
- Open your database instance in the RDS console and choose the option to connect compute resources. Select your Lambda function as the resource you want to connect.
- Create or select an AWS Secrets Manager secret holding the database credentials the proxy will use to authenticate to the actual database — the proxy needs this even if your Lambda-to-proxy leg uses IAM authentication instead of a stored password.
- Choose to create a new proxy, or select an existing one if you already have a proxy in front of another function that talks to the same database.
- Review the connection summary, which shows the security group changes RDS is about to make automatically — it will update the proxy's security group to allow inbound traffic from your DB instance and your Lambda function on your behalf.
- Confirm and create. A newly created proxy takes some time to become available before it can accept connections, so don't expect it to be immediately live the moment you click through.
- Update your Lambda function's connection string to point at the proxy's own endpoint, not the database's direct endpoint. This is the step people forget most — creating the proxy does nothing on its own if your application code is still connecting straight to the database underneath it.
⚠️ What this actually breaks
If you point your function at the proxy but a transaction includes something like a session variable, a temporary table, or certain locking statements, RDS Proxy may "pin" that entire client session to one specific underlying database connection for its whole duration, to keep the behavior correct. A heavily pinned workload defeats the purpose of pooling — you're effectively back to one connection per client for that session. Review which SQL patterns in your code trigger pinning before assuming the proxy alone has solved everything for you.
Tuning RDS Proxy so it doesn't just move the timeout somewhere else
Here's a small surprise a lot of people run into after switching to RDS Proxy expecting the whole problem to vanish completely: with the defaults left untouched, it's entirely possible to trade a database-side timeout for a proxy-side one instead. The proxy has its own settings that control how it manages its pool, and the defaults aren't automatically the right fit for every workload that gets pointed at it.
| Setting | What it controls | Default |
|---|---|---|
| IdleClientTimeout | How long a client connection can sit idle before the proxy closes it | 1,800 seconds (30 minutes) |
| MaxConnectionsPercent | The share of the database's total max_connections the proxy itself is allowed to use, as a percentage | 100 for most engines; 10 for SQL Server |
| MaxIdleConnectionsPercent | How many idle database connections the proxy is allowed to keep warm and ready | Half of MaxConnectionsPercent |
| ConnectionBorrowTimeout | How long the proxy waits for a pooled connection to free up before giving your client an error, once the pool is full | 120 seconds |
Raising MaxIdleConnectionsPercent is a one-line change from the command line once you know which value you want — for example, aws rds modify-db-proxy-target-group --db-proxy-name my-proxy --target-group-name default --connection-pool-config MaxIdleConnectionsPercent=30 updates just that one setting on the target group without touching anything else. That last row in the table above deserves special attention, because it's exactly where the timeout you were trying to eliminate can quietly reappear. If your Lambda function's own timeout is set shorter than ConnectionBorrowTimeout, the function will time out and report failure well before the proxy has even given up trying to find it a connection — you'll see a Lambda timeout in your own logs and assume the proxy isn't helping at all, when really the two settings were never aligned with each other in the first place. As a rule of thumb worth remembering: each layer's settings should nest comfortably inside the layer above it, with a little breathing room between them, rather than all being set to roughly the same number.
✅ Why this is the one to use
If your workload runs in unpredictable bursts — like Jake's Saturday rush — raise MaxIdleConnectionsPercent rather than leaving it at the default half-of-max split. A higher idle percentage means the proxy keeps more warm, ready connections sitting in reserve, so a sudden surge doesn't have to wait on brand-new connections being opened from scratch at the exact moment it's least convenient.
Also worth knowing, because it explains a genuinely confusing symptom people report right after switching to the proxy: it's normal to see a database's connection count stay elevated even during quiet periods once RDS Proxy is sitting in front of it. That's not a leak — the proxy deliberately keeps a pool of idle connections open so it doesn't have to pay the cost of reopening them the moment traffic returns unexpectedly. If that steady-state connection count concerns you, it's the MaxIdleConnectionsPercent setting you want to lower, not a sign that something behind the scenes is broken.
The hard cases: when the obvious fix still isn't enough
You hit the elastic network interface limit, not the database limit
When a Lambda function is connected to a VPC, AWS creates an elastic network interface — a virtual network card — for each unique combination of subnet and security group the function uses. There's a default quota of 250 network interfaces per VPC. If you have a lot of functions, each with slightly different security group combinations, all attached to the same VPC, you can exhaust that quota and start seeing an explicit ENILimitReachedException rather than a plain, unhelpful timeout. The fix is either consolidating functions onto fewer, shared security groups, or requesting a quota increase through the Service Quotas console for that specific limit.
You're using network ACLs and losing connections intermittently, not consistently
Network ACLs aren't required for Lambda to reach your subnets at all, but if you've deliberately added one, remember that Lambda's VPC networking uses ephemeral ports in the 1024–65535 range for both TCP and UDP. If your ACL doesn't explicitly allow that full range in both directions, you'll see connections fail intermittently rather than every single time — which is a genuinely confusing symptom to debug on your own, because it looks random right up until you know exactly which port range to check.
You want to skip persistent connections entirely
For genuinely spiky, low-average-volume workloads, there's a fundamentally different option worth naming plainly rather than tuning around: the RDS Data API, available for Aurora Serverless configurations, lets you run SQL over an HTTPS API call instead of managing a database connection at all. There's no connection to pool, reuse, or exhaust, because there isn't a persistent connection in the traditional sense to begin with. It's not a drop-in replacement for every workload — some drivers and query patterns don't map cleanly onto it — but for a function that fires rarely and unpredictably, it sidesteps this entire class of problem rather than carefully tuning around it forever.
You're worried about a DNS UnknownHostException under heavy concurrency
Lambda functions support a maximum of 20 concurrent TCP connections used specifically for DNS resolution. It's an unlikely culprit for most RDS timeouts, since typical DNS lookups happen over UDP rather than TCP, but if your function is resolving a large number of distinct hostnames at very high concurrency, or falling back to TCP DNS because of oversized DNS responses, you can genuinely exhaust that limit — and it's one you cannot request an increase for, full stop. If you land here, the fix is reducing how many distinct hostname lookups your function performs per invocation, not chasing the database side of things any further.
What this actually costs Jake's shop — and what fixing it saves
It's worth naming the actual business cost here, because "it's a technical edge case" undersells what happened. Jake's Saturday outage lasted eight minutes. In that window, two walk-in customers left without checking in — one of them a phone screen replacement, a job Jake would have turned around the same day for a paying customer standing right there. That's real revenue walking straight out the door because a database ran out of table space for connections it never should have been asked to hold in the first place.
The fixes in this article scale in effort and cost roughly in this order, and it's worth picking the cheapest one that actually solves your specific case rather than jumping straight to the most expensive:
| Fix | Solves the networking cause? | Solves the connection-storm cause? |
|---|---|---|
| Fixing VPC subnets and security groups | Yes — this is the only real fix here | No |
| Reusing connections outside the handler | No | Partially — helps warm starts only |
| Amazon RDS Proxy | No — networking must already work | Yes — this is what it's built for |
| Resizing the database instance | No | Only temporarily — raises the ceiling, doesn't manage the pool |
Notice that resizing the instance sits at the bottom of that table on purpose. It's the fix people reach for first because it doesn't require touching code or networking configuration at all, but it's also the one that costs real, recurring money every single month while only postponing the exact same problem until the next traffic spike outpaces the new, larger ceiling too. RDS Proxy solves the structural cause; a bigger instance just moves the wall a little further away and hands you a bigger bill for the privilege.
How to know it's actually fixed, not just quieter
Once you've made changes, the right way to confirm they're holding is to watch your function's Amazon CloudWatch Logs for a stretch that includes a real traffic peak, not a single quiet afternoon that happens to look fine. Look specifically for the exact timeout error text your function was previously logging, and confirm it's genuinely absent across that whole window — not just that the dashboard looks calmer on average, which can hide a smaller problem still happening underneath. For RDS Proxy specifically, the connection pool's own CloudWatch metrics will tell you whether it's routinely hitting its configured ceiling, which is your signal to revisit the MaxConnectionsPercent setting before it quietly becomes a problem again down the road.
One honest thing worth saying plainly here, in Ethan's words: "None of this is a promise that the timeout can never come back. If your traffic genuinely doubles next quarter, this exact ceiling reappears at a new number. What changes is that now you know precisely which number to watch, instead of finding out from a customer standing at the counter."
- The steps above cover the two most common causes end to end. If neither branch matches what you're seeing after working through the diagnostic table, the next place to look is your database's own CloudWatch metrics for signs of a slow query holding a connection open far longer than expected — a genuinely different problem from a pure timeout, but one that produces a very similar symptom under real load.
Frequently asked questions
Why does my Lambda function time out connecting to RDS but connecting from my laptop works fine?
Your laptop connects over the public internet, or through a VPN, straight to the database's endpoint without going through Lambda's own networking model at all. A Lambda function attached to a VPC only has a network path to the database if it's been explicitly placed in that same VPC, with subnet routing and security group rules that allow the connection through. Working from your laptop tells you the database itself is reachable and healthy — it tells you nothing about whether Lambda's own VPC configuration has actually been set up correctly.
Is Amazon RDS Proxy free to use?
No. RDS Proxy is a separate managed resource from your database instance, and it has its own pricing dimension distinct from the underlying RDS instance's own cost. Check the current RDS Proxy pricing page for the specific rate that applies to your region and engine before rolling it out broadly, since pricing details can change and shouldn't be assumed from an older source you happened to read once.
Does RDS Proxy work with Aurora Serverless?
RDS Proxy is designed to work with Amazon RDS and Aurora database instances and clusters generally, including scenarios involving connection pooling for bursty, serverless-style workloads — which is exactly the kind of traffic pattern it was built to absorb in the first place. Confirm the specific engine version and configuration compatibility for your exact Aurora setup against the current documentation before deploying it, since supported configurations have expanded since the feature's original launch.
What's the maximum timeout I can set on a Lambda function?
900 seconds, or 15 minutes. But as covered above, raising the timeout does not fix a connection that's failing because the database has no room for it — it only makes you wait longer, and pay more, to discover the exact same failure you already had.
Can RDS Proxy authenticate using my Lambda function's existing IAM role?
Yes. You can use IAM authentication for the connection between your Lambda function and the proxy, which means your function doesn't need to store or manage a database username and password directly in its code or environment variables. You can also choose to use Secrets Manager for the leg between the proxy and the actual database, or IAM authentication for both legs at once if you want no database passwords stored anywhere in your account at all.
My Lambda function lost internet access after I connected it to a VPC — why?
Once a Lambda function is attached to a VPC, every outbound request from that function routes through the VPC instead of going directly out through AWS's own managed network like it used to. If that VPC's subnet doesn't have a route to a NAT gateway or NAT instance in a public subnet, outbound internet traffic has nowhere to go and will time out — even for calls to unrelated AWS services the function used to reach without any trouble before it was moved into the VPC.
Can I connect a Lambda function to a public RDS instance without putting the function in a VPC?
Yes, technically — if the database is publicly accessible and its security group allows the connection, a Lambda function that isn't attached to any VPC can reach it over the internet like any other ordinary client. This is not the recommended setup for anything holding real data, though, since it means the database is reachable from outside your private network entirely, not just from your own application.
What's the difference between a security group and a network ACL for this kind of error?
A security group is attached to a specific resource, like your database or your Lambda function, and it's stateful — meaning it automatically allows return traffic for a connection it already let out, with no separate rule needed for the reply. A network ACL is attached at the subnet level, applies to everything inside that subnet, and is stateless — it requires explicit rules for both directions of traffic written out separately. Network ACLs aren't required for Lambda to connect to your subnets at all; most timeout issues trace back to security groups, not ACLs.
Does putting RDS Proxy in front of my database add noticeable latency to every query?
There is some added latency from routing every request through an extra hop, since the proxy sits between your function and the database rather than being a direct connection between the two. For most application workloads, that overhead is small compared to the latency saved by not repeatedly paying the cost of opening a brand-new TCP connection and authenticating from scratch on every single invocation — which is exactly the overhead RDS Proxy exists to eliminate in the first place.
How many connections can RDS Proxy actually give my Lambda functions?
RDS Proxy doesn't create new capacity out of nowhere — it manages a pool up to the share of your database's existing max_connections that you've configured through MaxConnectionsPercent. What it changes is how efficiently that existing ceiling gets used: many client-side connections from Lambda can share a much smaller number of real, underlying database connections, instead of each one demanding its own dedicated slot the moment it starts.
I set up RDS Proxy and I'm still getting "too many connections" errors — why?
The most common cause is code that hasn't actually been updated to connect to the proxy's endpoint — it's still pointed at the database directly, so the proxy is sitting there doing nothing useful at all. The second most common cause is heavy session pinning, where certain SQL patterns in your queries force the proxy to lock a client to one dedicated underlying connection for the whole session, which defeats pooling entirely for that particular traffic. Review your connection string first, then your query patterns for pinning triggers second.
Should I explicitly close my database connection at the end of every Lambda handler?
No, if you're relying on connection reuse across warm starts — closing it at the end defeats the entire purpose of moving the connection setup outside the handler in the first place. Let the connection persist so the next invocation, if it lands on the same warm execution environment, can reuse it instead of paying the full setup cost all over again.
Does provisioned concurrency help with RDS connection timeouts?
Provisioned concurrency keeps a specified number of execution environments pre-initialized and warm, which reduces how often your function pays the cost of a cold start. It can smooth out how many brand-new connections get opened during a predictable traffic pattern, since more invocations land on already-warm environments with an existing connection ready to go. It does not raise your database's own connection ceiling, and it won't help with an unpredictable burst that exceeds whatever concurrency you've already provisioned for.
Can I avoid managing a persistent database connection from Lambda entirely?
For Aurora Serverless configurations, the RDS Data API lets you execute SQL statements over an HTTPS API call instead of opening and managing a traditional database connection at all. There's no connection to pool or exhaust in the same sense, which sidesteps this entire category of problem — though it comes with its own tradeoffs around driver compatibility and query patterns that are worth checking against your specific application before committing to it fully.
What is session pinning in RDS Proxy, and why does it break connection reuse?
Certain SQL operations — things like setting session-level variables, using temporary tables, or certain locking behaviors — require consistent state on a single underlying database connection to behave correctly from start to finish. When your client uses one of those patterns, RDS Proxy "pins" that entire session to one specific database connection for its duration, rather than letting it share the pool with everyone else. If a large share of your queries trigger pinning, you lose much of the multiplexing benefit the proxy was supposed to provide, and it's worth auditing your query patterns specifically for this if pooling still seems ineffective after setup.
Does RDS Proxy support database engines other than MySQL and PostgreSQL?
RDS Proxy's supported engine list has expanded since its original release, which covered MySQL and PostgreSQL only. Its connection pool configuration options today include settings specific to Microsoft SQL Server as well, indicating broader engine support than the original launch had. Check the current list of supported engines and versions in the official documentation for your specific database engine and version before assuming compatibility, since this is exactly the kind of detail that changes over time without much fanfare.
Revision note. Written September 2026. This will need a revisit if AWS changes the default connection pool percentages, the Lambda ENI networking model, or the RDS Proxy supported-engine list. If you're reading this at 11pm with a line at the counter, start with the one-question diagnostic above — you're closer to fixed than it feels right now.