# Fix AWS RDS Connection Timeout

You're staring at your application logs and see "Connection timed out" or "Could not connect to database server" when trying to reach your RDS instance. The instance is running, your application worked before, and now everything is timing out.

RDS connection timeouts have several common causes: security group misconfiguration, VPC networking issues, DNS problems, or database resource exhaustion. Let's work through each systematically.

Diagnosis Commands

First, verify the RDS instance is actually running:

bash
aws rds describe-db-instances \
  --db-instance-identifier my-database \
  --query 'DBInstances[*].[DBInstanceIdentifier,DBInstanceStatus,Endpoint.Address,Endpoint.Port]' \
  --output table

If the status is "available," the instance is running. Note the endpoint address for testing.

Check the instance's security group:

bash
aws rds describe-db-instances \
  --db-instance-identifier my-database \
  --query 'DBInstances[*].VpcSecurityGroups[*].[VpcSecurityGroupId,Status]' \
  --output table

Examine the security group rules:

bash
aws ec2 describe-security-groups \
  --group-ids sg-12345678 \
  --query 'SecurityGroups[*].IpPermissions[*].[FromPort,ToPort,IpProtocol,IpRanges[*].CidrIp]' \
  --output table

Test network connectivity from your location:

```bash # Test if the port is reachable (requires netcat) nc -zv my-database.xxxxx.us-east-1.rds.amazonaws.com 5432

# Or use telnet telnet my-database.xxxxx.us-east-1.rds.amazonaws.com 5432 ```

If you have an EC2 instance in the same VPC, test from there:

bash
aws ssm send-command \
  --instance-ids i-1234567890abcdef0 \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["nc -zv my-database.xxxxx.us-east-1.rds.amazonaws.com 5432"]' \
  --output text

Check DNS resolution:

```bash nslookup my-database.xxxxx.us-east-1.rds.amazonaws.com

# Or dig my-database.xxxxx.us-east-1.rds.amazonaws.com ```

Test the actual database connection with credentials:

```bash # For PostgreSQL psql -h my-database.xxxxx.us-east-1.rds.amazonaws.com -U admin -d postgres -c "SELECT 1;"

# For MySQL mysql -h my-database.xxxxx.us-east-1.rds.amazonaws.com -u admin -p -e "SELECT 1;" ```

Check if you're hitting connection limits:

bash
aws rds describe-db-instances \
  --db-instance-identifier my-database \
  --query 'DBInstances[*].[DBInstanceClass,StorageType,AllocatedStorage]'

Check CloudWatch metrics for connections:

bash
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions DBInstanceIdentifier=my-database \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 \
  --statistics Average,Maximum \
  --output table

Common Causes and Solutions

Security Group Blocking Access

The most common cause. The security group attached to the RDS instance must allow inbound traffic on the database port from your client's IP or security group.

First, identify your client's IP or security group:

  • For EC2 instances: Get the instance's security group
  • For local development: Get your public IP
  • For services: Get their IP range or security group

Add an inbound rule to the RDS security group:

```bash # Allow access from a specific IP (for local development) aws ec2 authorize-security-group-ingress \ --group-id sg-12345678 \ --protocol tcp \ --port 5432 \ --cidr 203.0.113.50/32

# Allow access from an EC2 security group (for applications) aws ec2 authorize-security-group-ingress \ --group-id sg-12345678 \ --protocol tcp \ --port 5432 \ --source-group sg-87654321 ```

For MySQL, use port 3306. For other engines, adjust accordingly: - PostgreSQL: 5432 - MySQL: 3306 - MariaDB: 3306 - SQL Server: 1433 - Oracle: 1521

Verify the rule was added:

bash
aws ec2 describe-security-groups \
  --group-ids sg-12345678 \
  --query 'SecurityGroups[*].IpPermissions[?FromPort==`5432`]'

Wrong VPC or Subnet

If your RDS instance is in a different VPC than your application, they can't communicate directly. Check which VPC the instance is in:

bash
aws rds describe-db-instances \
  --db-instance-identifier my-database \
  --query 'DBInstances[*].DBSubnetGroup.VpcId'

Compare with your application's VPC:

bash
aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query 'Reservations[*].Instances[*].VpcId'

If they're in different VPCs, you have several options:

  1. 1.VPC Peering - Connect the VPCs:

