Error Medic

AWS RDS Access Denied: Fix Connection Refused and Timeout Errors (2024 Guide)

Fix AWS RDS access denied, connection refused, and timeout errors in minutes. Step-by-step guide covering IAM, security groups, VPC, and DB credentials.

Last updated:
Last verified:
2,512 words
Key Takeaways
  • Access denied on RDS is caused by one of four root causes: wrong DB credentials, IAM authentication misconfiguration, security group rules blocking the port, or SSL/TLS certificate mismatch
  • Connection refused (ECONNREFUSED) almost always means the security group inbound rule for port 3306 (MySQL) or 5432 (PostgreSQL) is missing, or the RDS instance is not set to Publicly Accessible when connecting from outside a VPC
  • RDS timeout errors are most commonly caused by a missing VPC route, network ACL blocking return traffic, or the RDS instance being in a private subnet without a NAT gateway or VPC endpoint
  • Quick fix checklist: (1) verify the endpoint and port with nmap or nc, (2) check security group inbound rules, (3) confirm IAM policy allows rds-db:connect if using IAM auth, (4) test with the native DB client using the master password before blaming application config
Fix Approaches Compared
MethodWhen to UseTime to ApplyRisk Level
Add security group inbound rulePort unreachable from your IP or application subnet2 minutesLow — additive change only
Enable Publicly Accessible flagConnecting from outside the VPC (local dev, CI/CD)5 minutes + rebootLow — does not expose DB without security group allowing it
Attach correct IAM policy (rds-db:connect)Using IAM database authentication instead of password5 minutesLow — IAM change, no DB restart needed
Rotate / reset master passwordAccess denied with correct endpoint but wrong credentials3 minutes via consoleLow — only affects password, not schema
Fix network ACL stateful rulesTimeout from specific subnet even with SG rule open10 minutesMedium — ACL rules are stateful-less; wrong order blocks all traffic
Move RDS to correct subnet / VPCApplication and DB in different VPCs with no peering30-60 minutesHigh — requires snapshot + restore or blue/green deployment
Download updated SSL bundle (rds-ca-2019)SSL handshake failure / certificate verify failed5 minutesLow — client-side only change

Understanding AWS RDS Access Denied, Connection Refused, and Timeout Errors

AWS RDS connection failures surface as three distinct error classes, each pointing to a different layer of the stack. Mixing them up wastes hours of debugging time. Before touching any config, identify which exact error you have.

Error class 1 — Access Denied (authentication layer)

Access denied for user 'app_user'@'10.0.2.45' (using password: YES)
PSQLEXCEPTOR: password authentication failed for user "app_user"
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Access denied

This error means the TCP connection reached RDS successfully. The problem is at the database authentication layer: wrong password, wrong username, the DB user doesn't exist, or IAM database authentication token expired.

Error class 2 — Connection Refused (network layer, hard block)

Error: connect ECONNREFUSED 10.0.1.23:5432
Can't connect to MySQL server on 'mydb.xxxx.us-east-1.rds.amazonaws.com' (111)
Connection refused (port 5432)

A hard refusal means something is actively rejecting the TCP SYN packet. This is almost always the security group having no inbound rule for the DB port, or the RDS instance stopped.

Error class 3 — Timeout (network layer, silent drop)

Error: connect ETIMEDOUT
Operational Error: could not connect to server: Connection timed out
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

A timeout means packets are being dropped silently — the SYN goes out but no SYN-ACK returns. This points to a network ACL, route table, or the instance being in a private subnet with no return path.


Step 1: Confirm the Endpoint and Port Are Reachable

Before changing any AWS configuration, verify the network path from the machine that is failing.

# Replace with your actual RDS endpoint
RDS_HOST="mydb.xxxx.us-east-1.rds.amazonaws.com"
RDS_PORT=5432  # 3306 for MySQL, 5432 for PostgreSQL, 1433 for MSSQL

# Quick TCP check (available everywhere)
nc -zv $RDS_HOST $RDS_PORT

# Or with nmap for more detail
nmap -p $RDS_PORT $RDS_HOST

# Traceroute to see where packets die
traceroute -T -p $RDS_PORT $RDS_HOST
  • If nc returns Connection refused immediately → security group or ACL is blocking (go to Step 3)
  • If nc hangs for 30+ seconds then times out → route or ACL drop (go to Step 4)
  • If nc returns Connection to ... succeededcredentials or SSL problem (go to Step 5)

Step 2: Check RDS Instance State

An RDS instance that is stopped, rebooting, or in a failed state will not accept connections regardless of network config.

# Using AWS CLI
aws rds describe-db-instances \
  --db-instance-identifier YOUR_DB_ID \
  --query 'DBInstances[0].{Status:DBInstanceStatus,Endpoint:Endpoint,MultiAZ:MultiAZ}'

# Expected output for a healthy instance:
# {
#   "Status": "available",
#   "Endpoint": { "Address": "...", "Port": 5432 },
#   "MultiAZ": false
# }

