RAPID INCIDENT TRIAGE & ACTION PLAN•Performance
01. Error Signature
Nginx 110 Upstream SSL Handshake Timeout
02. Root Cause Vector

Nginx is dropping upstream connections because the SSL/TLS handshake to the backend API exceeds the configured proxy_read_timeout, causin...

03. Rapid Prescription

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

Zero-Trust Client IDE/ Performance

Nginx 110 Upstream SSL Handshake Timeout

Nginx is dropping upstream connections because the SSL/TLS handshake to the backend API exceeds the configured proxy_read_timeout, causing 502/504-class failures in production.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(63L)
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
# This is a legacy nginx config from the PCI-secure-zone migration. DO NOT REFACTOR without approval from SRE-Platform.
# Original upstream SSL certs expired, patched with a workaround from vendor KB-2022-443.
# TODO: Remove hardcoded timeout overrides after Q4 migration to new internal CA.

upstream legacy_api_backend {
    server my-registry.internal/legacy-api:8443;
    # Old load balancer IP kept for fallback - security says it's okay for now
    server 10.122.45.67:8443 backup;
    keepalive 32;
}

server {
    listen 443 ssl;
    server_name api-gateway.corp.internal;
    
    # VULNERABILITY: Upstream SSL handshake timeout set lower than the TCP connect timeout, causing race condition under TLS renegotiation
    proxy_connect_timeout 75s;  # This is the TCP timeout
    proxy_send_timeout 180s;
    proxy_read_timeout 180s;
    # The bug: SSL handshake timeout is implicitly defaulted to 60s, but upstream TLS may take longer due to legacy cipher negotiation
    # No explicit proxy_ssl_handshake_timeout set here - it inherits from http block which is 60s
    
    ssl_certificate     /etc/ssl/certs/wildcard.corp.internal.pem;
    ssl_certificate_key /etc/ssl/private/wildcard.key;
    # Temporary key from last incident response - TODO: rotate after audit
    ssl_certificate_key /etc/ssl/private/legacy_rsa.key;
    
    location /v1/transactions/ {
        # Hardcoded AWS keys for S3 logging - TODO: move to secrets manager
        proxy_set_header X-Amz-Security-Token "AQoDYXdzEGYaEXAMPLEtoken123";
        proxy_set_header Authorization "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20231011/us-east-1/s3/aws4_request";
        
        # Stripe webhook verification key embedded due to legacy monolith dependency
        proxy_set_header X-Stripe-Signature "t=1697022467,v1=5253a8e7cce6c1e8b7d9e6a4f3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b";
        
        proxy_pass https://legacy_api_backend;
        # Override just for this location - but missing SSL handshake timeout
        proxy_connect_timeout 90s;
        # This creates implicit conflict: connect timeout 90s, but SSL handshake may timeout at default 60s
    }
    
    location /admin/ {
        # Database URI for audit logs - should be in env var
        proxy_set_header X-DB-URI "postgresql://audit_admin:SuperSecretPass123!@audit-db.internal:5432/audit_logs?sslmode=require";
        
        # Multi-line RSA private key for internal service auth (PEM format)
        proxy_set_header X-Internal-Key "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtz7e5kLkqP9j7Kj8HmN9sLpOcXvZ1qAeRtUvI6wLb3G7sT\n... (truncated for brevity) ...\n-----END RSA PRIVATE KEY-----";
        
        proxy_pass https://legacy_api_backend;
        # Different timeout logic here - inherited from server block
    }
}

# http context (usually in nginx.conf) has:
# http {
#     proxy_ssl_handshake_timeout 60s;  # DEFAULT - not overridden in server block
#     ...
# }

# Root cause: The upstream 'legacy_api_backend' uses TLS 1.2 with slow legacy ciphers.
# Under high load, SSL handshake may take >60s but <75s, triggering upstream timed out (110)
# while the TCP connection itself is still alive.
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix Nginx 'upstream timed out (110)' During SSL Handshake with Upstream API

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

TL;DR

  • What broke: Nginx initiated an SSL handshake to your upstream HTTPS backend but the backend didn't complete the handshake (or send response headers) within proxy_read_timeout. Errno 110 = ETIMEDOUT at the kernel socket layer — the connection was established but went silent.
  • How to fix it: Tune proxy_connect_timeout, proxy_read_timeout, enforce proxy_ssl_server_name on, verify your upstream TLS cert chain, and enable HTTP keepalives to the backend to eliminate per-request handshake overhead.
  • Fast path: Use our Client-Side Sandbox above to auto-refactor this — paste your Nginx location block and get a corrected config with all SSL proxy directives patched, without sending your config to any external server.

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

The Incident (What Does the Error Mean?)

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

2024/01/15 03:42:17 [error] 1234#1234: *58291 upstream timed out (110: Connection timed out)
while reading response header from upstream,
client: 10.0.1.45, server: api-gateway.internal,
request: "POST /v2/payments HTTP/1.1",
upstream: "https://api.example.com/v2/payments",
host: "api-gateway.internal"

Errno 110 is ETIMEDOUT — not a refused connection, not a DNS failure. The TCP socket to the upstream was opened, but Nginx hit its deadline waiting for the upstream to either complete the TLS handshake or send the first byte of the HTTP response header.

Immediate consequence: Nginx returns a 504 Gateway Timeout to the client. Every request hitting this upstream fails until the condition is resolved. In a payment or auth flow, this is a P0 incident.


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

The Attack Vector / Blast Radius

This isn't a security exploit in the traditional sense — but the blast radius is severe and the failure mode is insidious:

Cascading failure chain:

  1. Nginx worker processes accumulate stuck upstream connections, each holding a file descriptor open until proxy_read_timeout expires.
  2. Under load, this exhausts worker_connections. New client connections get 502 Bad Gateway or are silently dropped.
  3. If upstream is a microservice behind an internal load balancer, the timeout storm triggers health check failures, pulling the upstream out of rotation — which concentrates load on remaining nodes, triggering the same timeout on them.
  4. Connection pool starvation: Without keepalives configured, every proxied request burns a full TLS handshake (~200–400ms RTT overhead on a cold connection). At 500 RPS, you're doing 500 full TLS negotiations per second against the upstream. Any upstream CPU spike causes handshake queuing, which manifests as errno 110.

The security angle: If proxy_ssl_verify is off (common in misconfigured setups), you've already disabled certificate validation on the upstream leg. A network-level attacker on the path between Nginx and the upstream can intercept the connection. Fixing the timeout is the right time to also audit and enforce proxy_ssl_verify on.


How to Fix It (The Solution)

Basic Fix — Tune Timeouts and SSL Directives

 server {
     listen 443 ssl;
     server_name api-gateway.internal;
 
     location /v2/ {
         proxy_pass https://api.example.com;
 
-        # Missing or too-short timeouts
-        proxy_connect_timeout 5s;
-        proxy_read_timeout    10s;
-        proxy_send_timeout    10s;
+        proxy_connect_timeout 10s;
+        proxy_read_timeout    60s;
+        proxy_send_timeout    60s;
 
-        # SSL handshake to upstream will fail SNI without this
-        # proxy_ssl_server_name not set
+        proxy_ssl_server_name on;
+        proxy_ssl_protocols   TLSv1.2 TLSv1.3;
+        proxy_ssl_ciphers     HIGH:!aNULL:!MD5;
 
-        # Certificate verification disabled — MITM risk
-        proxy_ssl_verify off;
+        proxy_ssl_verify        on;
+        proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
     }
 }

Why proxy_ssl_server_name on matters: Without it, Nginx sends no SNI extension in the ClientHello. Modern backends and CDNs (Cloudflare, AWS ALB, GCP LB) require SNI to select the correct certificate. Without SNI, the handshake either fails outright or selects a default cert that triggers verification failure — both manifest as errno 110.


Enterprise Best Practice — Upstream Keepalives + Connection Pooling

The real fix at scale is eliminating per-request TLS handshakes entirely using upstream keepalive pools.

+# Define upstream block with keepalive pool
+upstream api_backend {
+    server api.example.com:443;
+
+    # Maintain up to 32 idle keepalive connections per worker
+    keepalive 32;
+    keepalive_requests 1000;
+    keepalive_timeout  65s;
+}
 
 server {
     listen 443 ssl;
 
     location /v2/ {
-        proxy_pass https://api.example.com;
+        proxy_pass https://api_backend;
 
+        # Required for keepalive to upstream to function
+        proxy_http_version 1.1;
+        proxy_set_header   Connection "";
 
         proxy_connect_timeout 10s;
         proxy_read_timeout    60s;
         proxy_send_timeout    60s;
 
         proxy_ssl_server_name on;
         proxy_ssl_verify      on;
         proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
+        proxy_ssl_session_reuse on;
     }
 }

proxy_ssl_session_reuse on instructs Nginx to reuse TLS session tickets for keepalive connections, dropping handshake overhead from ~300ms to ~1ms on reconnect.

proxy_http_version 1.1 + Connection "" — without these two lines, Nginx defaults to HTTP/1.0 upstream, which forces Connection: close on every request, destroying your keepalive pool silently.


💡 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

1. Lint Nginx configs in your pipeline before deploy:

# In your CI step — catches missing proxy_ssl_server_name, verify directives
nginx -t -c /path/to/nginx.conf

# gixy: static security analyzer for Nginx
pip install gixy
gixy /etc/nginx/nginx.conf
# Catches: proxy_ssl_verify off, SSRF risks, missing timeouts

2. Checkov IaC scan if Nginx is deployed via Terraform/Helm:

checkov -d ./helm/nginx --framework helm \
  --check CKV_NGINX_1  # TLS verification enabled

3. Synthetic monitoring with timeout assertions:

# Datadog Synthetic or k6 — assert p99 upstream response < 5s
# If p99 creeps toward your proxy_read_timeout, page before errno 110 fires
thresholds:
  http_req_duration: ['p(99)<5000']

4. OPA/Conftest policy to block deploys with proxy_ssl_verify off:

package nginx.ssl

deny[msg] {
    input.proxy_ssl_verify == "off"
    msg := "proxy_ssl_verify must be 'on' — upstream TLS verification is mandatory"
}

5. Track the actual TLS handshake time separately using $upstream_connect_time in your Nginx access log format. If this value spikes, you have a backend TLS performance problem — fix it at the source rather than papering over it with longer timeouts.

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.What is the difference between proxy_connect_timeout and proxy_read_timeout in the context of errno 110?
The proxy_connect_timeout controls the window for establishing the TCP connection and completing the TLS handshake to your upstream. The proxy_read_timeout governs how long Nginx waits for the first byte of the response header after the connection is live. An errno 110 on 'reading response header' typically indicts proxy_read_timeout, but a sluggish TLS handshake—often from backend overload or SNI misconfiguration—can trigger proxy_connect_timeout first. You can distinguish them by inspecting $upstream_connect_time in your logs. This precise root-cause analysis is critical; misdiagnosis here leads to misconfigured timeouts that either mask latency or cause premature failures.
Q2.Why does proxy_ssl_server_name off cause upstream timeouts instead of an immediate SSL error?
When SNI is disabled, multi-tenant upstream platforms (like AWS ALB or Cloudflare) don't receive the hostname needed to select the correct certificate. Instead of an immediate TLS alert, they often stall the handshake, waiting indefinitely for valid SNI data. This stall manifests as a timeout (errno 110) rather than a clean SSL failure. Manually toggling this setting is error-prone; a single misconfigured deployment can silently degrade performance across your fleet. Instead of risking this blast radius, drop the broken Nginx manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that enforces correct SNI configuration under Maker-Checker review.
Q3.Will increasing proxy_read_timeout actually fix the root cause, or just mask it?
Increasing the timeout is a tactical mitigation that stops immediate outages but does not address the underlying flaw. The real culprits are: missing SNI causing handshake stalls, lack of keepalive pools forcing full TLS renegotiation per request, or genuine upstream latency. While you can instrument $upstream_connect_time and $upstream_response_time to pinpoint the phase, manually applying these fixes across large mono-repos or at scale becomes a compliance and consistency nightmare. For reliable, automated PR drafting under Maker-Checker review integrated into your CI/CD pipeline, upgrade to the StackEngine CI/CD Automator (Team Edition). It provides on-prem analysis and enforces secure configurations enterprise-wide, turning ad-hoc fixes into governed policy.