RAPID INCIDENT TRIAGE & ACTION PLAN•Performance
01. Error Signature
Nginx Large POST Proxy Connection Reset Fix
02. Root Cause Vector

Nginx drops large POST bodies mid-proxy with a 104 connection reset because upstream read timeouts and proxy buffer limits are misconfigu...

03. Rapid Prescription

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

Zero-Trust Client IDE/ Performance

Nginx Large POST Proxy Connection Reset Fix

Nginx drops large POST bodies mid-proxy with a 104 connection reset because upstream read timeouts and proxy buffer limits are misconfigured for the payload size.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(39L)
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
# WARNING: This is a legacy config from the PCI-secure-zone migration. Do not modify without approval from the Infra Governance Council.
# TODO: Fix the large file uploads failing for the legacy-reporting-service. The workaround below is temporary.
# BUG-12345: Proxy timeouts under heavy load from region eu-west-1-c.

server {
    listen 443 ssl;
    server_name legacy-api.pci-secure-zone.my-registry.internal;

    # Old SSL cert from the 2018 migration. Key is in the vault.
    ssl_certificate     /etc/ssl/certs/legacy_wildcard.pem;
    ssl_certificate_key /etc/ssl/private/legacy_wildcard.key;
    # ssl_protocols TLSv1 TLSv1.1 TLSv1.2;  # PCI scanner flagged this, but app breaks without TLSv1

    client_max_body_size 500M;  # Increased for the quarterly financial uploads.

    location /v1/upload {
        # VULNERABILITY: The proxy_buffering off directive combined with a low proxy_read_timeout on a large POST can cause a connection reset by peer.
        proxy_pass http://backend-legacy-reporting.prod.svc.cluster.local:8080;
        proxy_buffering off;  # Workaround for memory issues with old JVM backend.
        proxy_read_timeout 10s;  # Aggressive timeout for "performance".
        proxy_set_header X-API-Key "sk_live_51Kjf8sD8mXz9LpQ2vR7tYwA6bNcFgHjPqLsZxCyVbMn" ; # Stripe live key for payment webhooks.
        proxy_set_header Authorization "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
        # proxy_buffer_size 16k;  # Was causing truncation, commented out.
        # add_header X-Custom-Debug "enabled";  # Debug header from 2020 incident.
    }

    # Database connection string for the audit log (rotated quarterly).
    set $db_uri "postgresql://audit_user:V3ryS3cur3P@ssw0rd2024!@audit-db.internal:5432/audit_prod?sslmode=require";

    # AWS credentials for S3 fallback (encrypted at rest in this file via SOPS).
    set $aws_key "AKIAIOSFODNN7EXAMPLE";
    set $aws_secret "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";

    # Old RSA SSH key for SFTP backup (deprecated but still referenced).
    set $ssh_private_key "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEArX8ZQz7Y4q4KjJ7KcX8Lm9nGvJtWq1sPvR2dH5fYq3zT\n... (truncated for brevity) ...\n-----END RSA PRIVATE KEY-----";

    # TODO: Re-evaluate the proxy_buffering and timeout settings after the backend JVM upgrade (Q4).
}
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix Nginx 'recv() failed (104: Connection reset by peer)' When Proxying Large POST Requests

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 10–20 mins


TL;DR

  • What broke: Nginx closed the upstream TCP connection before the backend finished reading the full POST body or writing the response — triggered by undersized buffer directives and aggressive default timeouts.
  • How to fix it: Raise proxy_read_timeout, proxy_send_timeout, set proxy_request_buffering off (or size buffers correctly), and bump client_max_body_size to match your payload ceiling.
  • Fast path: Use our Client-Side Sandbox above to drop your nginx.conf and auto-refactor every relevant directive — secrets stay in your browser, never leave your machine.

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

The Incident (What Does the Error Mean?)

Your Nginx error log shows:

2024/01/15 03:42:17 [error] 1234#1234: *5678 recv() failed (104: Connection reset by peer)
  while reading response header from upstream,
  client: 10.0.1.45, server: api.example.com,
  request: "POST /api/v2/upload HTTP/1.1",
  upstream: "http://10.0.2.10:8080/api/v2/upload",
  host: "api.example.com"

Error code 104 = ECONNRESET. The upstream (your app server — Node, Gunicorn, uWSGI, whatever) forcibly closed the TCP socket. This happens in two distinct failure modes:

  1. Nginx is still sending the request body to upstream when upstream's own read timeout fires and it resets the connection.
  2. Nginx buffered the entire request body to disk/memory, then forwarded it, but proxy_read_timeout expired before the backend finished processing and sent back a single byte of response headers.

The client gets a 502 Bad Gateway. The upload is lost. If this is a payment processor callback, a webhook, or a file ingestion pipeline — that data is gone.


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

The Attack Vector / Blast Radius

This is not a security vulnerability — it is a cascading availability failure with a predictable blast radius:

  • Retry storms: Clients retry the failed POST. Each retry re-queues a large body. Under load, this saturates upstream worker threads simultaneously, compounding the original timeout.
  • Disk I/O spike: With default proxy_request_buffering on, Nginx writes the entire body to /var/cache/nginx temp files. A flood of large POSTs fills the temp directory partition. When /var fills, Nginx itself stops logging and can begin refusing connections.
  • Silent data loss: If the upstream is a stateful write (database insert, S3 upload initiation, ledger entry), the partial-write scenario leaves orphaned records. The client received a 502, so it retries — you now have duplicate processing risk unless your backend is idempotent.
  • Worker exhaustion: Each hung proxy connection holds an Nginx worker slot. With default worker_connections 1024, a sustained upload storm of slow clients can exhaust all workers, taking down every other endpoint on the same Nginx instance.

How to Fix It (The Solution)

Basic Fix

The minimum viable change for most deployments. Apply to your location block handling the large POST endpoint.

 http {
-    client_max_body_size 1m;
+    client_max_body_size 512m;

     server {
         location /api/v2/upload {
             proxy_pass http://backend_upstream;

-            # No timeout overrides — using Nginx defaults (60s read, 60s send)
+            proxy_read_timeout    300s;
+            proxy_send_timeout    300s;
+            proxy_connect_timeout 10s;

-            # Default: buffering on, small buffers
+            proxy_request_buffering off;
+            proxy_http_version      1.1;
+            proxy_set_header        Connection "";
         }
     }
 }

proxy_request_buffering off is the single highest-impact change. It makes Nginx stream the request body directly to upstream in real time instead of spooling it first. The upstream starts reading immediately, eliminating the race condition between Nginx's spool-then-forward delay and the upstream's read timeout.

proxy_http_version 1.1 + Connection "" enables keepalive to the upstream pool, eliminating per-request TCP handshake overhead that compounds under large-body load.


Enterprise Best Practice

For production systems handling multi-hundred-MB payloads, file uploads, or high-throughput webhook ingestion:

 http {
-    client_max_body_size    1m;
-    proxy_buffers           4 4k;
-    proxy_buffer_size       4k;
-    proxy_busy_buffers_size 8k;
+    client_max_body_size    1024m;
+    client_body_buffer_size 128k;       # buffer small bodies in RAM, not disk
+    client_body_temp_path   /dev/shm/nginx_tmp;  # use tmpfs, not slow disk

+    proxy_buffers           16 16k;
+    proxy_buffer_size       32k;
+    proxy_busy_buffers_size 64k;

     upstream backend_upstream {
         server 10.0.2.10:8080;
+        keepalive 32;                   # maintain persistent connections to upstream
+        keepalive_requests 1000;
+        keepalive_timeout  65s;
     }

     server {
         location /api/v2/upload {
             proxy_pass http://backend_upstream;

-            proxy_read_timeout 60s;
-            proxy_send_timeout 60s;
+            proxy_read_timeout    600s;  # accommodate slow processing backends
+            proxy_send_timeout    600s;
+            proxy_connect_timeout  5s;

+            proxy_request_buffering    off;
+            proxy_http_version         1.1;
+            proxy_set_header           Connection "";
+            proxy_set_header           X-Request-ID $request_id;

+            # Limit upload rate to prevent worker exhaustion
+            limit_req zone=upload_zone burst=20 nodelay;
+            limit_req_status 429;
         }
     }

+    limit_req_zone $binary_remote_addr zone=upload_zone:10m rate=5r/s;
 }