```bash # Create peering connection aws ec2 create-vpc-peering-connection \ --vpc-id vpc-app \ --peer-vpc-id vpc-db

# Accept the peering request (in the other VPC's account if cross-account) aws ec2 accept-vpc-peering-connection \ --vpc-peering-connection-id pcx-12345678

# Add route in the application VPC aws ec2 create-route \ --route-table-id rtb-app \ --destination-cidr-block 10.0.2.0/24 \ --vpc-peering-connection-id pcx-12345678

# Add route in the database VPC aws ec2 create-route \ --route-table-id rtb-db \ --destination-cidr-block 10.0.1.0/24 \ --vpc-peering-connection-id pcx-12345678 ```

  1. 1.Move the RDS instance - Create a new instance in the correct VPC and migrate data

Public Access Disabled

If you're connecting from outside AWS, the RDS instance must be publicly accessible. Check:

bash
aws rds describe-db-instances \
  --db-instance-identifier my-database \
  --query 'DBInstances[*].PubliclyAccessible'

If it returns false, you can enable public access:

bash
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --publicly-accessible \
  --apply-immediately

However, this is not recommended for production. A better approach is to use a bastion host or VPN.

Create a bastion host for secure access:

bash
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --key-name my-key-pair \
  --security-group-ids sg-bastion \
  --subnet-id subnet-public

Then tunnel to RDS through the bastion:

bash
ssh -i my-key.pem -L 5432:my-database.xxxxx.us-east-1.rds.amazonaws.com:5432 ec2-user@bastion-public-ip

Now connect to localhost:5432 instead.

DNS Resolution Issues

Sometimes the RDS endpoint doesn't resolve correctly. This can happen with: - Custom DNS settings - VPC DNS configuration issues - Split-horizon DNS problems

Check if your VPC has DNS resolution enabled:

bash
aws ec2 describe-vpcs \
  --vpc-ids vpc-12345678 \
  --query 'Vpcs[*].[EnableDnsSupport,EnableDnsHostnames]'

Both should be true. Enable them if needed:

```bash aws ec2 modify-vpc-attribute \ --vpc-id vpc-12345678 \ --enable-dns-support

aws ec2 modify-vpc-attribute \ --vpc-id vpc-12345678 \ --enable-dns-hostnames ```

Try connecting by IP instead of hostname (not recommended for production as RDS IPs can change):

```bash # Get the IP nslookup my-database.xxxxx.us-east-1.rds.amazonaws.com

# Connect directly psql -h 10.0.1.50 -U admin -d postgres ```

Connection Limit Exhausted

RDS has a maximum number of connections based on instance class. Check if you're hitting the limit:

bash
# Check current connections via CloudWatch
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions DBInstanceIdentifier=my-database \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 \
  --statistics Maximum

If connections are maxed out, check for connection leaks in your application. You might also need to increase the instance class:

bash
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --db-instance-class db.t3.large \
  --apply-immediately

Or implement connection pooling using: - PgBouncer for PostgreSQL - ProxySQL for MySQL - Amazon RDS Proxy

Create an RDS Proxy:

bash
aws rds create-db-proxy \
  --db-proxy-name my-proxy \
  --engine-family POSTGRESQL \
  --auth AuthScheme=SECRETS,SecretArn=arn:aws:secretsmanager:...,IAMAuth=REQUIRED \
  --role-arn arn:aws:iam::123456789012:role/rds-proxy-role \
  --vpc-subnet-ids subnet-1 subnet-2 subnet-3 \
  --vpc-security-group-ids sg-12345678

Database Overload

If the database is overloaded, it might not accept new connections. Check CPU and memory:

bash
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name CPUUtilization \
  --dimensions DBInstanceIdentifier=my-database \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 \
  --statistics Average,Maximum \
  --output table

If CPU is high, identify and optimize slow queries:

bash
# Enable Performance Insights if not already enabled
aws rds modify-db-instance \
  --db-instance-identifier my-database \
  --enable-performance-insights \
  --apply-immediately

Verification Steps

After making changes, verify connectivity:

```bash # Test port connectivity nc -zv my-database.xxxxx.us-east-1.rds.amazonaws.com 5432

# Test database connection psql -h my-database.xxxxx.us-east-1.rds.amazonaws.com -U admin -d postgres -c "SELECT version();" ```

Create a simple test script to validate your application can connect:

bash
# For a Python application
python3 -c "
import psycopg2
conn = psycopg2.connect(
    host='my-database.xxxxx.us-east-1.rds.amazonaws.com',
    database='postgres',
    user='admin',
    password='your-password'
)
print('Connection successful!')
conn.close()
"

Set up monitoring for future issues:

bash
aws cloudwatch put-metric-alarm \
  --alarm-name rds-connections-high \
  --alarm-description "RDS database connections approaching limit" \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions DBInstanceIdentifier=my-database \
  --statistic Average \
  --period 300 \
  --threshold 80 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --evaluation-periods 2 \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:my-alerts