Initializing Enclave...

How to Fix AWS RDS PostgreSQL 'No Route to Host' Connection Error

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–20 mins depending on root cause

TL;DR

  • What broke: Your compute resource (EC2, Lambda, ECS task) cannot establish a TCP connection to RDS port 5432. The OS-level error No route to host means a firewall (Security Group or NACL) is actively dropping packets or there is no valid network path — this is not a DNS or credential issue.
  • How to fix it: Audit the RDS Security Group inbound rules, the client Security Group outbound rules, subnet route tables, and NACLs. One of these is blocking TCP 5432 between your client and the RDS ENI.
  • Fast path: Use our Client-Side Sandbox below to auto-refactor your Terraform security group and RDS module config — paste your failing .tf and get corrected ingress rules generated locally without sending your VPC IDs or DB strings anywhere.

The Incident

Raw error:

psql: error: could not connect to server: No route to host
        Is the server running on host "mydb.cxyz123.us-east-1.rds.amazonaws.com" (10.0.3.47)
        and accepting TCP/IP connections on port 5432?

This is not a PostgreSQL authentication error. The TCP handshake never completes. The client's kernel received an ICMP host unreachable or the SYN packet was silently dropped and timed out. Your application is dead in the water — connection pool exhaustion follows within seconds in production, cascading into full service unavailability.


The Attack Vector / Blast Radius

Why this kills production:

  • Every application thread waiting on a DB connection blocks. HikariCP, pgBouncer, SQLAlchemy pools all hit connectionTimeout simultaneously.
  • If this fires during a deployment, your new task revisions fail health checks, triggering a rollback loop.
  • Security angle: This error is also the correct behavior you want when an unauthorized host tries to reach RDS — but when it hits your own app, it means your Security Group rules have drifted, a Terraform apply partially failed, or someone manually edited SG rules in the console and broke the reference chain.
  • In multi-account VPC peering or Transit Gateway setups, a missing route table entry is the #1 cause. The DNS resolves fine, the private IP is reachable on paper, but the return path route is absent — packets go in, responses never come back.

The four failure points, in order of frequency:

  1. RDS Security Group missing inbound rule for TCP 5432 from the client SG or CIDR
  2. Client Security Group missing outbound rule for TCP 5432 (default outbound allows all, but explicit deny or restrictive egress breaks this)
  3. NACL stateless rules blocking ephemeral return ports (1024–65535) on the subnet
  4. Route table missing entry for the RDS subnet (cross-AZ, peered VPC, or TGW scenario)

How to Fix It

Basic Fix — Security Group Inbound Rule (AWS Console / CLI)

The RDS instance's Security Group must allow inbound TCP 5432 from the Security Group attached to your client, not from 0.0.0.0/0.

# AWS CLI — add missing ingress rule to RDS security group
- # No inbound rule exists for port 5432
+ aws ec2 authorize-security-group-ingress \
+   --group-id sg-0abc123def456 \
+   --protocol tcp \
+   --port 5432 \
+   --source-group sg-0zzz789app111

Enterprise Best Practice — Terraform Security Group with Least-Privilege SG Reference

Never use CIDR blocks for internal RDS access. Always reference the application Security Group ID directly. This prevents lateral movement if the app subnet CIDR is ever reused.

 resource "aws_security_group_rule" "rds_ingress_postgres" {
   type              = "ingress"
   from_port         = 5432
   to_port           = 5432
   protocol          = "tcp"
   security_group_id = aws_security_group.rds.id
-  cidr_blocks       = ["10.0.0.0/8"]  # Too broad, entire RFC1918 range
+  source_security_group_id = aws_security_group.app.id  # Scoped to app tier only
 }

# Also verify: explicit egress on the APP security group if you've locked it down
 resource "aws_security_group_rule" "app_egress_postgres" {
   type              = "egress"
   from_port         = 5432
   to_port           = 5432
   protocol          = "tcp"
   security_group_id = aws_security_group.app.id
-  # MISSING — egress rule absent when default egress was removed
+  source_security_group_id = aws_security_group.rds.id
 }

NACL Fix — Stateless Ephemeral Port Return Traffic

NACLs are stateless. If you've restricted your RDS subnet NACL, you must explicitly allow outbound ephemeral ports back to the client.

 resource "aws_network_acl_rule" "rds_subnet_egress_ephemeral" {
   network_acl_id = aws_network_acl.rds_subnet.id
   rule_number    = 100
   egress         = true
   protocol       = "tcp"
-  # Rule missing entirely — return traffic dropped
+  from_port      = 1024
+  to_port        = 65535
+  cidr_block     = "10.0.1.0/24"  # App subnet CIDR
+  rule_action    = "allow"
 }

Route Table Fix — VPC Peering / TGW Cross-Account

 resource "aws_route" "app_to_rds_vpc" {
   route_table_id         = aws_route_table.app_subnet.id
   destination_cidr_block = "10.1.3.0/24"  # RDS subnet in peered VPC
-  # Route missing — traffic hits local VPC boundary and drops
+  vpc_peering_connection_id = aws_vpc_peering_connection.app_to_data.id
 }

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

Checkov — catch missing SG ingress before terraform apply:

# .checkov.yml
checks:
  - CKV_AWS_25   # Ensure no security groups allow ingress from 0.0.0.0/0 to port 5432
  - CKV_AWS_382  # Ensure RDS is not publicly accessible
  - CKV2_AWS_5   # Ensure SGs are attached to resources (detached SGs = drift indicator)

OPA / Conftest policy — enforce SG reference over CIDR for RDS:

package terraform.aws.rds

deny[msg] {
  rule := input.resource.aws_security_group_rule[_]
  rule.config.from_port == 5432
  rule.config.cidr_blocks != null
  msg := "RDS port 5432 ingress must use source_security_group_id, not cidr_blocks"
}

GitHub Actions — block PR merge on SG drift:

- name: Checkov IaC Scan
  uses: bridgecrewio/checkov-action@master
  with:
    directory: ./terraform
    check: CKV_AWS_25,CKV_AWS_382
    soft_fail: false  # Hard fail the pipeline

Operational checklist to run before every RDS-touching deployment:

  1. aws ec2 describe-security-groups --group-ids <rds-sg-id> — verify port 5432 inbound rule exists and references the correct source SG
  2. aws ec2 describe-network-acls --filters Name=association.subnet-id,Values=<rds-subnet-id> — verify ephemeral egress rule present
  3. aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=<app-subnet-id> — verify route to RDS subnet exists
  4. nc -zv <rds-endpoint> 5432 from the app host — TCP reachability test before starting the app process

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →