Initializing Enclave...

How to Fix Nginx 'Upstream Response Buffered to a Temporary File' Warning

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

TL;DR

  • What broke: Nginx cannot hold the upstream response in memory. It's spilling to a temp file on disk, serializing request handling and tanking throughput under any real load.
  • How to fix it: Increase proxy_buffer_size, proxy_buffers, and proxy_busy_buffers_size in your location or http block to match your actual upstream response sizes.
  • Fast path: Use our Client-Side Sandbox above to paste your nginx.conf and auto-generate tuned buffer directives without leaking your config to a third-party AI.

The Incident (What Does the Error Mean?)

You'll see this in /var/log/nginx/error.log:

[warn] 1234#1234: *567 an upstream response is buffered to a temporary file /var/cache/nginx/proxy_temp/1/02/0000000021 while reading upstream

Nginx's proxy buffering pipeline works like this: when a response arrives from upstream, Nginx tries to hold it in memory using the configured buffer pool. The moment that pool is exhausted, Nginx opens a temp file under proxy_temp_path and starts writing to disk. Every request hitting this code path now pays a synchronous disk write penalty. On NVMe this is painful. On EBS gp2 or a shared NFS mount, this is a production incident.

The default values that cause this:

  • proxy_buffer_size: 4k (holds response headers only)
  • proxy_buffers: 8 4k (32k total body buffer per connection)
  • proxy_busy_buffers_size: 8k

Any upstream returning a response body larger than ~28-32k will immediately overflow to disk.


The Attack Vector / Blast Radius

This is a cascading throughput degradation, not a hard crash, which makes it worse — it degrades silently.

The failure chain:

  1. Upstream returns a 150k JSON payload (common for any list API or GraphQL response).
  2. Nginx exhausts its 32k memory buffer pool in milliseconds.
  3. Nginx opens a file descriptor to proxy_temp, writes the overflow, then reads it back to send to the client.
  4. Under concurrent load, you now have N open file descriptors + N disk write ops per request cycle.
  5. proxy_temp partition fills up → Nginx starts returning 502/504 to all clients, not just the large-response endpoints.
  6. Worker processes block on disk I/O → entire Nginx instance degrades, not just the affected location block.

Blast radius is instance-wide. A single chatty endpoint with large responses can starve all other virtual hosts on the same Nginx process.


How to Fix It

Basic Fix

Increase buffer sizes to accommodate your p95 upstream response size. If your API responses average 200-500k, target at least that in your buffer pool.

http {
-    proxy_buffer_size   4k;
-    proxy_buffers       8 4k;
-    proxy_busy_buffers_size 8k;
+    proxy_buffer_size   16k;
+    proxy_buffers       8 32k;
+    proxy_busy_buffers_size 64k;
}

Reload (zero-downtime): nginx -t && nginx -s reload

Enterprise Best Practice

Do not set large buffers globally in the http block — you'll balloon memory for every upstream, including static file servers and health check endpoints. Scope buffer overrides to the specific location or server block serving large responses. Also set proxy_max_temp_file_size 0 on critical paths to hard-fail rather than silently degrade to disk.

http {
    # Conservative global defaults — leave these small
    proxy_buffer_size        4k;
    proxy_buffers            8 4k;
    proxy_busy_buffers_size  8k;

    server {
        listen 443 ssl;
        server_name api.example.com;

        location /v1/data/ {
-           # No buffer overrides — inheriting broken global defaults
+           proxy_buffer_size        16k;
+           proxy_buffers            16 32k;
+           proxy_busy_buffers_size  64k;
+           # Hard-fail instead of silently spilling to disk
+           proxy_max_temp_file_size 0;
+           # Tune timeouts to match large response upstream SLA
+           proxy_read_timeout       120s;
            proxy_pass http://backend_upstream;
        }

        location /healthz {
            # Small responses — global defaults are fine here
            proxy_pass http://backend_upstream;
        }
    }
}

Memory math: proxy_buffers 16 32k = 512k per active connection. At 1000 concurrent connections to this location, that's 512MB reserved. Size accordingly against your worker process worker_rlimit_nofile and available RAM. A reasonable formula: (expected_concurrent_connections × proxy_buffers_total_size) < 60% of available RAM.

💡 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

1. Nginx config linting in your pipeline:

Use gixy — Nginx static analyzer from Yandex — to catch buffer misconfigurations before deploy:

# In your CI pipeline (GitHub Actions, GitLab CI, etc.)
pip install gixy
gixy /etc/nginx/nginx.conf

Gixy has a dedicated proxy_buffering rule that flags undersized buffer configurations.

2. Checkov for IaC-managed Nginx (Kubernetes Ingress):

If you're using the NGINX Ingress Controller on Kubernetes, enforce buffer annotations via OPA/Gatekeeper:

# OPA ConstraintTemplate snippet
violation[{"msg": msg}] {
  annotation := input.review.object.metadata.annotations
  not annotation["nginx.ingress.kubernetes.io/proxy-buffer-size"]
  msg := "Ingress must explicitly declare proxy-buffer-size annotation"
}

3. Observability — catch it before users do:

Add a log format that surfaces buffer overflows in your metrics pipeline:

log_format upstream_debug '$remote_addr - [$time_local] "$request" '
                          '$status $body_bytes_sent '
                          'upstream_response_length=$upstream_response_length '
                          'upstream_cache_status=$upstream_cache_status';

Alert on upstream_response_length > proxy_buffers_total_size using your log aggregator (Loki, Datadog, CloudWatch Logs Insights). By the time you see the buffered to a temporary file warn in logs at volume, you're already in a degraded state.

Related Diagnostics

"Part of the Performance Utility Matrix."

View all 219 Performance Tools →