If status is modifying, rebooting, storage-full, or incompatible-parameters, fix that first. A storage-full instance will refuse all writes and many connections.


Step 3: Fix Security Group Inbound Rules (Most Common Fix)

This resolves ~60% of RDS connection failures. Every RDS instance needs an inbound rule on its security group allowing traffic on the DB port from your source IP, security group, or CIDR.

# Find the security group attached to your RDS instance
SG_ID=$(aws rds describe-db-instances \
  --db-instance-identifier YOUR_DB_ID \
  --query 'DBInstances[0].VpcSecurityGroups[0].VpcSecurityGroupId' \
  --output text)

# Check current inbound rules
aws ec2 describe-security-groups \
  --group-ids $SG_ID \
  --query 'SecurityGroups[0].IpPermissions'

# Add a rule allowing your application security group (preferred over CIDR)
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 5432 \
  --source-group sg-0123456789abcdef0  # your app server's SG ID

# OR allow a specific CIDR (less preferred, use for dev/debugging)
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 5432 \
  --cidr 10.0.0.0/16

Key principle: Never add 0.0.0.0/0 as an inbound rule for a database port. Reference security groups by ID, not CIDR blocks, for application-to-database rules.


Step 4: Debug Network ACLs and Route Tables for Timeout Errors

Network ACLs are stateless — you need both an inbound rule AND an outbound rule for ephemeral ports (1024-65535) for return traffic to flow. This is the most commonly missed configuration.

# Get the subnet your RDS instance is in
SUBNET_ID=$(aws rds describe-db-instances \
  --db-instance-identifier YOUR_DB_ID \
  --query 'DBInstances[0].DBSubnetGroup.Subnets[0].SubnetIdentifier' \
  --output text)

# Find the NACL associated with that subnet
aws ec2 describe-network-acls \
  --filters "Name=association.subnet-id,Values=$SUBNET_ID" \
  --query 'NetworkAcls[0].{Inbound:Entries[?Egress==`false`],Outbound:Entries[?Egress==`true`]}'

For a private RDS subnet, you need:

  • Inbound: Allow TCP port 5432 (or 3306) from application subnet CIDR
  • Outbound: Allow TCP ports 1024-65535 to application subnet CIDR (ephemeral/return traffic)

If your RDS is in a private subnet without internet access, also verify the route table has a route to a NAT Gateway or VPC endpoint for AWS service calls your application may make.


Step 5: Fix Access Denied — DB Credentials and IAM Auth

5a. Password authentication failures

Reset the master password via CLI or console if you are locked out:

aws rds modify-db-instance \
  --db-instance-identifier YOUR_DB_ID \
  --master-user-password 'NewSecurePassword123!' \
  --apply-immediately

# Wait for the modification to complete
aws rds wait db-instance-available --db-instance-identifier YOUR_DB_ID

For application user passwords, connect as the master user and reset:

-- PostgreSQL
ALTER USER app_user WITH PASSWORD 'new_password';

-- MySQL
ALTER USER 'app_user'@'%' IDENTIFIED BY 'new_password';
FLUSH PRIVILEGES;

5b. IAM database authentication (token expiry)

IAM auth tokens expire after 15 minutes. If your application is caching the token, you will see periodic access denied errors that resolve and recur.

# Generate a fresh IAM auth token
aws rds generate-db-auth-token \
  --hostname mydb.xxxx.us-east-1.rds.amazonaws.com \
  --port 5432 \
  --region us-east-1 \
  --username app_user

# The IAM role executing the above needs rds-db:connect permission:
# {
#   "Effect": "Allow",
#   "Action": "rds-db:connect",
#   "Resource": "arn:aws:rds-db:us-east-1:123456789012:dbuser:db-XXXXXXXXXX/app_user"
# }

Step 6: SSL Certificate Issues

AWS rotated the RDS CA certificate in 2023 (rds-ca-rsa2048-g1). If your client still has the old certificate bundle, SSL handshakes will fail with:

SSL SYSCALL error: EOF detected
certificate verify failed: unable to get local issuer certificate
# Download the global bundle covering all regions and CAs
wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -O /etc/ssl/certs/rds-global-bundle.pem

# PostgreSQL connection with updated cert
psql "host=$RDS_HOST port=5432 dbname=mydb user=app_user \
      sslmode=verify-full sslrootcert=/etc/ssl/certs/rds-global-bundle.pem"

# MySQL connection with updated cert
mysql -h $RDS_HOST -P 3306 -u app_user -p \
      --ssl-ca=/etc/ssl/certs/rds-global-bundle.pem

Step 7: Publicly Accessible Flag for External Connections

If you are connecting from outside the VPC (local development, GitHub Actions, external monitoring), the instance must have PubliclyAccessible=true AND a security group rule allowing your external IP.

# Enable public accessibility
aws rds modify-db-instance \
  --db-instance-identifier YOUR_DB_ID \
  --publicly-accessible \
  --apply-immediately

