Snippets de nginx que siempre acabo necesitando
Siete bloques que copio de un proyecto a otro: los buferes del 502, las subidas grandes, el webhook con GET y POST en la misma URL, WebSockets y las cabeceras de seguridad.
Estos son los bloques de nginx que acabo copiando de un proyecto a otro. Todos salieron de un problema real en producción; ninguno estaba en la configuración inicial.
1. El 502 que solo aparece con sesión iniciada
Síntoma: la aplicación registra 200, el navegador ve 502, y anónimo funciona. En el log de nginx: upstream sent too big header.
Los búferes de cabeceras vienen en 4 u 8 KB. Un usuario autenticado arrastra cookies que no caben.
# En el nginx que habla con php-fpm
fastcgi_buffer_size 32k;
fastcgi_buffers 16 32k;
fastcgi_busy_buffers_size 64k;
# En el reverse proxy. Como archivo suelto en conf.d/, sin bloque server,
# aplica a TODOS los sitios y los vacuna de una vez.
# /etc/nginx/conf.d/00-proxy-buffers.conf
proxy_buffer_size 32k;
proxy_buffers 16 32k;
proxy_busy_buffers_size 64k;
Si tienes dos capas de nginx, necesitas las dos. El mensaje de error es idéntico y es fácil creer que ya quedó.
2. Subidas grandes que se cortan en 1 MB
Para almacenamiento tipo S3, o cualquier endpoint que reciba archivos pesados:
location / {
proxy_pass http://127.0.0.1:9000;
client_max_body_size 0; # 0 = sin limite
proxy_request_buffering off; # transmite mientras recibe
proxy_buffering off;
chunked_transfer_encoding off;
}
Sin esto nginx acumula el cuerpo entero en disco antes de pasarlo: lento con archivos grandes y, pasado el límite por omisión, un 413 seco.
3. Un webhook que necesita GET y POST en la misma URL
Es el caso de WhatsApp: la verificación llega por GET y los mensajes por POST, a la misma dirección, pero cada uno lo atiende un flujo distinto.
location = /webhook/entrada {
if ($request_method = GET) {
proxy_pass http://127.0.0.1:5678/webhook/verificacion;
}
proxy_pass http://127.0.0.1:5678/webhook/mensajes;
}
Ojo con dos cosas. El if dentro de location es célebre por su mala fama; funciona para esto, no lo estires. Y si nginx protesta con "proxy_pass cannot have URI part in location given by regular expression", es porque tu location es una expresión regular: usa = o un prefijo literal como arriba.
4. WebSockets a través del proxy
Los editores de automatizaciones y cualquier cosa con recarga en vivo lo piden. La parte que se olvida es el map, que va fuera del bloque server:
# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location / {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
5. Cabeceras de proxy que Laravel necesita
Sin estas, $request->ip() te devuelve la IP del proxy y las URLs generadas salen en http:// aunque el sitio esté en HTTPS:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Del lado de Laravel hay que confiar en el proxy, o las ignora.
6. Cabeceras de seguridad, para todos los sitios
# /etc/nginx/conf.d/_security_headers.inc (se incluye en cada vhost)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
El always no es adorno: sin él, las cabeceras no se agregan en respuestas de error, que es justo donde más da información un sitio.
7. Negar el acceso a archivos ocultos
location ~ /\.(?!well-known).* {
deny all;
}
Bloquea .env y .git dejando pasar .well-known, que certbot necesita para renovar. Compruébalo de verdad después de ponerlo:
curl -s -o /dev/null -w "%{http_code}\n" https://tusitio.com/.env # 403
Y la regla de siempre
sudo nginx -t && sudo systemctl reload nginx
Nunca restart si puedes reload, y nunca recargues sin probar primero. Un nginx -t que falla y un restart a ciegas dejan todos tus sitios abajo, no solo el que estabas editando.