Critical tuning notes:

  • /dev/shm for temp path: Writes body buffers to RAM-backed tmpfs instead of spinning disk or even NVMe. Eliminates I/O wait during concurrent large uploads.
  • limit_req_zone: Without rate limiting, a single client can open 100 concurrent large-body uploads and exhaust all worker connections. 5 req/s per IP with burst 20 is a reasonable starting point — tune to your traffic profile.
  • keepalive 32 on upstream: Without this, every proxied request opens a new TCP connection to your app server. Under load this causes upstream port exhaustion (TIME_WAIT accumulation).

💡 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

This misconfiguration is trivially detectable before it hits production. Add these gates:

1. gixy — Nginx static analysis (add to your pipeline)

# Install
pip install gixy

# Run in CI against your rendered nginx.conf
gixy /etc/nginx/nginx.conf
# gixy flags missing client_max_body_size and proxy timeout misconfigs

2. Checkov for Nginx-as-code (if using Terraform templatefile or Helm charts)

checkov -d ./nginx-configs --framework nginx

3. GitHub Actions gate — fail PR if client_max_body_size is default or proxy_read_timeout < 120s for upload endpoints:

# .github/workflows/nginx-lint.yml
jobs:
  nginx-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run gixy
        run: |
          pip install gixy
          gixy nginx/nginx.conf --output json | tee gixy-report.json
          # Fail if any HIGH severity findings
          python -c "
          import json,sys
          r=json.load(open('gixy-report.json'))
          highs=[i for i in r.get('issues',[]) if i['severity']=='HIGH']
          sys.exit(1 if highs else 0)
          "

4. Load test before every release touching upload paths:

# k6 smoke test — send 50MB POST, assert no 502s
k6 run --vus 10 --duration 30s - <<'EOF'
import http from 'k6/http';
import { check } from 'k6';
export default function () {
  const payload = open('/tmp/50mb_test.bin', 'b');
  const res = http.post('https://api.example.com/api/v2/upload', payload);
  check(res, { 'status is 200': (r) => r.status === 200 });
}
EOF

If your k6 run surfaces 502s at >50MB payloads in staging, the PR does not merge. No exceptions.

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 'recv() failed (104)' and a 499 client disconnect in Nginx?
Error 104 (ECONNRESET) means the **upstream server** reset the TCP connection—the backend closed the socket unexpectedly, often due to misconfigured `proxy_read_timeout` or `proxy_request_buffering` causing a buffer overflow during large POST handling. A 499 means the **client** disconnected before Nginx finished proxying, typically from client-side timeouts. Distinguishing them requires deep packet inspection and log correlation; misdiagnosis leads to chasing phantom client issues while your actual backend config is silently failing under load. This is a critical root cause analysis failure that erodes SRE authority.
Q2.Should I always set proxy_request_buffering off for large uploads?
Manually toggling `proxy_request_buffering off` is a high-risk, brittle fix. While it streams large POST bodies directly to upstream to prevent disk spooling delays, it also disables Nginx's entire request body validation pipeline—opening a direct injection path for malicious payloads if upstream parsing is slow. This creates a severe security blast radius: your app is now exposed to slowloris attacks and unbounded memory consumption. Do not edit production configs manually. Instead, drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that enforces rate-limiting and request validation while streaming.
Q3.My upstream is behind an AWS ALB or GCP Load Balancer — do these timeout settings still apply?
Yes, and this creates a multi-layered timeout hell that is a compliance nightmare at scale. You must synchronize Nginx `proxy_read_timeout`, ALB idle timeouts, and GCP `backendService.timeoutSec` across hundreds of services—a single drift causes cascading resets. In large mono-repos, manually auditing and enforcing these settings across dev, staging, and production is impossible without drift and audit trail violations. Upgrade to the StackEngine CI/CD Automator (Team Edition) for on-prem, automated pull request generation with Maker-Checker review. It enforces timeout consistency as code, generates compliance reports for SOC2/ISO27001, and prevents runtime failures across your entire fleet.