šŸŽ‰ DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

AWS RDS Connection Timeout: 6 Causes and Fixes for Production

RDS connection timeout in production? These 6 fixes cover security group rules, VPC subnet routing, parameter group connection limits, max_connections exceeded, SSL/TLS misconfig, and connection pool exhaustion — with exact AWS CLI commands.

Shubham4 min read
Share:Tweet

RDS connection timeouts are frustrating because they can come from 6 different layers. Here is a systematic debugging approach from the most to least common cause.

Quick First Steps

bash
# Test connectivity from EC2/ECS in same VPC
psql -h your-rds-endpoint.ap-south-1.rds.amazonaws.com -U postgres -d mydb -c "SELECT 1;"
 
# If no psql, use nc to test TCP connectivity
nc -zv your-rds-endpoint.ap-south-1.rds.amazonaws.com 5432
# "succeeded" = network is fine, problem is elsewhere
# timeout/refused = network/security group issue

Cause 1: Security Group Not Allowing Inbound Traffic

The most common cause. RDS security group does not allow inbound from your application.

bash
# Get RDS security group
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].VpcSecurityGroups'
 
# Check its inbound rules
aws ec2 describe-security-groups \
  --group-ids sg-0abc123 \
  --query 'SecurityGroups[0].IpPermissions'

Fix — allow inbound from app security group:

bash
# Get your app's security group ID first
APP_SG="sg-0xyz789"   # Your app/EC2/ECS security group
RDS_SG="sg-0abc123"  # Your RDS security group
 
aws ec2 authorize-security-group-ingress \
  --group-id $RDS_SG \
  --protocol tcp \
  --port 5432 \
  --source-group $APP_SG

Never use 0.0.0.0/0 for RDS inbound — use the specific security group of your application.

Cause 2: RDS in Private Subnet, App in Different VPC

If your app is in a different VPC (common with EKS separate VPCs), the subnets cannot route to each other.

bash
# Check if your RDS is publicly accessible
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].PubliclyAccessible'
# "false" = not reachable from outside VPC
 
# Check VPC peering or PrivateLink
aws ec2 describe-vpc-peering-connections \
  --query 'VpcPeeringConnections[?Status.Code==`active`]'

Fixes:

  1. VPC Peering (same account): Create peering between VPCs, add routes
  2. AWS PrivateLink: For cross-account access
  3. RDS Proxy: Best for Lambda/serverless — handles connection pooling too
bash
# Create RDS Proxy (handles both VPC routing and connection pooling)
aws rds create-db-proxy \
  --db-proxy-name mydb-proxy \
  --engine-family POSTGRESQL \
  --auth '[{"AuthScheme": "SECRETS", "SecretArn": "arn:aws:secretsmanager:..."}]' \
  --role-arn arn:aws:iam::123456789:role/rds-proxy-role \
  --vpc-subnet-ids subnet-0abc subnet-0def \
  --vpc-security-group-ids sg-0rds

Cause 3: max_connections Exceeded

RDS PostgreSQL has a connection limit based on instance size. At ~90% usage, new connections start timing out.

sql
-- Check current connections (run on RDS)
SELECT count(*) FROM pg_stat_activity;
 
-- Check max allowed
SHOW max_connections;
 
-- See connections by app
SELECT application_name, count(*) 
FROM pg_stat_activity 
GROUP BY application_name 
ORDER BY count DESC;

Fix — increase max_connections:

Default max_connections for RDS is LEAST({DBInstanceClassMemory/9531392}, 5000). For db.t3.micro: only ~100.

bash
# Create or update parameter group
aws rds modify-db-parameter-group \
  --db-parameter-group-name mydb-params \
  --parameters "ParameterName=max_connections,ParameterValue=200,ApplyMethod=pending-reboot"
 
# Apply parameter group to RDS
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --db-parameter-group-name mydb-params \
  --apply-immediately

Better fix — use PgBouncer connection pooler:

yaml
# PgBouncer as Kubernetes sidecar
containers:
- name: pgbouncer
  image: pgbouncer/pgbouncer:1.22
  env:
  - name: DATABASES_HOST
    value: mydb.ap-south-1.rds.amazonaws.com
  - name: DATABASES_PORT
    value: "5432"
  - name: POOL_MODE
    value: transaction    # Transaction pooling = most efficient
  - name: MAX_CLIENT_CONN
    value: "1000"         # App connects to PgBouncer
  - name: DEFAULT_POOL_SIZE
    value: "20"           # PgBouncer uses 20 real connections

Cause 4: SSL/TLS Certificate Issue

Since 2021, AWS RDS requires SSL for some configurations.

bash
# Test without SSL
psql "host=mydb.ap-south-1.rds.amazonaws.com dbname=mydb user=postgres sslmode=disable"
 
# Test with SSL
psql "host=mydb.ap-south-1.rds.amazonaws.com dbname=mydb user=postgres sslmode=require"

Application fix (Python/SQLAlchemy):

python
import ssl
 
ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations("rds-ca-2019-root.pem")    # Download from AWS
 
engine = create_engine(
    DATABASE_URL,
    connect_args={"ssl": ssl_context}
)

Download the RDS certificate bundle:

bash
wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem

Cause 5: RDS Parameter Group — wait_timeout or connect_timeout

bash
# Check current timeout settings
aws rds describe-db-parameters \
  --db-parameter-group-name mydb-params \
  --query 'Parameters[?ParameterName==`connect_timeout` || ParameterName==`wait_timeout`]'

For MySQL RDS, wait_timeout=28800 (8 hours) means idle connections get killed after 8h — then the pool tries to use dead connections.

Fix (MySQL):

bash
aws rds modify-db-parameter-group \
  --db-parameter-group-name mydb-params \
  --parameters "ParameterName=wait_timeout,ParameterValue=300,ApplyMethod=immediate"

And configure your connection pool to test connections before using them:

python
engine = create_engine(
    DATABASE_URL,
    pool_pre_ping=True,    # SQLAlchemy: test connection before checkout
    pool_recycle=3600,     # Recycle connections every hour
)

Cause 6: Connection Pool Exhaustion in App Code

Your app holds too many open connections.

python
# Bad — no pool size limit
engine = create_engine(DATABASE_URL)
 
# Good — explicit pool config
engine = create_engine(
    DATABASE_URL,
    pool_size=10,        # Persistent connections
    max_overflow=5,      # Burst capacity
    pool_timeout=30,     # Wait up to 30s for a connection
    pool_recycle=3600,   # Recycle after 1h
    pool_pre_ping=True,  # Test connections before use
)

Debugging Flow

Connection timeout
ā”œā”€ā”€ nc -zv RDS_HOST 5432 → FAILED?
│   ā”œā”€ā”€ Security group missing inbound rule → Fix SG
│   └── VPC routing issue → Add VPC peering or use RDS Proxy
└── nc SUCCEEDED but app still fails?
    ā”œā”€ā”€ Check max_connections → Add PgBouncer
    ā”œā”€ā”€ Check SSL requirement → Add SSL cert
    ā”œā”€ā”€ Check wait_timeout → Enable pool_pre_ping
    └── Check app pool size → Limit pool_size

More AWS troubleshooting? Read our AWS ALB 504 gateway timeout fix and AWS EKS pods stuck pending fix.

Did this fix work?

Tell us what needs improving. No account required.

šŸ”§

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments