Nginx para WordPress: Errores de configuración que provocan 5xx (y soluciones)

¿Te fue útil?

Cuando WordPress lanza un 502 o 504, el negocio no ve “un problema de conexión upstream”. Ven “la pasarela de pago rota”, “los anuncios caídos” o “la entrada del CEO no se publica”. Tú ves la otra cosa: Nginx sentado entre Internet y PHP-FPM como un portero que a veces olvida la lista de invitados.

Esta es una guía de campo sobre errores específicos de configuración de Nginx que reproducen 5xx en WordPress, cómo confirmarlos con comandos y qué cambiar sin convertir un incidente transitorio en una carrera a tiempo completo.

Datos y contexto interesantes (por qué sigue ocurriendo)

  • Nginx nació como respuesta al problema C10k (manejar 10.000 conexiones concurrentes) y se construyó en torno a I/O orientado a eventos. WordPress, por su parte, es una aplicación PHP con base de datos que prefiere trabajo síncrono. Al combinarlos obtienes un servidor web muy eficiente exponiendo una ruta de petición muy ineficiente.
  • “Bad Gateway” no es un error moral de Nginx. Es una afirmación: Nginx pidió una respuesta a un upstream (PHP-FPM) y no obtuvo una usable.
  • La mayoría de incidentes 5xx en WordPress son autoinfligidos: timeouts, valores por defecto de buffers, desajustes de permisos y suposiciones erróneas sobre cómo escala PHP-FPM. Existen bugs, pero los errores de configuración son los reincidentes.
  • PHP-FPM es un gestor de procesos, no magia. Si le das muy pocos workers, hace cola. Si le das demasiados, consume memoria y muere. Ambos pueden parecer “Nginx está roto”.
  • HTTP/2 no redujo el trabajo del servidor para páginas dinámicas; redujo el overhead de conexiones y mejoró el multiplexado. Un cliente ahora puede disparar muchas peticiones concurrentes sobre una conexión, lo que cambia la forma del tráfico y puede exponer saturación de FPM más rápido.
  • admin-ajax.php de WordPress es una URL pequeña con gran radio de impacto. Los plugins lo usan para todo, incluidas tareas de larga duración. Si lo tratas como una petición normal, descubrirás 504s en acciones “aleatorias” del administrador.
  • Las cabeceras han crecido con los años. El bloat de cookies por plugins, A/B testing y analytics puede disparar “upstream sent too big header” y manifestarse como 502/500 según el flujo de fallo.
  • Los timeouts por defecto rara vez están alineados. Nginx tiene sus timeouts, PHP-FPM tiene timeouts de petición y tu base de datos tiene esperas de bloqueo. El desajuste crea los clásicos misterios de “muere exactamente a los 60 segundos”.

Una idea parafraseada frecuentemente atribuida a Werner Vogels (operaciones y fiabilidad): Todo falla; el trabajo es diseñar para la falla y recuperarse rápido.

Broma #1: Lo único más fiable que un upstream mal configurado de Nginx es un canal de Slack llenándose con “¿alguien más ve 502s?” en menos de 30 segundos.

Guion de diagnóstico rápido (primeras/segundas/terceras comprobaciones)

Cuando un sitio WordPress empieza a lanzar 5xx, no empiezas reescribiendo todo el server block. Haces triage. Identificas si la falla es ruteo, salud del upstream, capacidad, o política (permisos/límites). Este guion es el camino más corto que conozco para “arreglar o aislar”.

Primero: identifica qué 5xx y dónde se genera

  • 502/504 suele indicar problemas de upstream (PHP-FPM, red, socket, timeouts).
  • 500 puede ser crash del upstream, fatal de script, bucle de reescritura o error interno de Nginx.
  • 503 suele ser limitación por tasa, modo mantenimiento, upstream marcado como caído o agotamiento de capacidad.

Segundo: revisa los registros de error que dicen la verdad

  • Registro de errores de Nginx para errores upstream, problemas de buffers, bucles de reescritura, problemas de permisos.
  • Registro de PHP-FPM para “server reached pm.max_children”, peticiones lentas, segfaults y workers terminados.
  • Kernel / journal de systemd para OOM kills, reinicios y agotamiento de descriptores de archivos.

Tercero: decide si es un bug de una sola petición o un evento de capacidad/cola

  • Bug de una sola petición: sólo falla una URL; otras páginas funcionan; el error se repite instantáneamente. Piensa en reescrituras, permisos, fatal de script, protección de traversal de rutas, comportamiento de un plugin específico.
  • Capacidad/cola: 504s generalizados, respuestas lentas, aumento del tiempo de conexión upstream, FPM al límite, DB lenta. Piensa en tuning de pm, timeouts, bloqueos de BD o un pico de tráfico.

Cuarto: elige una mitigación segura

  • Aumenta el detalle del logging temporalmente (no para siempre).
  • Incrementa timeouts específicos con cuidado solo donde coincida con la realidad.
  • Escala/aumenta la capacidad de FPM si puedes permitir la memoria.
  • Desactiva la ruta del plugin que hace trabajo de 2 minutos vía HTTP.

Tareas prácticas: comandos, salida esperada y decisiones (12+)

Estos son los comandos que realmente ejecuto cuando suena el pager. Cada uno incluye lo que significa la salida y la decisión que tomas a partir de ella. Ejecútalos en el host de Nginx salvo indicación.

Task 1: Confirmar que la configuración de Nginx se parsea correctamente

cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Qué significa: La sintaxis es válida. Esto no significa que la configuración sea correcta, solo que es parseable.

Decisión: Si esto falla, para. Corrige la sintaxis antes de perseguir 5xx fantasmas que son solo un reload fallido.

Task 2: Comprobar si Nginx cargó realmente tu configuración más reciente

cr0x@server:~$ sudo systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled)
     Active: active (running) since Fri 2025-12-27 09:12:41 UTC; 3h 10min ago
       Docs: man:nginx(8)
   Main PID: 1327 (nginx)
      Tasks: 2 (limit: 18962)
     Memory: 8.9M
     CGroup: /system.slice/nginx.service
             ├─1327 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
             └─1330 nginx: worker process

Qué significa: Nginx está en ejecución. La línea de comando del master puede revelar si está usando una ruta de configuración alternativa.

Decisión: Si no está activo o está en crash-loop, arregla eso primero. Si editaste archivos pero no recargaste, estás depurando la configuración de ayer.

Task 3: Seguir el log de errores de Nginx y reproducir una petición fallida

cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/27 12:18:07 [error] 1330#1330: *481 upstream prematurely closed connection while reading response header from upstream, client: 203.0.113.19, server: example.com, request: "GET /wp-admin/ HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
2025/12/27 12:18:08 [error] 1330#1330: *482 connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 203.0.113.19, server: example.com, request: "GET /wp-login.php HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"

Qué significa: Ya están gritando dos causas raíz distintas: un cierre prematuro del upstream (worker de FPM murió/timeout) y un problema de permisos en el socket.

Decisión: No adivines. Coge la línea de error que más se repita y arregla ese modo de fallo primero.

Task 4: Confirmar que PHP-FPM está activo y escuchando donde Nginx cree

cr0x@server:~$ sudo systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled)
     Active: active (running) since Fri 2025-12-27 09:11:02 UTC; 3h 12min ago
       Docs: man:php-fpm8.2(8)
    Process: 1051 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php8.2-fpm.sock /etc/php/8.2/fpm/pool.d/www.conf 82 (code=exited, status=0/SUCCESS)
   Main PID: 1018 (php-fpm8.2)
      Tasks: 18 (limit: 18962)
     Memory: 312.4M
     CGroup: /system.slice/php8.2-fpm.service
             ├─1018 php-fpm: master process (/etc/php/8.2/fpm/php-fpm.conf)
             ├─1032 php-fpm: pool www
             └─1033 php-fpm: pool www

Qué significa: El servicio está vivo. Aún no confirma que Nginx pueda alcanzar el socket, solo que FPM no ha desaparecido.

Decisión: Si está inactivo o reiniciándose, investiga por qué (OOM, error de configuración). Si está activo, verifica ruta del socket y permisos a continuación.

Task 5: Inspeccionar permisos del socket FastCGI

cr0x@server:~$ ls -l /run/php/php8.2-fpm.sock
srw-rw---- 1 www-data www-data 0 Dec 27 09:11 /run/php/php8.2-fpm.sock

Qué significa: Solo root y miembros del grupo www-data pueden conectar. Si los workers de Nginx corren como nginx, recibirán permission denied.

Decisión: O ejecuta Nginx como www-data (común en Debian/Ubuntu) o cambia el owner/group del pool FPM para que coincida con el usuario de Nginx. No hagas “chmod 777” y te metas en incidentes de seguridad.

Task 6: Verificar usuario de worker de Nginx y hacer que coincida con el socket

cr0x@server:~$ grep -R "^\s*user\s" /etc/nginx/nginx.conf
user www-data;

Qué significa: Los workers de Nginx corren como www-data, así que el socket anterior debería ser alcanzable.

Decisión: Si el usuario de Nginx difiere (por ejemplo, nginx), cambia o el usuario de Nginx o la propiedad del socket FPM; mantenlo consistente entre hosts.

Task 7: Ver si PHP-FPM está saturado (pm.max_children alcanzado)

cr0x@server:~$ sudo grep -R "pm.max_children" /etc/php/8.2/fpm/pool.d/www.conf
pm.max_children = 20
cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm.log
[27-Dec-2025 12:21:44] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it

Qué significa: Las peticiones están haciendo cola en FPM. Nginx ve upstreams lentos y empieza a hacer timeout o a fallar conexiones.

Decisión: O aumentas pm.max_children (si tienes memoria libre), reduces el coste por petición (cache, BD), o escalas horizontalmente. Si lo aumentas a ciegas, cambiarás 504s por OOM kills.

Task 8: Confirmar el status HTTP exacto y tiempos desde el edge

cr0x@server:~$ curl -sS -o /dev/null -w "code=%{http_code} ttfb=%{time_starttransfer} total=%{time_total}\n" https://example.com/wp-admin/
code=504 ttfb=60.002 total=60.002

Qué significa: Un muro de 60 segundos sugiere un timeout de Nginx (a menudo fastcgi_read_timeout por defecto bajo en algunas configs) o un upstream que se queda consistentemente bloqueado.

Decisión: Si el fallo ocurre a un número redondo, busca timeouts y colas. Si falla instantáneamente, busca permisos, sockets faltantes o fatales inmediatos de PHP.

Task 9: Validar que el ruteo de WordPress (try_files) no esté mal

cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '/server_name example.com/,/}/p' | sed -n '1,140p'
server {
    server_name example.com;
    root /var/www/example.com/public;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }
}

Qué significa: El patrón canónico está presente: sirve estático si existe, de lo contrario enruta a index.php con los argumentos de consulta.

Decisión: Si ves try_files $uri /index.php; sin $args, espera comportamiento “aleatorio” de la app y fallos de plugins. Si ves recursión (ruteo a una URI que vuelve a entrar en la misma location), espera 500s por bucles de redirección internos.

Task 10: Buscar “upstream sent too big header” (cookie bloat)

cr0x@server:~$ sudo grep -R "too big header" -n /var/log/nginx/error.log | tail -n 5
/var/log/nginx/error.log:1928:2025/12/27 11:04:15 [error] 1330#1330: *211 upstream sent too big header while reading response header from upstream, client: 198.51.100.77, server: example.com, request: "GET /wp-admin/ HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"

Qué significa: Nginx no pudo encajar las cabeceras del upstream en los buffers configurados. Las páginas de administración de WordPress y los plugins son culpables comunes por cookies grandes y redirecciones.

Decisión: Aumenta los buffers FastCGI en el servidor/location específico; además reduce el bloat de cookies si es posible. No lo aumentes globalmente sin motivo; inflarás la memoria bajo carga.

Task 11: Ver si estás rechazando uploads (413) pero los usuarios lo reportan como “5xx”

cr0x@server:~$ sudo grep -R "client intended to send too large body" -n /var/log/nginx/error.log | tail -n 3
/var/log/nginx/error.log:2210:2025/12/27 10:31:19 [error] 1330#1330: *302 client intended to send too large body: 134217728 bytes, client: 203.0.113.58, server: example.com, request: "POST /wp-admin/async-upload.php HTTP/2.0", host: "example.com"

Qué significa: Eso es un 413, no un 5xx, pero en ticketland se convierte en “upload falla, sitio roto”.

Decisión: Establece client_max_body_size a un valor sensato para tu negocio y alinéalo con los límites PHP (upload_max_filesize, post_max_size).

Task 12: Detectar OOM kills que parecen 502 aleatorios

cr0x@server:~$ sudo journalctl -k --since "2 hours ago" | grep -i -E "oom|killed process" | tail -n 10
Dec 27 11:58:22 server kernel: Out of memory: Killed process 1033 (php-fpm) total-vm:1324080kB, anon-rss:512000kB, file-rss:0kB, shmem-rss:0kB

Qué significa: El kernel mató a un worker de PHP-FPM. Nginx reporta upstream closed connection o 502. Parece intermitente porque depende de la presión de memoria.

Decisión: Deja de subir pm.max_children. Reduce el número de workers, arregla fugas de memoria (plugins), añade RAM o aisla cargas. OOM es el servidor diciendo “no” de la manera menos diplomática.

Task 13: Confirmar límites de descriptores de archivo (una fábrica silenciosa de 5xx)

cr0x@server:~$ sudo cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep "Max open files"
Max open files            1024                 524288               files

Qué significa: El límite soft es 1024. Bajo ráfagas de keepalive + HTTP/2 + sockets upstream, eso no es generoso.

Decisión: Aumenta el límite soft vía overrides de systemd y worker_rlimit_nofile en Nginx. Luego valida con carga. Si lo ignoras, perseguirás fallos upstream “aleatorios” por semanas.

Task 14: Comprobar fallos de conexión upstream (ruta del socket incorrecta o no creada)

cr0x@server:~$ sudo grep -R "fastcgi_pass" -n /etc/nginx/sites-enabled | head
/etc/nginx/sites-enabled/example.com.conf:42:        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
cr0x@server:~$ test -S /run/php/php8.2-fpm.sock; echo $?
0

Qué significa: Código de salida 0 significa que el socket existe y es un archivo socket. Si devuelve 1, Nginx apunta a una fantasía.

Decisión: Corrige la ruta del socket o cambia a TCP (127.0.0.1:9000) si necesitas conectividad entre contenedores/namespaces. Pero mantenlo consistente y documentado.

Task 15: Identificar peticiones PHP lentas y emparejarlas con timeouts de Nginx

