RDS "Could Not Connect to Server" Timeout: Security Groups, Public Access and VPC, in Order
If you're staring at "could not connect to server: Connection timed out" or "ERROR 2003 (HY000): Can't connect to MySQL server" on an Amazon RDS instance, the fix is almost always one of three things, checked in this order: the instance isn't set to Publicly Accessible, its security group has no inbound rule for your IP and port, or a network ACL or route table is quietly blocking the path. Here's the part that trips people up: reopening the security group to the whole internet — the first thing everyone tries — fixes nothing if Publicly Accessible is still set to No. That setting removes an entire layer the security group can't touch on its own.
Why "Connection Timed Out" Almost Never Means Your Database Is Broken
Jake runs a small phone shop, and last Saturday his repair-ticket app — a little Node script on an EC2 box, talking to an Amazon RDS MySQL instance — froze mid-queue. A walk-in customer waited eleven minutes for a screen that never loaded, then left for the shop across the street. The app hadn't crashed. The database hadn't crashed. Something in between the two had quietly stopped letting them talk.
That's what a timeout actually tells you. It's different from an authentication error, which comes back fast with a clear "access denied" because the two sides did connect and then disagreed about credentials. A timeout means your client sent a request and nothing ever answered — not "no", just silence, until the client gives up. That silence points at the network path, not the database engine itself: a security group, a "Publicly Accessible" flag, a network ACL, a route table, or a DNS/endpoint mistake. Fixing the wrong one of those (usually the security group, because it's the most visible) while leaving the real blocker in place is the single most common reason people spend an afternoon on what should be a five-minute fix.
Step 1 — Confirm the Instance Is Actually Up
Before touching any network setting, rule out the boring cause: the instance itself isn't ready yet. New instances and instances coming out of a reboot, a storage scaling event, or a failover can take a while to report as available, and every connection attempt during that window will time out or refuse.
- Open the RDS console and check the Status column for your instance, or run
aws rds describe-db-instances --db-instance-identifier YOUR-INSTANCE --query 'DBInstances[*].DBInstanceStatus'from a machine with the AWS CLI configured. - If the status reads anything other than available — creating, backing-up, modifying, maintenance — wait. New or recently modified instances can take up to about 20 minutes to settle.
- If the status is failed or incompatible-network, open the Logs & events tab on the instance page; it lists the specific resolution steps for that state rather than a generic timeout.
Once the instance shows available and you're still timing out, the problem is downstream in the network path, and the rest of this post is written in the order that actually clears it fastest.
Step 2 — Is "Publicly Accessible" Actually Set to Yes?
🙋♂️ Jake's Reality Check
"I thought giving it a public IP address was the risky part. Why do I need that AND a security group rule? Isn't that redundant?"
It's not redundant — they're two separate locks, and both have to be open. One controls whether the database gets a public network address at all. The other controls who's allowed to talk to it once it has one.
Ethan puts it this way: "Publicly Accessible is the front door existing. Your security group is who's on the guest list. You can hand out every invitation in the world, but if the building has no door facing the street, nobody's getting in — and that's exactly what a private-only RDS instance is." When Publicly Accessible is set to No, your DB instance still gets an endpoint and a DNS name, but that name never resolves to an address reachable from outside the VPC. Every connection attempt from your laptop or from any host outside that VPC times out, no matter what the security group says.
To check and fix it: open the instance in the RDS console, look at the Connectivity & security tab for the Publicly accessible value. If it reads No and you need internet-based access, choose Modify, change Public access to Yes, and apply the change — this typically doesn't require downtime, but confirm the "Apply immediately" setting matches what you want before submitting it.
⚠️ What this actually breaks
The popular advice of "just open the security group to 0.0.0.0/0" is often tried right here — and if Publicly Accessible is still No, it accomplishes nothing except leaving a wide-open inbound rule sitting on the instance for no benefit. If you later do make it public, go back and narrow that rule; a database open to the entire internet on its default port is a standing invitation for credential-stuffing and brute-force login attempts.
Step 3 — Security Groups: Get the Right Rule on the Right Group
A VPC security group acts like a firewall attached directly to the instance's network interface. By default, a newly created security group allows no inbound traffic at all — everything has to be explicitly opened. An RDS instance can have more than one security group attached, and if any one of them allows your traffic, the connection is permitted; it's a "most permissive wins" model, not "all must agree."
- Find which security groups are attached:
aws rds describe-db-instances --db-instance-identifier YOUR-INSTANCE --query 'DBInstances[*].VpcSecurityGroups[*].VpcSecurityGroupId' - Check each one's inbound rules:
aws ec2 describe-security-groups --group-ids YOUR-SG-ID --query 'SecurityGroups[*].IpPermissions' - Add a rule for your source and port:
aws ec2 authorize-security-group-ingress --group-id YOUR-SG-ID --protocol tcp --port 3306 --cidr YOUR-PUBLIC-IP/32— swap 3306 for 5432 on PostgreSQL, or your custom port if you changed it.
| Engine | Default port | Command to verify |
|---|---|---|
| MySQL / MariaDB | 3306 | mysql -h endpoint -P 3306 -u user -p |
| PostgreSQL | 5432 | psql -h endpoint -p 5432 -U user -d database |
| SQL Server | 1433 | Any SQL Server client, endpoint:1433 |
✅ Why this is the one to use
Scope the rule to your actual public IP as a /32, not to 0.0.0.0/0. Your home or office IP is usually static enough for day-to-day work, and a /32 rule gives you the exact same access with none of the exposure of leaving the port open to every address on the internet.
Remember that your own public IP isn't your computer's local address — it's the address the internet sees you as, and it changes if your ISP reassigns it or you switch networks (coffee shop Wi-Fi, a different office, a VPN). If a connection worked yesterday and times out today with nothing else changed, checking whether your public IP moved is the first thing to try before touching anything in the console.
Step 4 — Network ACLs (the Layer Everyone Forgets)
Security groups get all the attention because they live on the RDS console's Connectivity tab. Network ACLs (access control lists) sit one layer up, on the subnet itself, and RDS doesn't show them to you anywhere — you have to go find them in the VPC console. That's exactly why they're the thing people forget.
The practical difference: a security group is stateful (allow the inbound request, and the reply is automatically allowed back out), while a network ACL is stateless — you must explicitly allow both directions. That trips people up specifically on the return traffic: if you open inbound 3306 on the NACL but forget outbound, the request reaches the database and the reply never makes it back, which still looks like a timeout from the client's side.
To check it: aws ec2 describe-network-acls --filters "Name=association.subnet-id,Values=YOUR-SUBNET-ID" --query 'NetworkAcls[*].NetworkAclId', then aws ec2 describe-network-acls --network-acl-ids YOUR-NACL-ID --query 'NetworkAcls[*].Entries'. Confirm inbound rules allow your database port and that outbound rules allow the ephemeral port range, 1024–65535 — that range is where the client's side of the conversation happens, and a NACL that only opens 3306 outbound will silently break every connection.
Step 5 — VPC Subnets and Route Tables
An RDS instance lives inside a VPC, and specifically inside a DB subnet group — a set of subnets, usually spread across at least two Availability Zones, that RDS is allowed to place the instance in. Publicly Accessible and the security group control who's allowed in; the route table controls whether there's a path at all.
For a public subnet, the route table needs a route to an internet gateway. For a private subnet, there's no such route by design — instances there can typically reach the internet outbound through a NAT gateway (if one exists), but nothing on the internet can reach them directly, regardless of what the security group says. If your RDS instance sits in a subnet without a route to an internet gateway, setting Publicly Accessible to Yes still won't let you connect from outside the VPC; you'd need to either move it to a subnet that has that route, or connect through one of the private-access methods further down this post.
Check the route table for your RDS subnet with aws ec2 describe-route-tables --filters "Name=association.subnet-id,Values=YOUR-SUBNET-ID". If you expect public access and don't see a route to an internet gateway, that's your answer — no security group rule will fix it.
Step 6 — Wrong Endpoint, Wrong Port, or a DNS Problem
Sometimes the network path is completely fine and the connection string is simply wrong. Confirm the exact host name and port from the RDS console's Connectivity & security tab — not one copied from an old script, a teammate's notes, or a different environment. A cluster (Aurora-style) writer endpoint and an instance endpoint look similar and are not interchangeable.
Test DNS resolution directly: nslookup your-instance.xxxxx.rds.amazonaws.com or dig your-instance.xxxxx.rds.amazonaws.com. If that fails to resolve at all, the problem is upstream of security groups entirely — check your VPC's DNS resolution and DNS hostnames settings, or try resolving from a different network to isolate whether it's your local DNS server misbehaving.
Also check whether the port you're using is already occupied locally. If you have a MySQL or PostgreSQL server running on your own machine on the same default port, some clients will silently connect to that instead of failing loudly — worth ruling out if the "connection" behaves strangely rather than timing out cleanly.
Step 7 — Connecting From a Different VPC, Account, or On-Prem Network
If your application server is in a different VPC than the RDS instance — even in the same AWS account — the two networks don't talk to each other by default. You need explicit connectivity: a VPC peering connection between the two VPCs, or a Transit Gateway if there are several VPCs involved, plus route table entries on both sides and security group rules that reference the peer's CIDR range or security group ID. Peering also has a DNS side people miss: unless both VPCs have DNS resolution enabled for the peering connection, resolving the RDS endpoint's private DNS name from across the peering link can fail even after the routing itself is correct, which produces the same timeout as a routing miss.
The same logic applies to an on-premises network reaching RDS: you need a Site-to-Site VPN or Direct Connect link, and the route tables and NACLs on the RDS side have to know about that path too. One VPN-specific trap worth checking: if your local VPN client uses split tunneling — routing only some traffic through the tunnel and sending the rest out your regular internet connection — and the RDS instance's private IP range isn't included in the tunneled routes, your connection attempt goes out your normal internet path instead of through the VPN and simply times out, with nothing in AWS to blame. Skipping any one of these pieces reproduces the exact same "Connection timed out" symptom as a plain security group miss, which is why cross-VPC and on-prem setups are disproportionately represented in "I checked everything and it still doesn't work" reports.
Connecting From AWS Lambda or Another Serverless Client
Jake doesn't run anything serverless, but if you do, this one's worth its own section because it produces a completely different failure pattern. "Do I need to worry about any of this if I'm not using Lambda?" he asked. Ethan's answer: "No — skip straight past this one if your app runs on a regular server. This is a Lambda-specific gotcha."
A Lambda function attached to a VPC gets network interfaces in the subnets and security groups you assign it, and from there it can reach an RDS instance in the same VPC over a private IP with no internet gateway or NAT involved at all — that part isn't the problem. The problem shows up under load: every concurrent invocation can open its own database connection, and a traffic spike can spin up far more concurrent connections than a steady-state application ever would, exhausting max_connections in a way that looks like a wall of "too many connections" errors rather than a single timeout. Amazon RDS Proxy exists specifically for this pattern — it pools and reuses connections in front of the database, so a burst of Lambda invocations shares a much smaller, steady set of database connections instead of each one opening its own. You can attach a proxy to a Lambda function directly from the function's configuration tab in the console, and RDS Proxy supports IAM authentication for the Lambda-to-proxy leg, so the function's execution role can connect without a database password living anywhere in its code.
| Method | Needs RDS to be public? | Best for |
|---|---|---|
| App server in the same VPC | No | Any production app tier |
| Bastion / jump host | No | Occasional admin access from a laptop |
| Site-to-Site VPN / Direct Connect | No | On-prem apps, corporate networks |
| Publicly Accessible + scoped security group | Yes | Individual developers connecting from home/office |
Going Private Instead of Public
Jake's follow-up question was the right one: "Do I actually need Publicly Accessible turned on at all?" For a lot of setups, no — and Ethan's opinion here is blunt: "If your app server is in the same VPC as the database, making the database public is solving a problem you don't have and creating one you didn't want."
A bastion host is just a small EC2 instance in the same VPC as the RDS instance, reachable by SSH (or by AWS Systems Manager Session Manager, which avoids opening SSH to the internet at all), that you tunnel your database client through. Your RDS security group only ever needs to trust the bastion's security group — never your own changing home IP. That single change also solves the "worked yesterday, times out today" problem for good, since the bastion's IP inside the VPC doesn't move the way your home connection does.
When It's Not Actually a Timeout: Authentication and Connection-Limit Errors
If the network path is genuinely fine — you can reach the port with a plain TCP test — but the database itself refuses or drops the connection, you're dealing with a different family of problems entirely. For MySQL, a full instance returns "ERROR 1040: Too many connections" once max_connections is reached. For PostgreSQL, it's "FATAL: remaining connection slots are reserved for non-replication superuser connections," because a handful of slots are always held back for administrative use.
🕐 What changed between versions
- Before: the reserved-connections behavior for RDS for PostgreSQL was governed by
rds.rds_superuser_reserved_connections, alongside PostgreSQL's ownsuperuser_reserved_connections. - Now: starting in RDS for PostgreSQL 17.1, 16.5, 15.9, 14.14, 13.17, and 12.21, a new
rds_reservedrole holds slots for Amazon's own administrative users, sized by therds.rds_reserved_connectionsparameter;rds.rds_superuser_reserved_connectionsis deprecated on version 16 in favor of the standardreserved_connectionsparameter. - What that means for you: if you've raised
max_connectionsand are still short on usable slots, check which of these parameters your engine version actually uses — the older name silently does nothing on the newer versions.
To see where you stand, connect successfully once (this part isn't blocked by anything above) and run SHOW STATUS LIKE 'Threads_connected'; and SHOW VARIABLES LIKE 'max_connections'; on MySQL, or SELECT count(*) FROM pg_stat_activity; and SHOW max_connections; on PostgreSQL. If you're near the ceiling, look for idle connections your application isn't closing before raising the limit — a parameter group change here requires a reboot to take effect for max_connections. For a workload with many short-lived connections, RDS Proxy pools them for you instead of raising limits indefinitely.
The Multi-AZ Failover Edge Case
One failure mode is easy to miss because nothing about it looks like a configuration mistake: a Multi-AZ instance fails over to its standby, and the standby lives in a different subnet — in a different Availability Zone — than the primary was in. That subnet is part of the same DB subnet group, but it's not guaranteed to have identical route table or NACL entries unless you set it up that way deliberately. Everything was correctly configured an hour ago; the underlying instance just isn't the one your rules were written for anymore.
If a previously working connection starts timing out with no changes on your end, check whether a failover event shows up in the instance's Recent events, and confirm the route tables and NACLs are consistent across every subnet in the DB subnet group — not just the one the primary happened to be using. This is also a good reason to double-check your subnet group spans at least two Availability Zones with matching network configuration before you need a failover to go smoothly, rather than discovering the mismatch during one.
Do Third-Party GUI Clients Change Any of This?
MySQL Workbench, DBeaver, TablePlus, pgAdmin — they all connect over the exact same network path as the command-line client. None of the steps above change depending on which one you use, and switching tools is never itself the fix for a timeout. Where a GUI client genuinely helps is bundling an SSH tunnel through a bastion host directly into its connection profile, so you're not running a separate ssh -L command by hand every time you connect.
Where they don't help: if connectivity is already broken, a nicer interface just gives you a nicer-looking timeout. Get the CLI client connecting first — it's faster to test with and rules out client-specific configuration as a variable — then move to a GUI tool once you know the path itself is open.
Before You Open Access: the Security Angle
Every fix on this page involves opening something up, so it's worth deciding what you're actually opening before you do it. A security group rule scoped to your personal /32 is far better than 0.0.0.0/0, but it's still a static, password-based front door. Where your engine supports it, IAM database authentication lets applications and users connect with short-lived, automatically rotating tokens tied to an IAM identity instead of a long-lived database password. For any credential you do keep, store it in AWS Secrets Manager rather than in application config, so it can be rotated without touching your code.
Least privilege applies inside the database too, not just at the network edge. A monitoring dashboard or a reporting job doesn't need the master user account — create a database role scoped to only the tables and permissions that job actually uses, so a leaked credential for one purpose doesn't hand over the whole instance.
Let AWS Diagnose It For You
Before working through every step above by hand, it's worth knowing AWS ships an automated runbook that checks most of this for you, if your application server is an EC2 instance.
- Run:
aws ssm start-automation-execution --document-name "AWSSupport-TroubleshootConnectivityToRDS" --parameters "InstanceId=EC2-INSTANCE-ID,DBInstanceIdentifier=DB-INSTANCE-NAME" - Check the result:
aws ssm get-automation-execution --automation-execution-id EXECUTION-ID, using the execution ID returned by the first command.
It won't fix anything for you, but it narrows the search to the specific layer that's actually failing, which is usually the slow part of this whole process. For a multi-hop setup — VPC peering, a Transit Gateway, or a path that crosses several route tables — AWS's VPC Reachability Analyzer is worth reaching for next; you give it a source and a destination, and it traces the exact hop where the path breaks instead of you checking each hop by hand.
The Task Right After This One: Encrypting the Connection
Once you can connect, it's worth pausing on whether you should be connecting in the clear. Amazon RDS supports SSL/TLS connections for Db2, MariaDB, SQL Server, MySQL, Oracle, and PostgreSQL, which encrypts the traffic between your client and the instance. You can optionally go a step further and validate the server's identity by downloading the certificate bundle for your AWS Region and engine, then pointing your client at it during connection setup. A network path being open and a connection being encrypted are two different things — solving the timeout only gets you the first one, and it's a natural moment to handle the second before this instance goes back into daily use.
Quick Decision Table: Symptom to Likely Cause
| Symptom | Most likely cause | Where to look |
|---|---|---|
| Times out from your laptop, works from an EC2 box in the VPC | Publicly Accessible = No, or missing SG rule for your IP | Connectivity & security tab |
| Worked yesterday, times out today, nothing changed | Your public IP changed | Recheck your current IP, update the SG rule |
| Inbound rule looks correct, still times out | Network ACL missing outbound ephemeral ports | VPC console > subnet > Network ACL |
| TCP connects fine, database rejects the login | Authentication, not networking | Credentials, CONNECT grant, user host mask |
| Lambda works fine at low traffic, fails under a burst | Concurrent invocations exhausting max_connections | Add RDS Proxy in front of the database |
| Fails only after a failover event | Standby subnet's route table/NACL differs | Compare route tables across the DB subnet group |
Frequently Asked Questions
How do I connect to RDS from my local machine?
Use the exact endpoint and port shown on the instance's Connectivity & security tab, with a standard client for your engine (mysql, psql, or a SQL Server client). That only succeeds if Publicly Accessible is Yes and your security group allows your current IP on that port.
How do I troubleshoot RDS connection issues fastest?
Check Publicly Accessible first, then the security group, then network ACLs, then route tables — in that order, because each one only matters if the layer before it is already correct.
What does "publicly accessible" actually control?
It controls whether the instance gets a network address reachable from outside its VPC at all. It's separate from the security group, which controls who's allowed to use that address once it exists.
Why does opening my security group to 0.0.0.0/0 still not work?
Almost always because Publicly Accessible is still set to No, so there's no public path for the security group's rule to even apply to. It's also a bad idea to leave a database open to the whole internet once you do fix that — scope the rule to your actual IP instead.
What's the default port for MySQL and PostgreSQL on RDS?
3306 for MySQL and MariaDB, 5432 for PostgreSQL, 1433 for SQL Server. You can change the port when you create or modify the instance, but the security group rule has to match whatever port you actually chose.
How do I find my current public IP to add to a security group?
In the RDS or EC2 console, the security group edit screen offers a "My IP" option that auto-fills your current public address. That's the address to use, not your computer's local network address.
Why did my connection work yesterday and time out today with nothing changed?
Your public IP most likely changed — home and small-office ISPs frequently reassign it, and switching networks (a coffee shop, a VPN, a new office) always changes it. Recheck your current IP and update the security group rule.
Can I connect to RDS without making it publicly accessible?
Yes, and for anything running inside AWS you generally should. An app server in the same VPC connects directly with no public exposure needed; a bastion host or Systems Manager Session Manager covers occasional admin access from outside.
What is rds.rds_reserved_connections and why does it matter?
On newer RDS for PostgreSQL versions, it sets how many connection slots are held back for Amazon's own administrative role. If your max_connections looks high enough but you're still hitting a reserved-slots error, this parameter (or its older equivalent, depending on your engine version) is worth checking.
Why do I get "Error reading from connection" instead of a timeout?
That usually means a connection was established and then dropped mid-session, which is a different problem from a timeout — check idle connection timeouts, network stability between the client and RDS, and whether the instance hit a connection or resource limit partway through.
Does Multi-AZ failover break my connection settings?
It can, if the standby's subnet has a route table or network ACL that isn't configured the same way as the primary's. The endpoint name doesn't change, but the underlying instance and its subnet do.
How do I check network ACLs versus security groups?
Security groups are stateful and attach to the instance; network ACLs are stateless and attach to the subnet. Check both — a NACL missing outbound ephemeral ports (1024–65535) can block a connection even when the security group is perfectly correct.
My EC2 instance is in the same VPC — why can't it connect?
Confirm the EC2 instance's own security group is allowed as a source in the RDS security group's inbound rule (or that its IP/CIDR is), and that both instances are actually in the same VPC and not just the same AWS account or region.
How long does an RDS instance take to become available after a change?
Often just a few minutes, but new instances or larger modifications can take up to about 20 minutes. Check the Status column before assuming a timeout is a network problem.
Should I use RDS Proxy or a bastion host?
They solve different problems. RDS Proxy pools and manages database connections for an application with many short-lived connections, such as Lambda; a bastion host is for occasional human access from outside the VPC. Many production setups use both, for different purposes.
Is it safe to leave my RDS instance publicly accessible?
It's a real exposure if the security group isn't tightly scoped, since a public, default-port database is a known target for automated login attempts. If nothing outside the VPC genuinely needs direct access, turning Publicly Accessible off and using a bastion or same-VPC access instead removes that exposure entirely.
Revision note. Written September 2026, covering current RDS console behavior for MySQL, PostgreSQL, and SQL Server DB instances, including the reserved-connections changes on recent RDS for PostgreSQL versions and RDS Proxy's role in Lambda connection storms. This will need a look whenever AWS reshuffles the Connectivity & security tab or changes how reserved connection slots are named. If you've been going in circles on this one, you're not missing something obvious — the layers genuinely don't show each other's state, and that's the whole reason this took longer than it should have.