šŸŽ‰ 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.

šŸ”§

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