cr0x@server:~$ sudo grep -R "request_slowlog_timeout" /etc/php/8.2/fpm/pool.d/www.conf
request_slowlog_timeout = 5s
cr0x@server:~$ sudo tail -n 20 /var/log/php8.2-fpm/www-slow.log
[27-Dec-2025 12:19:11]  [pool www] pid 1099
script_filename = /var/www/example.com/public/wp-admin/admin-ajax.php
[0x00007f2b9c8a2e10] mysqli_query() /var/www/example.com/public/wp-includes/wp-db.php:2056

Qué significa: PHP pasa tiempo en consultas a BD. Nginx espera. Si tu timeout de Nginx es menor que el tiempo de ejecución de la petición lenta, obtendrás 504s.

Decisión: Arregla la lentitud (BD/índices/plugin) antes de inflar timeouts. Timeouts mayores sin capacidad son cómo obtienes una caída en cámara lenta en lugar de una falla rápida.

Errores comunes: síntomas → causa raíz → solución

Esta es la sección “estoy sangrando; ¿qué arteria es?”. Lee el síntoma, valida con logs y aplica la solución dirigida.

1) 502 Bad Gateway inmediatamente en páginas PHP

Síntomas: Los assets estáticos cargan. Cualquier ruta .php falla instantáneamente. El log de error de Nginx muestra connect() ... failed (2: No such file or directory) o (13: Permission denied).

Causa raíz: Ruta errónea en fastcgi_pass, socket faltante (FPM no en ejecución), o desajuste de permisos entre el usuario de Nginx y el pool FPM.

Solución: Alinea fastcgi_pass con el socket real, asegura que FPM esté activo y configura directivas del pool FPM como:
listen = /run/php/php8.2-fpm.sock,
listen.owner = www-data,
listen.group = www-data,
listen.mode = 0660.
Recarga FPM y Nginx.

2) 504 Gateway Timeout en límites de tiempo consistentes (30s/60s/120s)

Síntomas: El sitio “funciona pero a veces hace timeout”, a menudo en un número redondo. El log de Nginx muestra upstream timed out.

Causa raíz: Timeouts desalineados: fastcgi_read_timeout de Nginx demasiado bajo para el tiempo real de la petición, o cola en FPM/BD que hace esperar la petición.

Solución: Primero encuentra por qué las peticiones son lentas (saturación FPM, locks BD, plugin). Luego ajusta timeouts intencionalmente: los timeouts de Nginx deben estar un poco por encima del peor caso realista para peticiones interactivas. Para trabajos de larga duración, deja de hacerlos síncronos sobre HTTP.

3) 500 Internal Server Error justo después de deploy/reload

Síntomas: Todo iba bien, luego reload, luego 500s. El log de Nginx menciona rewrite or internal redirection cycle o could not build the variables_hash o un error de include path.

Causa raíz: Bucles de reescritura, orden de includes roto, o locations con regex inválidas que capturan más de lo debido.

Solución: Usa la estructura conocida y probada para WordPress, mantén las reglas de reescritura al mínimo y evita regex “ingeniosas” que intentan parsear WordPress. Nginx es un gran enrutador y un mal framework PHP.

4) 502/500 en wp-admin solamente, la página principal bien

Síntomas: La home carga. Las páginas de administrador fallan con errores de buffers/cabeceras. El error log muestra upstream sent too big header.

Causa raíz: Cabeceras grandes desde el upstream (normalmente cookies) por plugins o flujos de autenticación que exceden los buffers FastCGI.

Solución: Aumenta el buffering FastCGI para ese servidor, por ejemplo ajustando fastcgi_buffer_size y fastcgi_buffers. También reduce la proliferación de cookies (desactiva el plugin que escribe media novelas en cookies).

5) 503 Service Unavailable bajo carga, luego se recupera

Síntomas: Picos causan 503s, a menudo con logs de “limiting requests” o fallos upstream. A veces solo algunos clientes lo reciben.

Causa raíz: Rate limiting demasiado agresivo, limitación de conexiones por IP (mala para clientes NATeados), o colapso de capacidad upstream (FPM al máximo).

Solución: Ajusta limitación de tasas usando distribuciones reales de tráfico. No castigues a todos detrás de un NAT corporativo. Si el upstream se colapsa, arregla capacidad y caching primero; el rate limiting debe proteger, no throttlear permanentemente.

6) 502s aleatorios con “upstream prematurely closed connection”

Síntomas: Fallos intermitentes, peor bajo carga. Los logs de Nginx muestran upstream closed connection while reading response headers.

Causa raíz: Worker de PHP-FPM crasha, es matado (OOM), alcanza request_terminate_timeout, o hace segfault por una extensión.

Solución: Revisa logs del kernel OOM, logs de FPM y logs de error de PHP. Estabiliza la memoria, limita el número de workers, elimina extensiones sospechosas y establece timeouts de terminación razonables para fallar rápido en vez de fallar raro.

7) 500/502 tras habilitar “microcache” o fastcgi_cache

