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, andproxy_busy_buffers_sizein yourlocationorhttpblock to match your actual upstream response sizes. - Fast path: Use our Client-Side Sandbox above to paste your
nginx.confand 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:
- Upstream returns a 150k JSON payload (common for any list API or GraphQL response).
- Nginx exhausts its 32k memory buffer pool in milliseconds.
- Nginx opens a file descriptor to
proxy_temp, writes the overflow, then reads it back to send to the client. - Under concurrent load, you now have N open file descriptors + N disk write ops per request cycle.
proxy_temppartition fills up → Nginx starts returning 502/504 to all clients, not just the large-response endpoints.- Worker processes block on disk I/O → entire Nginx instance degrades, not just the affected
locationblock.
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.