# Get your current public IP and add it
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 5432 \
  --cidr "${MY_IP}/32"

Warning: For production databases, prefer a bastion host, AWS Systems Manager Session Manager port forwarding, or VPN over setting PubliclyAccessible=true.


Systematic Diagnostic Flowchart

  1. Can you resolve the DNS name?nslookup mydb.xxxx.us-east-1.rds.amazonaws.com — if NXDOMAIN, the endpoint is wrong
  2. Is the instance available? → Check via CLI or console
  3. Does nc -zv succeed? → If refused: security group. If timeout: NACL/route table
  4. Can the master user connect? → If yes, problem is with app user grants; if no, check SSL or password
  5. Is access denied happening periodically? → IAM token expiry; implement token refresh in connection pool
  6. Does it fail only for writes? → Check if instance is storage-full or read replica is being used for writes

Frequently Asked Questions

bash
#!/usr/bin/env bash
# rds-diagnose.sh — RDS connection troubleshooting script
# Usage: RDS_HOST=mydb.xxx.us-east-1.rds.amazonaws.com RDS_PORT=5432 DB_ID=mydb ./rds-diagnose.sh

set -euo pipefail

RDS_HOST="${RDS_HOST:?Set RDS_HOST}"
RDS_PORT="${RDS_PORT:-5432}"
DB_ID="${DB_ID:?Set DB_ID}"
REGION="${AWS_DEFAULT_REGION:-us-east-1}"

echo "=== 1. RDS INSTANCE STATUS ==="
aws rds describe-db-instances \
  --db-instance-identifier "$DB_ID" \
  --region "$REGION" \
  --query 'DBInstances[0].{Status:DBInstanceStatus,Engine:Engine,MultiAZ:MultiAZ,PubliclyAccessible:PubliclyAccessible,StorageSpace:AllocatedStorage,FreeStorage:FreeStorageSpace}' \
  --output table 2>/dev/null || echo "ERROR: Could not describe instance — check DB_ID and IAM permissions"

echo ""
echo "=== 2. DNS RESOLUTION ==="
nslookup "$RDS_HOST" || echo "FAIL: DNS resolution failed — check endpoint spelling"

echo ""
echo "=== 3. TCP CONNECTIVITY (timeout=5s) ==="
if nc -zv -w 5 "$RDS_HOST" "$RDS_PORT" 2>&1; then
  echo "PASS: TCP port $RDS_PORT is open"
else
  EXIT=$?
  if [ $EXIT -eq 1 ]; then
    echo "FAIL: Connection refused — check security group inbound rules"
  else
    echo "FAIL: Connection timed out — check NACLs, route tables, and VPC peering"
  fi
fi

echo ""
echo "=== 4. SECURITY GROUP RULES ==="
SG_ID=$(aws rds describe-db-instances \
  --db-instance-identifier "$DB_ID" \
  --region "$REGION" \
  --query 'DBInstances[0].VpcSecurityGroups[0].VpcSecurityGroupId' \
  --output text 2>/dev/null)
echo "RDS Security Group: $SG_ID"
aws ec2 describe-security-groups \
  --group-ids "$SG_ID" \
  --region "$REGION" \
  --query 'SecurityGroups[0].IpPermissions[?FromPort==`'"$RDS_PORT"'`]' \
  --output table 2>/dev/null || echo "Could not retrieve SG rules"

echo ""
echo "=== 5. RECENT RDS EVENTS (last 24h) ==="
aws rds describe-events \
  --source-identifier "$DB_ID" \
  --source-type db-instance \
  --duration 1440 \
  --region "$REGION" \
  --query 'Events[*].{Time:Date,Message:Message}' \
  --output table 2>/dev/null | tail -20

echo ""
echo "=== 6. CLOUDWATCH — DB CONNECTIONS (last 10 min) ==="
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnections \
  --dimensions Name=DBInstanceIdentifier,Value="$DB_ID" \
  --start-time "$(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-10M +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --period 60 \
  --statistics Maximum \
  --region "$REGION" \
  --query 'sort_by(Datapoints, &Timestamp)[-3:].{Time:Timestamp,Connections:Maximum}' \
  --output table 2>/dev/null

echo ""
echo "=== DONE ==="
echo "Next steps:"
echo "  - If TCP failed: add inbound rule to SG $SG_ID for port $RDS_PORT"
echo "  - If timeout: check NACL on RDS subnet (needs ephemeral outbound 1024-65535)"
echo "  - If TCP OK but login fails: test with master user via psql/mysql client directly"
echo "  - If connections near max: deploy RDS Proxy or PgBouncer connection pooling"
E

Error Medic Editorial

The Error Medic Editorial team is composed of senior DevOps and SRE engineers with hands-on experience managing large-scale AWS infrastructure. Our troubleshooting guides are written and reviewed by practitioners who have encountered these errors in production, ensuring every step is tested and actionable.

Sources

Related Guides