Síntomas: Usuarios autenticados ven páginas incorrectas, acciones del admin fallan, 5xx ocasionales por bloqueo de cache/stampede, cabeceras raras.

Causa raíz: Cachear contenido dinámico/autenticado incorrectamente, cachear respuestas que deberían bypassearse o cachear páginas de error.

Solución: Cachea solo GET/HEAD anónimos, bypass para cookies como wordpress_logged_in_ y rutas admin, y evita cachear 5xx. Microcache puede ser genial; también es una trampa con excelente marketing.

8) 500 en URLs específicas con “Primary script unknown”

Síntomas: Algunas rutas PHP fallan; el log de error o de FPM referencia Primary script unknown.

Causa raíz: Mapeado erróneo de root/fastcgi_param SCRIPT_FILENAME, normalmente por copiar y pegar una configuración genérica que no coincide con tu layout de directorios.

Solución: Usa snippets FastCGI provistos por la distribución cuando sea posible y asegúrate de que root apunte al document root de WordPress. Confirma la ruta real en el filesystem de index.php.

Análisis profundo: los culpables habituales en Nginx + WordPress

Fundamentos FastCGI que no puedes permitirte ignorar

Nginx no “ejecuta PHP”. Habla FastCGI con PHP-FPM. Esa conversación tiene tres modos recurrentes de fallo:

  • Fallo de conexión (socket faltante/erróneo/permisos) → 502 instantáneo.
  • Upstream murió a mitad de respuesta (crash, OOM, timeout de terminación) → 502 con “prematurely closed connection”.
  • Upstream demasiado lento (colas, BD lenta, trabajo largo) → 504.

Si los tratas como lo mismo, harás la misma reparación una y otra vez y te preguntarás por qué el gráfico no cambia.

Ruteo de WordPress: la única línea try_files que importa

WordPress quiere “pretty permalinks” y espera que rutas inexistentes se roten a index.php. El enfoque limpio en Nginx es un try_files que comprueba un archivo real, luego un directorio real y finalmente pasa a /index.php?$args.

Los errores que causan 5xx casi siempre son variaciones de:

  • Omitir $args, lo que rompe el comportamiento de query-string y puede disparar lógica extraña de plugins, redirecciones y (sí) bucles bajo ciertas condiciones.
  • root incorrecto de modo que /index.php no existe donde Nginx cree. Nginx enruta a PHP; PHP no encuentra el archivo; verás 500/502 según la configuración.
  • Locations con regex que interceptan demasiado, especialmente las que intentan “asegurar WordPress” bloqueando patrones. Las listas de bloqueo son donde las buenas intenciones se convierten en outages.

Timeouts: alinea la cadena, no solo subas números

El tuning de timeouts es donde los SREs reciben el reproche de ser “demasiado cautelosos” hasta el día que el servidor colapsa lentamente.

Tienes timeouts en múltiples lugares:

  • Nginx: client_header_timeout, client_body_timeout, send_timeout, además de timeouts FastCGI como fastcgi_connect_timeout, fastcgi_send_timeout, fastcgi_read_timeout.
  • PHP-FPM: request_terminate_timeout y (opcionalmente) ajustes de ejecución máxima a nivel PHP.
  • PHP: max_execution_time (CLI difiere de FPM), límites de memoria y timeouts a nivel de extensiones.
  • Base de datos: esperas de bloqueo y timeouts de consulta (o ausencia de ellos).

Si Nginx se rinde a los 60 segundos pero FPM mantiene un worker ocupado 180 segundos, has creado una fuga de recursos bajo carga: las peticiones siguen ejecutándose después de que el cliente ya recibió “timeout”. Así obtienes la espiral: más timeouts → más workers atascados → más timeouts.

Haz esto en su lugar:

  • Define presupuestos para peticiones interactivas (por ejemplo, páginas de admin deben ser rápidas; tareas en background deben ser asíncronas).
  • Asegura que el timeout de Nginx esté ligeramente por encima del máximo esperado para esos endpoints interactivos.
  • Asegura que el timeout de terminación de FPM esté ligeramente por encima de Nginx para poder loguear scripts lentos y limpiar.
  • Mueve trabajo largo a colas/cron/trabajadores CLI. WordPress puede hacerlo, pero no con pensamiento deseoso.

Buffers: la razón oculta por la que tu área de administración “muere” al azar

Los buffers FastCGI son básicamente “cuánta cabecera/cuerpo de respuesta Nginx almacenará mientras lee del upstream”. Cuando son demasiado pequeños, Nginx puede fallar al leer cabeceras y devolver un 502.

Las respuestas de administración de WordPress pueden inflar cabeceras por:

  • Cookies enormes (múltiples plugins que ponen tracking o estado).
  • Múltiples cabeceras Set-Cookie durante flujos de autenticación.
  • Cadenas largas de redirecciones o plugins de seguridad añadiendo cabeceras.

