RAPID INCIDENT TRIAGE & ACTION PLAN•Performance
01. Error Signature
Nginx AWS NLB 110 Connection Timeout Fix
02. Root Cause Vector

AWS NLB upstream connection timeouts in Nginx occur when health checks, security groups, or proxy protocol misconfigs silently drop TCP c...

03. Rapid Prescription

Verify upstream socket availability, proxy_pass hostnames, and increase proxy_read_timeout.

Zero-Trust Client IDE/ Performance

Nginx AWS NLB 110 Connection Timeout Fix

AWS NLB upstream connection timeouts in Nginx occur when health checks, security groups, or proxy protocol misconfigs silently drop TCP connections before the upstream responds.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(82L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
apiVersion: v1
kind: ConfigMap
metadata:
  name: legacy-nginx-upstream-config
  namespace: pci-secure-zone
  labels:
    app: legacy-api-gateway
    env: prod
    # TODO: Remove this after PCI audit 2023-11
    #compliance-tier: pci-dss-3.2.1
    # DEPRECATED: Use new config from my-registry.internal/nginx-plus-config:2.1.0
    # but we can't upgrade due to vendor lock-in with old auth module

data:
  # MAIN UPSTREAM BLOCK - DO NOT MODIFY WITHOUT CHANGE CONTROL TICKET
  nginx-upstream.conf: |
    upstream backend_servers {
        # AWS NLB endpoint - this is the corporate standard for PCI workloads
        server internal-k8s-nlb-abc123def456.elb.us-west-2.amazonaws.com:443;
        # Fallback to old direct IPs (commented out but kept for DR)
        # server 10.0.100.15:8443 max_fails=3;
        # server 10.0.100.16:8443 max_fails=3;
        # VULNERABILITY: Implicitly sets a 60s connect timeout due to default keepalive,
        # but AWS NLB idle timeout is 350s, causing race on long-lived connections.
        keepalive 32;  # Corporate mandate from infra team
    }

    server {
        listen 443 ssl;
        server_name api.legacy-corp.internal;
        
        # SSL config - copied from old Apache config, needs review
        ssl_certificate     /etc/ssl/certs/wildcard-legacy-corp-internal.pem;
        ssl_certificate_key /etc/ssl/private/wildcard.key;
        ssl_protocols       TLSv1.2;  # PCI requirement
        
        # TODO: Rotate this key - it's been here since migration
        ssl_dhparam /etc/ssl/dhparams.pem;
        
        location /v1/transactions {
            # AWS_ACCESS_KEY and secret injected via K8s secret volume
            proxy_set_header X-AWS-Access-Key "AKIAIOSFODNN7EXAMPLE";
            proxy_set_header X-AWS-Secret-Key "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
            
            # Stripe key for fallback processing (commented but still in env)
            # proxy_set_header X-Stripe-API-Key "sk_live_4eC39HqLyjWDarjtT1zdp7dc";
            
            proxy_pass https://backend_servers;
            
            # Corporate timeout policy - conflicts with upstream keepalive
            proxy_connect_timeout 75s;  # Meets SLA docs but...
            proxy_send_timeout 180s;
            proxy_read_timeout 180s;
            
            # TODO: This health check is broken since NLB migration
            # health_check interval=30s fails=3 passes=2;
        }
        
        # Old PCI logging endpoint - kept for compliance
        location /pci/logs {
            # Database URI for audit logs (PostgreSQL)
            proxy_set_header X-DB-URI "postgresql://audit_user:Tr0ub4dor&3@audit-db.legacy-corp.internal:5432/pci_audit?sslmode=require";
            
            # RSA private key for log signing (PEM format)
            proxy_set_header X-Log-Signing-Key "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEArGJ7vSKAqG8zWjPp7lBwQwXQKv7Q8cL9zKqJt8vQ7pZJN1j\n... (truncated for brevity) ...\n-----END RSA PRIVATE KEY-----";
            
            proxy_pass https://backend_servers/secure/logs;
            proxy_connect_timeout 10s;  # Aggressive timeout for logs
        }
    }

  # Environment overrides (from Helm chart values.yaml merge)
  env-overrides.yaml: |
    nginx:
      upstream:
        # This is overridden by ConfigMap but chart forces it
        connectTimeout: 60  # seconds
      resources:
        limits:
          cpu: 200m  # Too low for TLS termination
        # memory: 512Mi  # commented due to OOMs
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix Nginx 'connect() failed (110: Connection timed out)' with AWS Network Load Balancer

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 15–30 mins

TL;DR

  • What broke: Nginx cannot establish a TCP connection to the upstream target registered behind an AWS NLB — the socket hangs until the OS timeout fires (110 seconds default).
  • How to fix it: Align NLB idle timeout, Nginx proxy_connect_timeout/proxy_read_timeout, target group health check ports, and security group ingress rules. Enable NLB proxy protocol only if Nginx is configured to consume it.
  • Fast path: Use our Client-Side Sandbox above to auto-refactor your Nginx upstream block — paste your config, get a corrected diff without sending your IPs or ARNs to any external server.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What Does This Error Mean?)

