751 words, 4 min read

You've built a signed redirect endpoint. It validates a token, generates a URL, and sends the browser on its way with a 302. It works perfectly in local development but fails with a cryptic 502 Bad Gateway in staging. Here's how to diagnose and fix it without touching your web server config.

The symptom

A curl -v request to the endpoint returns nginx's own 502 page—not a Laravel or application error page, just nginx's default 150-byte HTML. That detail matters: it means PHP-FPM never delivered a response nginx could forward. The application may have produced something, but nginx rejected it before the browser ever saw it.

Finding the root cause

The nginx error log is the first place to look:

upstream sent too big header while reading response header from upstream

That single line explains everything. nginx has a default buffer for reading response headers from an upstream FastCGI process (fastcgi_buffer_size, which defaults to 4 KB). The 302 response your application returned was otherwise valid, but its Location header was too long to fit in that buffer. nginx dropped the whole response and returned a 502.

This happens frequently with e-signature providers, OAuth flows, and any service that encodes its entire state (session, return URL, signature, expiry) in a redirect URL. Those URLs routinely run to several kilobytes.

Why it only fails in staging

Local development typically uses the built-in PHP server or a framework dev server, neither of which imposes nginx's header buffer limits. The problem is invisible until nginx sits between the application and the browser—which is the case in every hosted environment but rarely in local development.

The obvious fix and why to think twice

The textbook answer is to raise nginx's buffer size:

fastcgi_buffer_size 32k;
fastcgi_buffers 16 32k;

This works, but it requires access to the server config and a reload. It also papers over the underlying issue: you're transmitting a very large URL in an HTTP header, which is inherently fragile. Different proxies, CDNs, and load balancers have their own header size limits, and you may hit another one later.

A better fix: move the URL out of the header

HTTP redirects put the destination in a Location header. If instead you put the URL in the response body, header buffer limits are irrelevant—response bodies are streamed, not buffered in header-size chunks.

The simplest implementation is a minimal HTML page with a <meta> refresh and a JavaScript fallback:

$url = $this->buildSigningUrl($signer);

return response(
    '<html><head><meta http-equiv="refresh" content="0;url=' . e($url) . '"></head>' .
    '<body><script>window.location.replace(' . json_encode($url) . ')</script></body></html>'
);

The browser executes the redirect client-side in under a frame. From the user's perspective, it's indistinguishable from a 302. From nginx's perspective, it's a normal 200 response with a small body—no oversized headers and no 502.

Why not use a URL shortener or cache indirection?

An alternative is to store the long URL behind a short token and redirect the browser to a local endpoint that looks it up:

/esign/{uuid}?redirect=1  →  /esign/forward/{token}  →  {long signing URL}

The first redirect is short and fits in nginx's buffer. But the second redirect—from your forward endpoint to the signing URL—still goes through PHP-FPM and nginx, still puts the long URL in a Location header, and still hits the same limit. You've added a round trip without solving anything.

The only indirection that works is one where the browser makes the final hop to the long URL, not your server—which is exactly what the HTML body approach does.

The diagnostic checklist

If you hit a 502 on a redirect endpoint and can't reproduce it locally:

  1. curl -v the URL—check whether the response is an application error page or nginx's own 502 page. Nginx's own page means the problem is between nginx and upstream, not inside your application.
  2. Check /var/log/nginx/error.log around the timestamp. upstream sent too big header points to buffer limits; connect() failed or upstream timed out point elsewhere.
  3. Check PHP-FPM's log for crashes or OOM kills if the nginx log is silent.
  4. Measure the URL you're redirecting to. If it's over 3–4 KB, the buffer is likely the culprit.

The pattern generalizes beyond PHP. Any application server behind nginx—Python/WSGI, Node, or Go—can trigger the same error with a large redirect. The fix is the same: put the long URL in the body, not a header.