La acción correcta es tunear de forma dirigida con pruebas desde logs. Aumenta buffers en el server block que sirve WordPress, valida impacto en memoria y luego—esto es lo que muchos omiten—recorta el bloat de cookies. No necesitas una cookie del tamaño de un cuento corto.

Permisos y propiedad de archivos: lo aburrido que causa outages reales

WordPress necesita acceso de lectura a archivos PHP y acceso de escritura para uploads, caches y a veces actualizaciones de plugins (según cómo despliegues). La causa más común de outage no es “permisos incorrectos”, sino “permisos cambiados en un host durante una reparación manual urgente”.

Vigila:

  • El usuario worker de Nginx no puede atravesar directorios (falta el bit execute).
  • El pool FPM corre como un usuario distinto al esperado y no puede leer el código o escribir uploads.
  • El sistema de despliegue crea archivos propiedad de un usuario CI con modos restrictivos.

Arregla con propiedad consistente y un modelo de despliegue que no dependa de que el servidor web pueda modificar el código. Si tu plan de producción incluye “el sitio se actualiza solo”, has aceptado caos operativo como característica.

HTTP/2 y concurrencia: por qué “unos pocos usuarios” pueden fundir PHP-FPM

HTTP/2 permite a un solo cliente abrir muchas streams concurrentes. Eso es excelente para cargar páginas más rápido. También es una forma elegante de que una pestaña del navegador (o un bot) genere un estallido de hits PHP en paralelo: panels de admin, endpoints de API y assets que se proxyean a PHP por mala configuración.

Si tu configuración de Nginx enruta demasiado a PHP (como imágenes por PHP, o falta de caching estático), HTTP/2 puede acelerar el dolor. La solución es servir archivos estáticos como estáticos, agresiva y correctamente, y hacer que PHP maneje solo lo que necesita PHP.

Cuando reglas de “hardening” causan 5xx

WordPress atrae snippets de hardening como polillas a una luz. Muchos están bien. Algunos rompen uploads, endpoints REST o flujos admin negando métodos o rutas incorrectamente.

Patrones comunes de rotura:

  • Bloquear POST a /wp-json/ o /wp-admin/admin-ajax.php porque “AJAX da miedo”. Eso puede bubblear como 500/503 según cómo se trate la denegación y qué espere la app.
  • Negar acceso a /wp-content/uploads/ con reglas demasiado amplias, causando que plugins fallen y lancen 5xx al no poder recuperar recursos.
  • Intentar bloquear ejecución PHP en uploads pero bloquear endpoints legítimos por una regex mala.

El hardening debe probarse como código de aplicación. Pónlo en staging. Añade checks de regresión para acciones admin. Y mantén tus reglas legibles, porque las leerás a las 2 a.m.

Broma #2: La manera más rápida de descubrir que no tienes staging es desplegar un “snippet de seguridad” directamente a producción y llamarlo valentía.

Tres micro-historias corporativas desde el frente

Micro-historia 1: El incidente causado por una suposición equivocada

Una empresa mediana ejecutaba WordPress detrás de Nginx en dos nodos de aplicación. Hicieron una actualización de PHP y el nuevo paquete usó una ruta de socket PHP-FPM diferente a la antigua. La checklist de deploy decía “restart services” y todos lo hicieron. Las health checks del balanceador eran básicas: golpeaban / y buscaban HTTP 200.

En un nodo, la config del sitio de Nginx seguía apuntando al socket viejo. El contenido estático de la home estaba cacheado y servido correctamente, así que el health check permaneció verde. El primer usuario real que intentó iniciar sesión obtuvo un 502. Luego todos los editores. Luego el equipo de marketing lo descubrió intentando publicar una entrada sensible al tiempo y viendo cómo el panel admin giraba hasta morir.

El ingeniero on-call asumió inicialmente “la actualización de PHP está rota” y empezó a revertir. Pero el rollback no ayudó porque el reinicio del servicio mantuvo la nueva ruta de socket y el desajuste con la antigua config de Nginx persistió. No era un defecto de software; era la suposición de que “PHP-FPM siempre vive en la misma ruta”.

La solución fue dolorosamente simple: apuntar fastcgi_pass al socket correcto, recargar Nginx y añadir una health check que ejercitara una ruta PHP dinámica (algo barato como un /health.php dedicado). También añadieron un guardián: una comprobación previa al reload que verifica que el socket exista y sea conectable por el usuario de Nginx.

Lo que cambió a largo plazo no fue la ruta del socket. Fue la cultura: dejaron de confiar en “la homepage devuelve 200” como definición de salud para una aplicación PHP.

Micro-historia 2: La optimización que salió mal

Otro equipo quería “acelerar WordPress” sin comprar más servidores. Habilitaron microcaching en Nginx y se sintieron héroes. El tráfico anónimo se volvió más rápido. Sus gráficos mejoraron. Alguien pegó un screenshot en la presentación trimestral.

Luego empezó lo raro. Usuarios logueados veían ocasionalmente la página de admin equivocada. Los editores se quejaban de que guardar un borrador a veces devolvía un 502. El equipo de soporte recibió tickets de “hice clic en publicar y desapareció”. El ingeniero on-call miró el log de errores de Nginx y vio una mezcla de timeouts upstream y comportamiento de bloqueo relacionado con cache durante ráfagas.

