502 solo con la sesion iniciada: upstream sent too big header
PHP registraba 200 y el navegador veia 502. El culpable estaba en medio, eran dos nginx y no uno, y cada capa tiene su propia familia de directivas.
Creé un usuario de demostración para que la gente pudiera recorrer mi plataforma. Entré a probarlo y la pantalla principal me dio 502 Bad Gateway.
Lo raro: sin sesión iniciada, esa misma URL redirigía al login perfectamente. El error solo aparecía después de entrar. Y en el log de PHP el requestfiguraba como 200.
PHP dice 200, el navegador ve 502
Ese desacuerdo es la pista. Si PHP respondió bien y el cliente recibió un 502, el problema no está en la aplicación: está en alguien de en medio. El log de nginx lo dijo con todas sus letras:
upstream sent too big header while reading response header from upstream
Qué significa
nginx reserva un búfer de tamaño fijo para leer las cabeceras de la respuesta del upstream. Por omisión son 4 u 8 KB. Si las cabeceras no caben, nginx no las trunca ni avisa: aborta y devuelve 502.
La clave está en la palabra cabeceras. El cuerpo de la respuesta puede pesar megabytes sin problema — eso se transmite en pedazos. Lo que no puede crecer es el bloque de cabeceras.
Y ahí encaja por qué solo pasaba con sesión: un usuario autenticado arrastra cookies de sesión, cookies de XSRF y, según la aplicación, cabeceras extra que un anónimo no tiene. Anónimo cabía en 4 KB; autenticado, no.
La trampa: eran dos nginx, no uno
Subí los búferes en el nginx que tenía enfrente, recargué, probé. Seguía en 502.
Se me había olvidado mi propia arquitectura. Una petición atraviesa dos:
navegador
-> nginx del host (reverse proxy, TLS) <- directivas proxy_*
-> nginx del contenedor (sirve /public) <- directivas fastcgi_*
-> php-fpm
Y cada capa tiene su propia familia de directivas. El nginx que habla con php-fpm usa fastcgi_buffer_size. El que hace de proxy usa proxy_buffer_size. Arreglar uno no arregla el otro, y el mensaje de error es idéntico en ambos, así que es fácil creer que ya quedó.
El arreglo
En el nginx que habla con php-fpm:
location ~ \.php$ {
fastcgi_pass app:9000;
include fastcgi_params;
fastcgi_buffer_size 32k;
fastcgi_buffers 16 32k;
fastcgi_busy_buffers_size 64k;
}
Y en el reverse proxy. Aquí hice algo que recomiendo: en vez de editar el vhost del sitio que falló, creé un archivo nuevo en conf.d/ con las directivas sueltas:
# /etc/nginx/conf.d/00-proxy-buffers.conf
proxy_buffer_size 32k;
proxy_buffers 16 32k;
proxy_busy_buffers_size 64k;
Los archivos de conf.d/ se incluyen dentro del bloque http, así que unas directivas a nivel raíz del archivo aplican a todos los sitios y las heredan los server que ya existen y los que crees mañana. Arreglé un sitio y vacuné los otros nueve.
sudo nginx -t && sudo systemctl reload nginx
Cómo diagnosticarlo la próxima vez
Si ves un 502 que aparece y desaparece según quién sea el usuario:
- Compara los logs. Si el de la aplicación dice 200 y el cliente ve 502, el culpable está en medio.
- Lee el error de nginx, no solo el access log. El mensaje es explícito; el 502 no dice nada.
- Cuenta las capas. ¿Cuántos nginx hay entre el navegador y tu código? Cada uno necesita su ajuste, y con su propio prefijo de directiva.
- Prueba autenticado y anónimo. Si solo falla con sesión, sospecha del tamaño de las cabeceras antes que de la lógica.
Lo que más me costó no fue el arreglo, que son tres líneas. Fue acordarme de que mi petición pasaba por dos servidores web y no por uno.