The Scenario
You’re pushing a deploy on a Thursday afternoon — your Django app on an EC2 t3.medium starts throwing OperationalError: could not connect to server: Connection timed out during a routine migration. The RDS PostgreSQL db.r6g.large instance shows 0 connections in CloudWatch, but your app can’t reach it. Your on-call Slack channel is lighting up.
Symptoms
- Application logs show:
FATAL: connection to host <rds-endpoint> port 5432 failed: Connection timed out nc -zv <rds-endpoint> 5432hangs indefinitely from your EC2 instance- CloudWatch RDS metrics show
DatabaseConnectionsat 0, CPU low - No changes to security groups or NACLs in the last hour (you check CloudTrail)
Root Cause
This almost always means a network-level block, not a database engine issue. The TCP handshake never completes. Common culprits: a stale security group rule that was removed accidentally, a VPC NACL entry that got overwritten, or (the sneaky one) a subnet route table change that broke traffic to the RDS subnet. RDS itself is fine — you can’t even start a connection.
Resolution (Step-by-Step)
- Verify basic connectivity from the app host
# From the EC2 instance
timeout 5 telnet <rds-endpoint> 5432
# If it hangs, you've got a network issue
- Check security group rules on both sides
# Get the RDS security group
aws rds describe-db-instances --db-instance-identifier <db-id> --query 'DBInstances[0].VpcSecurityGroups'
# Check inbound rules on that SG — ensure your app's SG or CIDR is allowed on port 5432
aws ec2 describe-security-groups --group-ids <sg-id> --query 'SecurityGroups[0].IpPermissions'
- Validate NACL rules on the RDS subnet
# Get subnets for the RDS instance
aws rds describe-db-instances --db-instance-identifier <db-id> --query 'DBInstances[0].DBSubnetGroup.Subnets[*].SubnetIdentifier'
# Check NACL associations for each subnet
aws ec2 describe-network-acls --filters "Name=association.subnet-id,Values=<subnet-id>"
# Ensure inbound ephemeral ports (1024-65535) are open from your app's CIDR
- Test from a different VPC context
# Launch a temporary t2.nano in the same subnet as the RDS instance
# If it connects from there, the issue is on the app subnet's route/NACL
- Check route tables for the app subnet
aws ec2 describe-route-tables --filters "Name=association.subnet-id,Values=<app-subnet-id>"
# Ensure there's a route to the RDS subnet's CIDR (or a VPC peering/VPN if cross-VPC)
Why This Sometimes Doesn’t Work
The standard security group check misses the case where the RDS instance is in a different AZ than your app, and an AZ-specific subnet NACL got nuked during a routine Terraform apply. I’ve seen a terraform destroy on a dev environment accidentally wipe the NACL for a shared staging subnet because of a refactored module. Always check the subnet-level NACL, not just the security group. Also, if you’re using RDS Proxy, the proxy’s security group might have a stale rule — check that separately.
Verification
# From the app instance
nc -zv <rds-endpoint> 5432
# Expected output: Connection to <rds-endpoint> port 5432 [tcp/postgresql] succeeded!
# Then test an actual query
psql -h <rds-endpoint> -U <user> -d <database> -c "SELECT 1;"
# Expected: returns "1 row"
Common Follow-up Questions
Q: Why does ping work to the RDS endpoint but telnet on 5432 doesn’t?
A: ICMP (ping) is handled differently — security groups and NACLs can allow ICMP while blocking TCP. The database port is what matters.
Q: Can RDS itself cause a connection timeout?
A: Almost never — a timeout means the TCP handshake failed before reaching the engine. If RDS was full or failing, you’d see a Connection refused or a database-level error, not a timeout.
Q: How do I check if this is a cross-region VPC peering issue?
A: Verify the VPC peering connection status is active and that route tables on both sides have routes pointing to the peer’s CIDR. Also check that the security group in the peered VPC allows inbound from your app’s CIDR.
Prevent This in the Future
This class of issue — silent network changes breaking app connectivity — is exactly why you need proactive monitoring on your critical database endpoints. Many teams catch these failures only when users report downtime. Set up uptime monitoring and alerts with Better Stack to get notified the moment a connection check fails, before your customers feel it. Set up uptime monitoring and alerts with Better Stack