## TL;DR
"Connection timed out" to RDS almost always means the network path is blocked, not that the database is down: the security group does not allow your source IP on the DB port. Work the checklist in order: can you reach the port, does the SG allow your IP, is the NACL blocking, is the route table right. Do not restart the database; it is not the problem.

## Error / query
```text
"connection timed out" to RDS: security group checklist
```

## Use this skill when
- Your app gets "connection timed out" reaching RDS but the DB status is available
- A new app server or Lambda cannot reach a database that older hosts reach fine
- The error started after a security group or VPC change
- You need a repeatable checklist instead of clicking around the console

## Not for this skill when
- The error is "connection refused"; that means you reached the host but nothing listens, different problem
- RDS itself shows as failed or storage-full; fix the database first
- Auth fails with "password authentication failed"; the network is fine, the credential is wrong

## Steps

### Step 1: Test whether the port is reachable at all
```bash
timeout 10 bash -c 'echo > /dev/tcp/[db-endpoint]/5432' && echo "port open" || echo "port blocked or timed out"
```
Expected: "port open" means networking is fine and the problem is elsewhere; "timed out" confirms a network-layer block.

### Step 2: Check what the RDS security group allows
```bash
aws rds describe-db-instances --db-instance-identifier mydb --query 'DBInstances[0].VpcSecurityGroups[].VpcSecurityGroupId' --output text
```
Expected: the SG IDs; take the first one into step 3.

### Step 3: List the inbound rules on that security group
```bash
aws ec2 describe-security-groups --group-ids [sg-id-from-step-2] --query 'SecurityGroups[0].IpPermissions' --output table
```
Expected: inbound rules; your source IP (or its SG) must appear on the DB port (5432 for Postgres, 3306 for MySQL). If it does not, that is the fix.

### Step 4: Check the subnet NACL is not blocking the return traffic
```bash
aws ec2 describe-network-acls --filters Name=vpc-id,Values=[vpc-id] --query 'NetworkAcls[].Entries' --output table | head -20
```
Expected: allow rules for the DB port in both directions; NACLs are stateless, so the ephemeral return range needs an allow too.

### Step 5: Verify DNS resolves the endpoint to the right IP
```bash
nslookup [db-endpoint] | tail -3
```
Expected: a private IP in your VPC range; a public IP means the endpoint is resolving wrong or the DB is not in the expected VPC.

## Variant phrasings

### "Cannot connect to RDS from EC2"
Same checklist; the most common cause is the EC2 instance's security group not being listed as a source in the RDS security group.

### "RDS connection timeout from Lambda"
Lambda in a VPC needs the RDS SG to allow the Lambda's SG, and the Lambda needs a NAT route if it also calls the internet; check both.

### "Connection timed out vs connection refused to RDS"
Timed out is a firewall or routing block (this skill); refused means you got through but the DB is not listening on that port.

## Why it happens
RDS security groups default to deny, and every new client (host, Lambda, CI runner) has a new source IP or SG that nobody added. The database is healthy; the front door is just closed to the new arrival.

## Edge cases and pitfalls
- Opening the DB port to the all-interfaces address/0 "to test" and forgetting to close it; use your specific IP and set a reminder.
- The SG rule references another SG, but the client was launched into a different SG after a refactor; the reference silently stops matching.
- RDS in a private subnet with no route from the client's subnet; SGs are fine but packets have no path.
- DNS caching on the client can point at an old IP after a failover; flush or wait out the TTL before blaming the SG.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_4oUmtqlkOE9kmPSjKow-zg
