Fix Nginx "upstream sent too big header" with Django
Fix 502 errors caused by Nginx 'upstream sent too big header': measure Django's response headers, raise proxy_buffer_size safely, and shrink oversized cookies, sessions, and headers.
Problem statement
Some requests fail with 502 Bad Gateway, typically right after login, after adding items to a cart, or on pages that set several cookies, while other pages work. Gunicorn logs a normal 200 or 302 for the same request. The Nginx error log shows:
upstream sent too big header while reading response header from upstream,
client: 198.51.100.7, server: example.com, request: "POST /accounts/login/ HTTP/1.1",
upstream: "http://unix:/run/gunicorn/gunicorn.sock:/accounts/login/"
Nginx reads the upstream's response status line and headers into a single buffer sized by proxy_buffer_size (4 KB or 8 KB by default, depending on the platform). When Django's response headers exceed it, Nginx gives up and returns 502. The body size does not matter; only headers count.
- Confirm the message in
/var/log/nginx/error.log. - Measure the response header size by requesting the same URL from Gunicorn directly.
- Raise the header buffer in the proxied location:
proxy_buffer_size 16k; proxy_buffers 8 16k; proxy_busy_buffers_size 32k;. - Find what makes the headers large: cookie-based sessions or messages, many cookies, large
Content-Security-PolicyorLinkheaders. - Shrink the cause: database or cache sessions, session-based messages, fewer and smaller cookies.
- Reload Nginx with
nginx -t && systemctl reload nginxand retest.
Step-by-step solution
1. Confirm the error
sudo grep "too big header" /var/log/nginx/error.log | tail -5
If the log shows a different upstream error (connect() failed, upstream prematurely closed connection), this is a different problem.
2. Measure the headers
Request the failing URL from Gunicorn directly, bypassing Nginx, with the same cookies the browser sends:
curl -s -o /dev/null -D /tmp/headers.txt \
--unix-socket /run/gunicorn/gunicorn.sock \
-H "Host: example.com" -H "X-Forwarded-Proto: https" \
-H "Cookie: sessionid=...; csrftoken=..." \
http://localhost/accounts/login/
wc -c /tmp/headers.txt
sort -t: -k1,1 /tmp/headers.txt | awk -F: '{print length($0), $1}' | sort -n | tail
This shows the total header size and which headers are largest. Anything above 4 KB can trigger the error with defaults; Set-Cookie and Content-Security-Policy are the usual suspects.
3. Raise the buffer
location / {
proxy_pass http://unix:/run/gunicorn/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
}
proxy_buffer_sizeholds the status line and all response headers.proxy_busy_buffers_sizemust be at least as large asproxy_buffer_sizeand smaller than the total ofproxy_buffersminus one buffer; otherwisenginx -tfails with an error naming these directives.- These buffers are per connection, so very large values multiply by concurrent connections. 16 KB to 32 KB covers almost every legitimate application.
For uWSGI or FastCGI upstreams, the equivalents are uwsgi_buffer_size / uwsgi_buffers and fastcgi_buffer_size / fastcgi_buffers.
sudo nginx -t && sudo systemctl reload nginx
4. Find and fix the cause in Django
A bigger buffer stops the 502s; shrinking the headers fixes the underlying problem, because browsers also limit cookie sizes (about 4 KB per cookie) and send every cookie back on each request.
Cookie-based sessions. With SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies", all session data travels in a cookie. Anything stored in the session grows it. Switch to server-side sessions:
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
Messages framework. The default FallbackStorage stores messages in a cookie first. Many or long messages (for example a list of import errors) make a large cookie. Store them in the session instead:
MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"
Many cookies. Analytics and A/B testing tools set cookies on your domain; the response may set several at once. Remove unused ones or scope them to specific paths.
Large security headers. A Content-Security-Policy listing many hosts, or Link headers for dozens of preloads, add kilobytes to every response. Consolidate sources, use 'self' and nonces instead of long host lists, and preload only critical assets.
5. Watch for the request-side equivalent
Large cookies also make requests big. When the browser sends them back, Nginx may reject the request with 400 Bad Request: Request Header Or Cookie Too Large. That limit is large_client_header_buffers (default 4 8k). Raising it treats the symptom; reducing cookie size fixes both directions.
Verification check:
curl -s -o /dev/null -w "%{http_code}\n" -H "Cookie: sessionid=..." https://example.com/accounts/login/
sudo tail -f /var/log/nginx/error.log
Repeat the action that failed (log in, add to cart) in a browser. It should succeed and the error log should stay quiet. Re-measure the headers after the Django changes; ideally they are well below 4 KB again.
Explanation
Nginx proxies responses in two phases: first it reads the status line and headers into one buffer, then it streams the body using other buffers. The header buffer is small by default because most applications send only a few hundred bytes of headers. Django applications exceed it when state that belongs on the server (sessions, messages) is kept in cookies, or when security and performance headers grow over time. The 502 is Nginx refusing to forward a response it cannot parse within its limits.
Edge cases / notes
Behind a load balancer or CDN
Load balancers and CDNs have their own header size limits, often around 8 KB to 32 KB, and may return their own error pages. Fixing header size in Django helps with all of them.
Only some users affected
Users with more session data (admins, users with many items in a basket) hit the limit first. That pattern points at session or message cookies.
When to automate this
Add a test that logs in a user, performs the actions that store the most session data, and asserts the response headers stay below a budget (for example 4 KB). Include sensible proxy_buffer_size values in your Nginx configuration template for every site.
Next steps
- For other causes of 502 responses, see Django 502 Bad Gateway: Causes and Fixes.
- Review proxy settings in How to Configure Nginx for Django in Production.
- Move session state server-side with Configure Django Sessions for Production.
- If logins do not stick after the fix, see Fix Django Login Not Persisting Behind HTTPS.
FAQ
What does "upstream sent too big header" mean in Nginx?
The application's response headers were larger than proxy_buffer_size, so Nginx could not read them and returned 502.
What proxy_buffer_size should I use for Django?
16 KB is enough for most applications. If you need much more, reduce the headers instead of raising buffers further.
Why do only logged-in users get 502 errors?
Their responses set larger cookies, usually from cookie-based sessions or messages. Store that state on the server.
Is this the same as "400 Request Header Or Cookie Too Large"?
No. That error concerns the request the browser sends, controlled by large_client_header_buffers. Both are usually caused by oversized cookies.