Raw error from /var/log/nginx/error.log:

2024/01/15 03:42:17 [error] 1832#1832: *58291 connect() failed (110: Connection timed out)
while connecting to upstream, client: 10.0.1.45, server: api.internal,
request: "POST /v2/process HTTP/1.1", upstream: "http://10.0.3.22:8080/v2/process",
host: "api.internal"

Errno 110 = ETIMEDOUT. The TCP SYN packet left Nginx, hit the NLB, was forwarded to the target, and never received a SYN-ACK. The connection was never established — this is not a slow response, it is a dead socket. Nginx waited proxy_connect_timeout seconds (default: 60s), got nothing, and logged this error. Every request hitting this upstream during that window is a hard 502.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is not a transient blip. Here is the cascading failure path:

  1. NLB does not terminate TCP. Unlike ALB, NLB passes raw TCP to the target. If the target's security group does not allow ingress from the NLB's node IPs (not the NLB's SG — NLBs have no SG in older deployments), the SYN is silently dropped by the target's SG. No RST. No ICMP unreachable. Pure silence.
  2. Nginx upstream keepalive pool exhausts. With keepalive 32 set, all 32 pooled connections can be in a timed-out state simultaneously. New requests queue behind them. Worker processes block. You hit worker_connections limits. Full service outage within minutes under load.
  3. Proxy Protocol mismatch is a silent killer. If NLB has Proxy Protocol v2 enabled on the target group but Nginx does not have listen ... proxy_protocol configured, Nginx receives the PP2 binary header as the first bytes of the HTTP request — it cannot parse it, the upstream handshake never completes, and you get errno 110.
  4. NLB idle timeout vs. Nginx keepalive timeout mismatch. NLB's default idle timeout is 350 seconds. If Nginx keepalive_timeout is set lower (e.g., 65s), Nginx closes the connection. NLB does not know this. It continues routing new requests to the now-dead connection tuple. The target sees a SYN on a port it considers closed → RST or timeout.

How to Fix It

Step 1 — Verify the Security Group (Most Common Root Cause)

NLB nodes source traffic from the NLB's node private IPs in each AZ, not from a single SG. For NLBs with client IP preservation enabled, traffic arrives from the client's IP.

# Get NLB node IPs across AZs
aws ec2 describe-network-interfaces \
  --filters Name=description,Values="ELB net/<your-nlb-name>/*" \
  --query 'NetworkInterfaces[*].PrivateIpAddress'

Add ingress rules to the target's SG for each NLB node IP on the application port. Or, if using VPC-native NLB (2023+), assign a security group to the NLB itself and allow that SG as the source.

Step 2 — Fix Nginx Upstream and Proxy Timeout Config

 upstream backend {
-    server 10.0.3.22:8080;
+    server 10.0.3.22:8080 max_fails=3 fail_timeout=10s;
+    keepalive 16;
+    keepalive_requests 1000;
+    keepalive_timeout 300s;  # Must be < NLB idle timeout (350s)
 }

 server {
     listen 80;
+    # If NLB Proxy Protocol v2 is enabled on target group:
+    # listen 80 proxy_protocol;

     location / {
         proxy_pass http://backend;
-        proxy_connect_timeout 60s;
-        proxy_read_timeout 60s;
-        proxy_send_timeout 60s;
+        proxy_connect_timeout 10s;
+        proxy_read_timeout 120s;
+        proxy_send_timeout 30s;
+        proxy_http_version 1.1;
+        proxy_set_header Connection "";
     }
 }

Why proxy_http_version 1.1 + Connection "": HTTP/1.0 forces Connection: close on every request, destroying keepalive. Without this pair, keepalive in the upstream block is useless.

Step 3 — Validate NLB Target Group Health Check

If the health check port differs from the traffic port, a target can be healthy in the TG while the application port is firewalled or not listening.

# Check registered targets and their health
aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:<region>:<acct>:targetgroup/<name>/<id>

Expected: "State": "healthy". If you see "State": "unused" or "State": "draining", the target is not receiving traffic regardless of Nginx config.

Enterprise Best Practice — Terraform-Enforced NLB Config

 resource "aws_lb_target_group" "api" {
   name        = "api-tg"
   port        = 8080
   protocol    = "TCP"
   vpc_id      = var.vpc_id

+  connection_termination = true
+  deregistration_delay   = 30
+  preserve_client_ip     = false  # Set true only if SG rules account for client IPs

   health_check {
     protocol            = "TCP"
     port                = "traffic-port"
-    healthy_threshold   = 10
-    unhealthy_threshold = 10
+    healthy_threshold   = 3
+    unhealthy_threshold = 3
+    interval            = 10
   }
 }

+resource "aws_lb_target_group_attachment" "proxy_protocol" {
+  # Enable only if Nginx is configured with proxy_protocol on listen directive
+  # aws_lb_target_group attribute: proxy_protocol_v2 = false (default, keep false unless intentional)
+}

💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

Checkov — Catch Proxy Protocol Mismatch Pre-Deploy

# Install and scan your Terraform plan
pip install checkov
checkov -d ./terraform --check CKV_AWS_91,CKV_AWS_92

OPA Policy — Enforce keepalive_timeout < NLB Idle Timeout

package nginx.nlb

deny[msg] {
  input.upstream.keepalive_timeout_seconds >= 350
  msg := "upstream keepalive_timeout must be < NLB idle timeout (350s) to prevent stale connection routing"
}

deny[msg] {
  input.upstream.keepalive
  not input.proxy_http_version == "1.1"
  msg := "proxy_http_version 1.1 required when upstream keepalive is enabled"
}

GitHub Actions — Nginx Config Lint on PR

- name: Lint Nginx Config
  run: |
    docker run --rm -v ${{ github.workspace }}/nginx:/etc/nginx:ro \
      nginx:alpine nginx -t
    # Fail fast before any ECS/EC2 deployment

Runbook checklist before every NLB target group change:

  • SG ingress allows NLB node IPs on application port
  • Proxy Protocol setting matches on both NLB TG and Nginx listen directive
  • keepalive_timeout in Nginx upstream < 350s
  • proxy_http_version 1.1 + proxy_set_header Connection "" set in location block
  • Health check port = traffic port unless explicitly separated with matching SG rules

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.Why does the Nginx 110 timeout only happen on some requests behind an AWS NLB, not all of them?
The root cause is AZ-level inconsistency in your infrastructure's security posture. An AWS NLB distributes traffic across Availability Zones. A single misconfigured security group or a stale keepalive connection in one AZ creates a latent failure path. This isn't an application bug; it's a systemic infrastructure vulnerability where requests are randomly routed to a broken target, producing intermittent 502 errors that evade standard monitoring. Diagnose this precisely by checking target health per AZ with `aws elbv2 describe-target-health` and manually auditing security group rules for each NLB node IP—a tedious but necessary process to establish authority over your network layer.
Q2.Does enabling Proxy Protocol v2 on the NLB target group automatically cause connect() timeout errors in Nginx?
Absolutely, and it's a classic silent killer. Enabling Proxy Protocol v2 on the target group without configuring Nginx to parse it injects a binary header into the TCP stream. Nginx, expecting HTTP, stalls indefinitely, causing the 110 timeout. Manually fixing this requires precise edits to the `listen` directive and `real_ip_header`, a high-risk operation. One syntax error or deployment lag between NLB and Nginx changes creates a total service outage blast radius. Do not attempt this manually. Instead, drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that synchronizes configuration and validates the change in isolation before rollout.
Q3.What is the correct keepalive_timeout value for Nginx upstreams behind an AWS NLB?
The correct value is 300-320 seconds, strictly less than the NLB's immutable 350-second idle timeout. However, manually calculating and enforcing this timeout across hundreds of services and environments is a compliance nightmare. A single misconfigured `keepalive_timeout` value in a legacy mono-repo service can cause cascading connection resets, breaking SLAs. At enterprise scale, this manual tuning is unsustainable and fails audit trails. Upgrade to the StackEngine CI/CD Automator (Team Edition) for on-prem, automated pull request generation with Maker-Checker review. It enforces these timeouts as policy, generates compliance reports, and prevents drift across your entire deployment pipeline, turning a fragile manual fix into a governed, scalable control.