La causa raíz no fue el microcaching en general; fue aplicarlo demasiado ampliamente. Cachearon respuestas que nunca deberían cachearse: páginas admin, peticiones con cookies de autenticación y algunos endpoints de plugins. Bajo carga, se formó un stampede de cache: muchas peticiones esperaban el mismo cálculo upstream, pero por la clave de cache y condiciones de bypass no se coalescían correctamente.

La solución incluyó reglas estrictas de bypass para todo lo que tuviera cookies de WordPress, no-cache explícito para admin y AJAX, y caching conservador solo de GET/HEAD anónimos. También incluyeron “no cachear 5xx”, porque cachear un fallo es una forma de convertir un tropiezo transitorio en un outage prolongado.

La lección no fue “nunca cachees”. Fue: cachear es comportamiento de aplicación. Trátalo como código, pruébalo como código y desplegarlo lentamente como si fueras responsable del resultado. Porque lo eres.

Micro-historia 3: La práctica aburrida pero correcta que salvó el día

Una gran empresa tenía WordPress como uno de muchos sitios en una plataforma Nginx compartida. Sus configs se generaban desde plantillas y se versionaban. Cada cambio requería una prueba de config y un smoke test automatizado que golpeaba: home, un endpoint PHP dinámico, página de login y un endpoint de subida de media con un archivo pequeño.

Un día, un ingeniero propuso una limpieza: “estandarizar includes FastCGI en todos los sitios”. Modificaron un snippet compartido usado por docenas de hosts. Parecía seguro. No lo fue. El snippet cambió cómo se calculaba SCRIPT_FILENAME, lo que rompió a un subconjunto de sitios cuyos root no eran uniformes.

La pipeline lo captó. El smoke test falló en la ruta de login con un 500 y los logs mostraron Primary script unknown. Nadie tuvo que aprender ese modo de fallo durante un incidente en vivo. La solución fue ajustar la plantilla para respetar el root de cada sitio y añadir una prueba tipo unit que valide las rutas resueltas esperadas para cada vhost.

No fue ingeniería glamorosa. Nadie recibió un chute de dopamina por “evitamos un outage”. Pero ahorró horas de downtime y mucha credibilidad interna.

Si buscas la moraleja: las prácticas aburridas escalan mejor que la depuración heroica.

Listas de verificación / plan paso a paso (despliega sin drama)

Paso a paso: desde alerta 5xx hasta servicio estable

  1. Confirma el radio del impacto. ¿Una URL o todas las rutas PHP? ¿Solo anónimos o también admin?
  2. Extrae las N líneas principales del log de Nginx. Busca fallos de conexión upstream, timeouts, errores de cabecera/buffer, bucles de reescritura.
  3. Comprueba la salud de PHP-FPM. Estado del servicio, existencia del socket, alineación de permisos, avisos de max_children.
  4. Busca OOM/reinicios. Los logs del kernel y los reinicios de systemd suelen ser la historia real.
  5. Mide una petición. Usa curl timing para ver si es fallo instantáneo vs timeout.
  6. Mitiga de forma segura. Escala FPM dentro de límites de memoria, evita la cache problemática, desactiva el endpoint de plugin que hace trabajo largo o incrementa timeouts temporalmente con ticket para arreglar la causa raíz.
  7. Verifica con un endpoint de salud dinámico. No declares victoria basándote en contenido estático.
  8. Anota la firma. “Si ves X en logs, fue Y como causa raíz.” Esto reduce MTTR más que casi cualquier tuning.

Checklist de configuración: server block mínimo viable para WordPress en Nginx

  • root correcto al document root de WordPress.
  • try_files incluye $args: try_files $uri $uri/ /index.php?$args;
  • Location PHP incluye snippet correcto y fastcgi_pass correcto.
  • Denegar acceso a archivos sensibles con sentido (.htaccess, backups de wp-config), sin caos de regex.
  • Caching estático para assets servidos directamente por Nginx, no via PHP.
  • Tamaño de upload alineado con límites PHP.
  • Logging que incluya tiempos upstream al menos durante ventanas de incidente.

Checklist operacional: prevenir repeticiones de 5xx

  • Las health checks deben golpear PHP. Un endpoint dinámico puede salvarte de pools medio-verdes.
  • Pinea y controla la ruta del socket PHP-FPM. Evita defaults implícitos que cambian con upgrades.
  • Limita y observa FPM. Define pm.max_children con medidas de memoria, no intuición.
  • Habilita slow logs. Si no sabes qué es lento, “arreglarás” timeouts para siempre.
  • No permitas que producción se actualice sola. Despliega código como adultos: CI, artefactos, rollbacks.
  • Prueba los reloads de Nginx. nginx -t no es opcional. Tampoco verificar que la nueva config está activa.

FAQ

1) ¿Por qué obtengo 502 en lugar de 504?

502 típicamente significa que Nginx no pudo establecer o mantener una conexión válida con el upstream (socket faltante, permission denied, upstream cerrado temprano). 504 significa que Nginx se conectó pero no recibió respuesta a tiempo. Tu error log suele aclararlo.

2) ¿Debería usar Unix socket o TCP para php-fpm?

En el mismo host, un Unix socket es común y eficiente, con menos piezas móviles. Usa TCP cuando necesites conectividad entre contenedores o hosts, o cuando tu entorno haga engorroso el manejo de permisos de sockets. Sea cual sea la elección, mantenla consistente y monitorizada.

3) Subí fastcgi_read_timeout y los 504s pararon. ¿Ya está listo?

Puede que hayas cambiado una “falla rápida” por una “cola más larga”. Si las peticiones son lentas por saturación de FPM o BD, timeouts mayores pueden aumentar la presión de concurrencia y empeorar picos. Usa slow logs y métricas de colas para confirmar que arreglaste la causa y no el síntoma.

4) ¿Qué causa “upstream sent too big header” en WordPress?

Normalmente bloat de cookies: demasiadas cookies, cookies muy grandes o demasiadas cabeceras Set-Cookie. WordPress admin más plugins es la tormenta perfecta. Arregla aumentando FastCGI buffers y reduciendo el crecimiento de cookies donde sea posible.

5) ¿Pueden las reescrituras de Nginx causar 500?

Sí. Bucles de reescritura y ciclos de redirección internos pueden producir 500s. WordPress generalmente necesita un try_files simple y rewrites mínimos. Si haces lógica de reescritura compleja, probablemente estés reimplementando mal el ruteo de WordPress.

6) ¿Cómo sé si PHP-FPM es el cuello de botella o la base de datos?

Empieza con los slow logs de PHP-FPM. Si los stack traces apuntan a llamadas DB (por ejemplo, en wp-db.php), la base de datos probablemente sea el limitador. También busca avisos de FPM max_children (colas) y correlaciona con métricas DB (esperas de bloqueo, consultas lentas). Un 504 suele ser “alguien esperó por otra cosa”.

7) ¿Por qué solo fallan wp-admin y wp-login.php mientras la home funciona?

Las páginas de admin suelen generar cabeceras más grandes y dependen de cookies. Además son más dinámicas, por lo que exponen problemas upstream antes. Si la home está cacheada o es mayormente estática, puede enmascarar fallos upstream. Por eso las health checks estáticas mienten.

8) ¿Es seguro habilitar fastcgi_cache para WordPress?

Puede ser seguro para tráfico anónimo si haces bypass para cookies de login, rutas admin, URLs de preview, carritos/checkout y todo lo personalizado. Un caching mal aplicado rompe la corrección primero y luego la disponibilidad. Prueba a fondo y despliega gradualmente.

9) ¿Cuál es la causa más común y “simple” de 502 tras mantenimiento?

Una ruta de socket desajustada tras una actualización de PHP o un servicio reiniciado que recreó el socket con permisos distintos. Es vergonzosamente común y se detecta rápido comprobando fastcgi_pass y un ls -l al socket.

10) ¿Necesito tunear worker_processes, worker_connections de Nginx por problemas 5xx en WordPress?

A veces, pero raramente es la primera solución. Los outages de WordPress tienden a ser por CPU/RAM/BD upstream, no por agotamiento de workers de Nginx. Aun así, si ves límites de descriptores o caps de conexión, arréglalos. Nginx suele ser el mensajero, no el asesino.

Conclusión: próximos pasos para evitar repeticiones

Si ves 5xx en WordPress detrás de Nginx, tu trabajo es dejar de tratarlo como un capricho místico del servidor web. Los modos de fallo son consistentes: conectividad de socket, capacidad del upstream, timeouts, buffers y ruteo. Los logs te dirán cuál es, si los lees como operador y no como adivino.

Próximos pasos prácticos:

  1. Añade un endpoint de salud dinámico y haz que tu balanceador lo compruebe.
  2. Estandariza y verifica rutas y permisos de sockets durante deploys y upgrades.
  3. Habilita logging lento en PHP-FPM y trata lo “lento” como un bug de fiabilidad.
  4. Dimensiona los workers FPM según memoria medida y párate cuando llegues al headroom seguro.
  5. Haz el caching explícito y conservador: solo GET/HEAD anónimos salvo razón fuerte.
  6. Escribe un runbook corto usando las tareas arriba, para que el próximo incidente sea un procedimiento y no un debate.

Haz eso y tu próximo 502 no será un misterio. Será una clase conocida de problemas con una lista corta de soluciones — y eso es lo que “fiabilidad” parece en el mundo real.

← Anterior
Debian 13 reinicios de enlace SATA: demuestra que es el cable o la backplane, no Linux
Siguiente →
PostgreSQL vs RDS PostgreSQL: ajustes de rendimiento que aún debes hacer (incluso en gestionado)

Deja un comentario