WordPress 502 Bad Gateway: PHP-FPM, Nginx, Cloudflare — Cómo encontrar al culpable

¿Te fue útil?

Los 502 son el peor tipo de caída: los que hacen que todos discutan de quién es la culpa. Cloudflare apunta a tu origen. Nginx apunta a PHP-FPM. PHP-FPM apunta a WordPress. WordPress apunta a un plugin escrito en 2013 por alguien que desde entonces encontró la paz en la carpintería.

Esta guía es una manera orientada a producción para dejar de adivinar. Identificarás dónde murió la petición, por qué murió y qué cambio la arregla realmente, sin convertir tu servidor en una feria de ciencias.

Qué significa realmente un 502 (en esta pila)

Un 502 Bad Gateway no es un error de WordPress. Es un error de proxy. Algo que actúa como puerta de enlace (Cloudflare, Nginx, un balanceador) intentó hablar con un upstream (tu origen, PHP-FPM) y recibió datos corruptos, nada o sufrió un timeout que produce un 502.

Esa distinción importa porque se depuran los 502 demostrando dónde ocurrió la falla. Tu objetivo no es “arreglar WordPress”. Tu objetivo es “identificar el primer componente que no cumplió su función”. Una vez que lo sabes, la solución se vuelve obvia. A menudo aburrida. Normalmente eficaz.

Dos realidades comunes:

  • 502 desde Cloudflare: Cloudflare no pudo obtener una respuesta válida de tu origen a tiempo, o el origen cerró la conexión. Cloudflare no ejecutó tu código PHP. Le preguntó al origen educadamente y fue ignorado.
  • 502 desde Nginx: Nginx no pudo comunicarse con PHP-FPM (socket rechazado, permiso denegado, upstream cerrado, upstream con timeout). Nginx tampoco ejecutó tu PHP. Intentó entregarlo y la entrega falló.

El modelo mental que uso en guardia: los 502 son fallos de entrega. La pregunta más valiosa es: ¿qué entrega?

Guía rápida de diagnóstico

Si no recuerdas nada más, recuerda este orden. Corta la culpa y te lleva al culpable rápido.

Paso 1: Clasifica la fuente del 502 (edge vs origen)

  • Si los usuarios ven una página de error con marca de Cloudflare, empieza por los encabezados de Cloudflare y la alcanzabilidad del origen.
  • Si los usuarios ven la página de error de tu sitio o un simple “502 Bad Gateway” de tu servidor, empieza por los logs de Nginx y PHP-FPM.

Paso 2: Revisa el log de errores de Nginx por la cadena de error del upstream

Esto es el suero de la verdad más rápido. La redacción exacta te dice si es “upstream timed out”, “connect() failed”, “upstream prematurely closed connection” o “no live upstreams”. Cada una mapea a una solución distinta.

Paso 3: Comprueba la salud y capacidad de PHP-FPM

Busca max_children reached, entradas en el slowlog o un socket de pool muerto/inaccesible. No modifiques a ciegas. Confirma saturación y confirma el margen de memoria.

Paso 4: Correlaciona por tiempo y ruta de la petición

La mayoría de 502 no son aleatorios. Se concentran en endpoints específicos (wp-admin, wp-cron.php, /checkout, una acción AJAX). Encuentra la ruta. Luego encuentra qué código se ejecuta.

Paso 5: Decide: conectividad, capacidad o latencia

  • Conectividad: permisos del socket, listen backlog, SELinux/AppArmor, firewall, dirección upstream equivocada.
  • Capacidad: trabajadores insuficientes, CPU saturada, MySQL bloqueado, esperas de IO, presión de memoria.
  • Latencia: consultas lentas, llamadas a API externas, plugin defectuoso, disco lento, bloqueos DNS.

Broma #1: Un 502 es la forma en que tu servidor dice “lo intenté”, que también es lo que escribo en retrospectivas de incidentes cuando faltan gráficos.

Sigue la petición: Cloudflare → Nginx → PHP-FPM → WordPress

Imagina la petición como una carrera de relevos:

  1. Navegador → Cloudflare: el usuario llega al edge. Cloudflare aplica reglas WAF, caché, comprobaciones de bots.
  2. Cloudflare → Origen (tu servidor): Cloudflare se conecta a tu Nginx/Apache, normalmente en 443.
  3. Nginx → PHP-FPM: Nginx proxifica las peticiones PHP a un socket UNIX o puerto TCP.
  4. PHP-FPM → WordPress: PHP ejecuta código, llama a MySQL, quizá Redis, quizá APIs externas.
  5. La respuesta vuelve.

Un 502 ocurre cuando un corredor deja caer el testigo. La clave es encontrar qué corredor y por qué. Por eso los logs y la correlación temporal importan más que la intuición.

Hechos y contexto interesantes (para que los errores tengan sentido)

  • Hecho 1: “Bad Gateway” es un estado HTTP definido para intermediarios, no para código de aplicación. Tu app PHP normalmente no genera 502 a propósito.
  • Hecho 2: Nginx se popularizó en parte porque su modelo orientado a eventos maneja muchas conexiones inactivas eficientemente—ideal para clientes lentos, no una varita mágica para upstreams lentos.
  • Hecho 3: PHP-FPM es el gestor de procesos de facto para PHP porque aisla la ejecución de PHP y permite que el servidor web se mantenga ligero; esa separación es exactamente por qué existen fallos de “entrega”.
  • Hecho 4: El famoso “522” de Cloudflare existe, pero “502/504 en el edge” a menudo es solo lentitud del origen expresada de forma distinta—los timeouts son decisiones de política, no verdades universales.
  • Hecho 5: El endpoint admin-ajax de WordPress puede convertirse en un punto de alta concurrencia; es efectivamente un endpoint RPC que muchos plugins abusan.
  • Hecho 6: Los ajustes de keepalive y buffering se afinaron históricamente para upstreams caros y clientes lentos; las pilas modernas todavía heredan estos parámetros, y un mal ajuste puede amplificar fallos.
  • Hecho 7: La clásica advertencia “max_children reached” en PHP-FPM no es un error por sí misma; es una alarma de capacidad que suele correlacionar con 502/504 en el proxy.
  • Hecho 8: Los sockets UNIX son un poco más rápidos que el loopback TCP para Nginx→FPM, pero los errores de permisos y de ruta son más comunes con sockets que con TCP.
  • Hecho 9: Muchos 502 “aleatorios” ocurren en despliegues porque los reloads de PHP-FPM pueden brevemente eliminar sockets o reiniciar conexiones; la configuración de recarga gradual importa.

Tareas prácticas: comandos, salidas, decisiones (12+)

Estas son las tareas que realmente ejecuto bajo presión. Cada una tiene tres partes: un comando, lo que significa la salida y la decisión que tomas. Ajusta los nombres de servicio (php8.2-fpm vs php-fpm) para tu distro.

Task 1: Confirmar dónde se genera el 502 (encabezados)

cr0x@server:~$ curl -sS -D - -o /dev/null https://example.com/ | sed -n '1,20p'
HTTP/2 502
date: Fri, 26 Dec 2025 14:03:12 GMT
content-type: text/html; charset=UTF-8
server: cloudflare
cf-ray: 88b12345abcd1234-FRA

Qué significa: server: cloudflare y un cf-ray indican que el edge está devolviendo el 502. Eso no exculpa a tu origen.

Decisión: Prueba inmediatamente el origen directamente (Task 2). Si el origen está bien, revisa ajustes de Cloudflare/WAF. Si el origen falla también, depura el origen.

Task 2: Saltar Cloudflare para probar la respuesta del origen

cr0x@server:~$ curl -sS -D - -o /dev/null --resolve example.com:443:203.0.113.10 https://example.com/ | sed -n '1,20p'
HTTP/2 200
server: nginx
content-type: text/html; charset=UTF-8

Qué significa: Esto golpea 203.0.113.10 mientras sigue usando SNI para example.com. Estás viendo la respuesta del origen.

Decisión: Si el origen devuelve 200 mientras Cloudflare devuelve 502, sospecha problemas de conectividad Cloudflare→origen, desacuerdo en modo TLS, lucha WAF/bots o rate limiting en origen contra IPs de Cloudflare. Si el origen también falla, entra en Nginx/PHP-FPM.

Task 3: Ver la cadena exacta de error del upstream en Nginx

cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/26 14:03:10 [error] 21344#21344: *99172 connect() to unix:/run/php/php8.2-fpm.sock failed (111: Connection refused) while connecting to upstream, client: 172.67.10.20, server: example.com, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"

Qué significa: Nginx intentó conectarse al socket de PHP-FPM y recibió Connection refused. Eso no es un plugin lento. Es una entrega rota.

Decisión: Comprueba si PHP-FPM está en ejecución y escuchando en ese socket (Task 4, Task 5). Si está en ejecución, puede ser una discrepancia de ruta de socket, permisos o un crash loop.

Task 4: Comprobar la salud del servicio PHP-FPM

cr0x@server:~$ 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-26 13:58:22 UTC; 4min 53s
       Docs: man:php-fpm8.2(8)
   Main PID: 20811 (php-fpm8.2)
     Status: "Processes active: 12, idle: 4, Requests: 983, slow: 3, Traffic: 0.8req/sec"

Qué significa: El servicio está en ejecución. Eso no garantiza que esté escuchando donde Nginx espera.

Decisión: Verifica la existencia y propiedad del socket (Task 5) y la configuración del pool FPM para listen.

Task 5: Verificar que el socket existe y los permisos

cr0x@server:~$ sudo ls -l /run/php/php8.2-fpm.sock
srw-rw---- 1 www-data www-data 0 Dec 26 13:58 /run/php/php8.2-fpm.sock

Qué significa: El socket existe, es propiedad de www-data, modo 660. Nginx debe ejecutarse como www-data o pertenecer al grupo www-data para conectar.

Decisión: Si Nginx se ejecuta como usuario nginx, ajusta grupos o establece listen.owner, listen.group y listen.mode en la configuración del pool FPM. Si falta el socket, PHP-FPM no lo está creando—revisa logs (Task 6).

Task 6: Leer logs y journal de PHP-FPM por errores del pool

cr0x@server:~$ sudo journalctl -u php8.2-fpm -n 80 --no-pager
Dec 26 14:02:57 server php-fpm8.2[20811]: [WARNING] [pool www] server reached pm.max_children setting (20), consider raising it
Dec 26 14:03:01 server php-fpm8.2[20811]: [ERROR] [pool www] child 22109 exited on signal 11 (SIGSEGV) after 12.345678 seconds from start
Dec 26 14:03:01 server php-fpm8.2[20811]: [NOTICE] [pool www] child 22117 started

Qué significa: Dos problemas separados: saturación de capacidad (max_children) e inestabilidad (segfault). El segfault suele ser una extensión defectuosa, problema con OPcache o corrupción de memoria.

Decisión: Si ves advertencias de max_children, confirma que correlacionan con 502 y afina con precaución (Task 11). Si ves segfaults, desactiva extensiones sospechosas, verifica paquetes de PHP y considera revertir cambios recientes. Trata los segfaults como un incidente de confiabilidad, no como “WordPress haciendo de las suyas”.

Task 7: Confirmar que Nginx apunta al upstream correcto

cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '/fastcgi_pass/,+3p'
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

Qué significa: Nginx espera esa ruta de socket. Si el pool FPM escucha en una ruta o puerto TCP distinto, has creado un sistema distribuido dentro de un solo servidor.

Decisión: Alinea fastcgi_pass con el listen del pool. Evita “verdades múltiples” (un socket en un archivo, un puerto TCP en otro).

Task 8: Comprobar timeouts del upstream vs errores de conexión

cr0x@server:~$ sudo grep -E "upstream timed out|prematurely closed|Connection refused|no live upstreams" -n /var/log/nginx/error.log | tail -n 8
41288:2025/12/26 14:01:21 [error] 21344#21344: *98811 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 162.158.90.12, server: example.com, request: "GET /wp-admin/ HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
41307:2025/12/26 14:03:10 [error] 21344#21344: *99172 connect() to unix:/run/php/php8.2-fpm.sock failed (111: Connection refused) while connecting to upstream, client: 172.67.10.20, server: example.com, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"

Qué significa: Tienes tanto timeout como rechazo. Eso sugiere indisponibilidad intermitente de FPM (recargas/crashes) y respuestas lentas del upstream cuando está disponible.

Decisión: Arregla primero la estabilidad (evita rechazos), luego aborda la lentitud. Un sistema consistentemente lento es más fácil de afinar que uno que desaparece al azar.

Task 9: Inspeccionar conexiones activas y presión del backlog de escucha

cr0x@server:~$ sudo ss -xlp | grep php8.2-fpm.sock
u_str LISTEN 0 4096 /run/php/php8.2-fpm.sock 113217 * 0 users:(("php-fpm8.2",pid=20811,fd=8))

Qué significa: El socket está escuchando, backlog es 4096. Si el backlog es pequeño y tienes ráfagas, verás fallos de conexión bajo carga.

Decisión: Si el backlog es bajo, ajusta listen.backlog de FPM y asegúrate de que los límites del kernel no sean pequeños. Pero no uses backlog para ocultar una pool de trabajadores insuficiente.

Task 10: Comprobar CPU, carga, IO wait (¿está muriendo la máquina?)

cr0x@server:~$ uptime; mpstat -P ALL 1 3; vmstat 1 5
 14:03:33 up 36 days,  3:22,  2 users,  load average: 12.44, 10.81, 8.03
Linux 6.1.0 (server)  12/26/2025  _x86_64_  (4 CPU)

Average:     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
Average:     all   72.10    0.00   18.34    6.22    0.00    0.78    0.00    0.00    0.00    2.56
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
12  1      0  14200  12000 820000    0    0   120   980 4200 8800 74 19  2  5  0

Qué significa: La CPU está saturada y hay algo de IO wait. Si PHP-FPM necesita CPU y la máquina está al máximo, seguirán los timeouts de Nginx.

Decisión: Reduce la concurrencia (rate limit, caché), optimiza caminos calientes o añade capacidad. Antes de aumentar FPM, confirma el margen de memoria (Task 11) para no “arreglar” 502 invocando OOM kills.

Task 11: Medir memoria por worker de PHP-FPM para afinar pm.max_children con seguridad

cr0x@server:~$ ps -ylC php-fpm8.2 --sort:rss | head -n 8
S   UID   PID  PPID  C PRI NI   RSS    SZ WCHAN  TTY          TIME CMD
S    33 22117 20811  0  80  0 126432 210944 -     ?        00:00:01 php-fpm: pool www
S    33 22098 20811  0  80  0 118944 205312 -     ?        00:00:02 php-fpm: pool www
S    33 22077 20811  0  80  0 112880 198656 -     ?        00:00:02 php-fpm: pool www
S    33 22031 20811  0  80  0 108220 190112 -     ?        00:00:03 php-fpm: pool www
S    33 21990 20811  0  80  0 104112 186880 -     ?        00:00:04 php-fpm: pool www

Qué significa: RSS alrededor de ~110–125 MB por worker en esta captura. Ese es el número que importa al subir pm.max_children.

Decisión: Si tienes 2 GB disponibles para workers PHP, probablemente no pongas max_children en 80 “por el tráfico”. Lo fijas en lo que tu RAM puede pagar, con margen para MySQL, caché del SO y picos.

Task 12: Activar y leer el slowlog de PHP-FPM para atrapar a los verdaderos culpables

cr0x@server:~$ sudo grep -nE "request_slowlog_timeout|slowlog" /etc/php/8.2/fpm/pool.d/www.conf | tail -n 5
277:request_slowlog_timeout = 5s
278:slowlog = /var/log/php8.2-fpm/www-slow.log
cr0x@server:~$ sudo tail -n 40 /var/log/php8.2-fpm/www-slow.log
[26-Dec-2025 14:01:22]  [pool www] pid 22031
script_filename = /var/www/example.com/public/wp-admin/admin-ajax.php
[0x00007f4c8b8f2a30] curl_exec() /var/www/example.com/public/wp-includes/Requests/Transport/cURL.php:204
[0x00007f4c8b8f28a0] request() /var/www/example.com/public/wp-includes/class-wp-http.php:420
[0x00007f4c8b8f2500] post() /var/www/example.com/public/wp-includes/class-wp-http.php:634
[0x00007f4c8b8f1d40] call_external_api() /var/www/example.com/public/wp-content/plugins/some-plugin/api.php:88

Qué significa: Una ruta específica (admin-ajax.php) está atascada esperando una llamada a una API externa vía cURL.

Decisión: No “aumentes timeouts”. Aísla o arregla el comportamiento del plugin: añade caché, circuit breakers, trabajos asíncronos o desactiva el plugin. Y establece un timeout upstream sensato para evitar acumulación de workers.

Task 13: Comprobar stalls en MySQL/MariaDB (upstream oculto común)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST\G" | sed -n '1,40p'
*************************** 1. row ***************************
     Id: 18821
   User: wpuser
   Host: localhost
     db: wordpress
Command: Query
   Time: 42
  State: Sending data
   Info: SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'

Qué significa: Una consulta lleva 42 segundos. Eso puede bloquear workers PHP lo suficiente para causar timeouts upstream en Nginx y 5xx en Cloudflare.

Decisión: Encuentra por qué es lenta: tablas infladas, índices faltantes, contención de disco o plugins que almacenan opciones autoload enormes. Arregla el cuello de botella de la base de datos o solo estarás moviendo sillas en cubierta.

Task 14: Validar presión de disco y salud del sistema de archivos (sí, el almacenamiento importa)

cr0x@server:~$ df -hT /var /var/www; iostat -xz 1 3
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4  120G  111G  3.6G  97% /
/dev/nvme0n1p2 ext4  120G  111G  3.6G  97% /
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          68.12    0.00   17.43    9.87    0.00    4.58

Device            r/s     w/s    rkB/s    wkB/s  await  svctm  %util
nvme0n1          45.0    60.0   3200.0   5400.0  18.2   0.7   92.0

Qué significa: El sistema de archivos raíz está al 97% de uso y el disco está al 92% de utilización con await significativo. Los discos casi llenos causan fallos en cascada: fallan escrituras de logs, fallan archivos temporales, la BD se ralentiza, PHP bloquea.

Decisión: Libera espacio ya (logs, backups antiguos), luego corrige el plan de almacenamiento subyacente. No trates de afinar PHP-FPM en un disco que está gritando.

Task 15: Confirmar que la conectividad Cloudflare→origen no está bloqueada

cr0x@server:~$ sudo iptables -S | sed -n '1,60p'
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -s 198.51.100.0/24 -j ACCEPT

Qué significa: Solo un /24 específico puede alcanzar el 443. Si las IPs del edge de Cloudflare no están en esa allowlist, verás errores de edge intermitentes según qué PoP te contacte.

Decisión: Abre 443 apropiadamente o mantén una allowlist correcta. “Solo permitimos IPs de Cloudflare” es una estrategia válida—si la mantienes actualizada.

Modos de fallo de Nginx que producen 502

1) “connect() failed (111: Connection refused)”

Esto significa que Nginx intentó conectar al upstream (socket FPM o puerto TCP) y el SO lo rechazó. Causas comunes:

  • PHP-FPM está caído o en crash-loop.
  • Nginx apunta a la ruta de socket o puerto equivocado.
  • Una recarga brevemente eliminó el socket y Nginx compitió con el reinicio.

Solución: Estabiliza primero el upstream. Verifica la ruta del socket en nginx -T, asegura la salud del servicio PHP-FPM y evita bucles agresivos de recarga durante deploys.

2) “upstream timed out (110: Connection timed out) while reading response header from upstream”

Nginx se conectó a PHP-FPM, envió la petición y luego esperó demasiado por los encabezados. Este es el caso clásico de “PHP está lento o saturado”. Sospechosos probables:

  • Los workers están todos ocupados (pm.max_children reached), así que las peticiones se encolan.
  • Uno o más endpoints son lentos (admin-ajax, checkout, cron).
  • La latencia de la base de datos o almacenamiento bloquea PHP, por lo que FPM no puede responder.
  • Llamadas a APIs externas cuelgan y fijan workers.

Solución: Usa slowlog para identificar el camino de código. Luego arregla por qué es lento. Aumentar timeouts en Nginx sin abordar acumulación de workers solo convierte 502 en 504 y hace que tus gráficos de latencia parezcan arte moderno.

3) “upstream prematurely closed connection”

El upstream aceptó la conexión, luego murió o la cerró sin enviar una respuesta completa. Causas comunes:

  • Worker PHP se estrelló (segfault, error fatal, OOM kill).
  • Problemas de buffer FastCGI con headers o respuestas enormes (menos común con HTML de WordPress, más con plugins raros).
  • Configuraciones fastcgi mal ajustadas.

Solución: Revisa logs de PHP-FPM por crashes y logs del sistema por OOM kills. Trata los crashes como bugs reales. Si son headers, busca cookies sobredimensionadas o headers generados por plugins.

4) Permiso denegado en el socket

Nginx no puede abrir el socket de FPM por permisos de sistema de archivos o SELinux/AppArmor. El log de errores dirá (13: Permission denied).

Solución: Asegura que el socket sea legible/escribible por el usuario worker de Nginx. Si SELinux está en enforcing, necesitas el contexto correcto—no una plegaria.

Modos de fallo de PHP-FPM que producen 502

1) pm.max_children reached (límite de capacidad)

Este es el escenario más común de “funciona hasta que no funciona”. Cuando todos los hijos están ocupados, las nuevas peticiones se encolan. Si la cola espera más que el timeout de Nginx, obtienes 502/504.

Qué hacer:

  • Mide memoria y CPU por worker antes de aumentar límites.
  • Arregla primero las peticiones lentas. Añadir más workers puede aumentar la carga en la base de datos y empeorar las cosas.
  • Considera pools separados para admin y tráfico público si te tomas en serio la disponibilidad.

2) Peticiones lentas que fijan workers (el asesino silencioso)

Un endpoint lento de un plugin no solo perjudica ese endpoint. Consume un worker. Bajo concurrencia, las peticiones lentas se convierten en un problema de colas y luego en una caída.

Solución: Usa slowlog. Identifica el stack trace. Arregla el camino de código. Añade caché, reduce llamadas, haz que las llamadas externas fallen rápidamente con alternativas.

3) Crashes: SIGSEGV, OOM, errores fatales

Los crashes causan “prematurely closed” o “connection refused” según el momento. Los OOM kills son especialmente desagradables porque son silenciosos a menos que revises logs del kernel.

cr0x@server:~$ dmesg -T | tail -n 20
[Fri Dec 26 14:03:02 2025] Out of memory: Killed process 22077 (php-fpm8.2) total-vm:812340kB, anon-rss:256144kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:680kB oom_score_adj:0

Decisión: Si OOM mató workers PHP, no aumentes max_children. Reduce memoria por petición (plugins, ajuste de OPcache), añade RAM o mueve servicios fuera de la máquina.

4) Listen/backlog mal y estampidas de peticiones

Bajo picos súbitos, un backlog pequeño o límites del kernel ajustados pueden convertir la carga en rechazo de conexiones. Suele aparecer durante ráfagas, tormentas de cron o purgas de caché.

Solución: Asegura que listen backlog de FPM sea razonable y que los ajustes del kernel no sean obsoletos. Pero de nuevo: backlog no es capacidad.

Culpables en WordPress y plugins/temas

WordPress en sí suele estar bien. WordPress más el ecosistema de plugins es un bazar vivo donde la calidad varía. Los 502 suelen venir de:

  • Abuso de admin-ajax.php: polling frecuente, acciones de larga duración, bucles sin límite.
  • Tormentas de wp-cron.php: el “pseudo-cron” disparado por peticiones web puede acumularse bajo carga o cuando llegan visitantes tras periodos de inactividad.
  • Llamadas a API externas: marketing, CRM, pagos, envíos, analytics. Llamadas externas sin timeouts estrictos son pegamento para los workers.
  • Inflado de opciones autoload: blobs serializados grandes cargados en cada petición. Es como llevar todo tu ático a cada reunión.
  • Procesamiento de imágenes en petición: redimensionar y optimizar síncronamente en PHP.

Usa el slowlog como detector de plugins

El stack trace del slowlog es tu amigo porque apunta a una ruta bajo wp-content/plugins/ o a un tema. Eso es evidencia. Cambia las conversaciones.

WP-CLI para triage sin heroísmos

Si el sitio se está derritiendo y necesitas aislar rápido, desactiva plugins no esenciales de forma controlada. Mejor: hazlo en staging. Pero en incidentes reales, a veces tienes que cortar la energía a un plugin y pedir disculpas después.

cr0x@server:~$ cd /var/www/example.com/public
cr0x@server:~$ sudo -u www-data wp plugin list --status=active
+-----------------------+----------+--------+---------+
| name                  | status   | update | version |
+-----------------------+----------+--------+---------+
| woocommerce           | active   | none   | 8.5.1   |
| some-plugin           | active   | none   | 2.9.0   |
| cache-plugin          | active   | none   | 1.3.2   |
+-----------------------+----------+--------+---------+

Decisión: Si el slowlog apunta a some-plugin, desactívalo primero y vuelve a probar. Si la caída para, tienes un candidato a causa raíz. Luego haz la solución real: configuración, actualización, reemplazo o escalado al proveedor.

Cloudflare: cuándo es “ellos” y cuándo eres tú

Cloudflare es un proxy inverso con opiniones. Impone timeouts y devolverá un error mientras tu origen sigue pensando. Causas comunes relacionadas con Cloudflare de 502:

  • Sobrecarga del origen: Cloudflare incrementa la concurrencia haciendo tu sitio más accesible y, en algunos casos, reintentando. Tu origen aún necesita capacidad.
  • Desacuerdo de modo TLS: “Full” vs “Full (strict)” y problemas de validación de certificados pueden mostrarse como problemas de conexión.
  • Lucha WAF y bots: peticiones bloqueadas pueden parecer fallos parciales si solo afectan a algunos endpoints/usuarios.
  • Errores en listas de IP permitidas: el firewall solo permite rangos antiguos de Cloudflare.

Enfoque práctico:

  1. Demuestra la salud del origen saltándote Cloudflare (Task 2).
  2. Revisa logs de origen por IPs de Cloudflare y patrones de petición en los tiempos de error.
  3. Confirma que tu firewall no está bloqueando IPs del edge (Task 15).

Broma #2: Cloudflare no está “caído”, solo practica poner límites con tu servidor de origen.

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

1) Síntoma: picos de 502 durante máximos de tráfico

Causa raíz: PHP-FPM max_children reached; las peticiones se encolan; Nginx expira esperando encabezados.

Solución: Mide RSS por worker, fija max_children en base a RAM, activa slowlog, arregla endpoints lentos, añade caché. Si es necesario, añade capacidad (más CPU/RAM, BD separada).

2) Síntoma: 502 solo en wp-admin o admin-ajax

Causa raíz: Plugin realizando llamadas externas lentas, o endpoints admin que evitan caché y ejecutan consultas más pesadas.

Solución: Usa slowlog, identifica el archivo del plugin, añade timeouts HTTP estrictos, cachea respuestas externas o desactiva/reemplaza el plugin.

3) Síntoma: 502 tras deploy/restart, luego “se recupera”

Causa raíz: Recarga de PHP-FPM que elimina el socket brevemente; Nginx lo pilla a mitad de transición; o calentamiento de OPcache crea pico de CPU.

Solución: Usa recargas graduales, escalona reinicios, mantiene health checks y evita reiniciar Nginx+FPM simultáneamente. Considera precalentar endpoints comunes.

4) Síntoma: Cloudflare muestra 502, acceso directo al origen está bien

Causa raíz: Firewall bloquea algunas IPs de Cloudflare; problemas intermitentes de ruteo edge→origen; desacuerdo en modo TLS.

Solución: Arregla allowlists, confirma ajustes TLS, asegúrate de que el origen soporte la concurrencia de Cloudflare. Valida con pruebas --resolve.

5) Síntoma: error en Nginx muestra Permission denied en socket FPM

Causa raíz: Propiedad del socket no coincide con el usuario worker de Nginx; contexto SELinux incorrecto.

Solución: Alinea usuarios/grupos o establece listen.owner/listen.group/listen.mode. Para SELinux, aplica la política/contexto correcto (no desactives SELinux en producción a menos que disfrutes auditorías).

6) Síntoma: 502 aparece con “upstream prematurely closed connection”

Causa raíz: Crash de PHP (segfault) o OOM kill; a veces errores fatales que terminan workers.

Solución: Revisa journal y dmesg. Restaura extensiones o cambios recientes. Reduce presión de memoria. Añade swap solo como último recurso y no lo confundas con una solución.

7) Síntoma: 502 tras activar un plugin “de rendimiento”

Causa raíz: Plugin agresivo de caché provoca stampede, purgas excesivas o incrementa carga de admin-ajax; a veces malconfigura headers/buffering.

Solución: Desactívalo para confirmar. Reintrodúcelo con configuración sensata: pre-calentamiento de caché, límites de tasa, estrategia de object cache y reglas de purga cuidadosas.

Listas de verificación / plan paso a paso

Checklist A: Primeros 10 minutos en guardia

  1. Comprueba si el error es de Cloudflare o del origen.
  2. Ejecuta curl -D - para capturar encabezados y confirmar el encabezado server.
  3. Salta Cloudflare con curl --resolve para probar el origen directamente.
  4. Haz tail del log de errores de Nginx y busca la cadena de error del upstream.
  5. Revisa el estado de PHP-FPM y el journal por max_children, crashes y errores de pool.
  6. Chequeo rápido del sistema: CPU, RAM, IO wait, disco lleno.
  7. Si es necesario, aplica una mitigación: desactiva temporalmente el plugin problemático conocido, limita endpoints abusivos o sube timeouts solo como parche.

Checklist B: Identificar si es conectividad vs capacidad vs latencia

  • Conectividad: socket faltante, permiso denegado, conexiones rechazadas, firewall bloquea. Arregla configuración y políticas de seguridad primero.
  • Capacidad: max_children reached, CPU saturada, presión de memoria. Arregla con dimensionamiento, caché, más computo.
  • Latencia: slowlog apunta a BD/API/plugin. Arregla el camino de código lento y el rendimiento de dependencias.

Checklist C: Cambios de endurecimiento que previenen reincidencias

  1. Activa slowlog de PHP-FPM con umbral bajo (p.ej., 3–5s) y rota logs.
  2. Añade IDs de petición en los logs de acceso de Nginx y propágalos (para trazabilidad).
  3. Fija timeouts estrictos para llamadas externas en el código de la app (plugins/temas).
  4. Separa responsabilidades: BD fuera de la máquina web para sitios concurridos; aísla tráfico admin en pool separado.
  5. Implementa caché de forma intencional: caché de página, object cache y estrategia de invalidación correcta.
  6. Plan de capacidad basado en mediciones, no en corazonadas.

Tres mini-historias del mundo corporativo (realistas y dolorosas)

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

El sitio estaba detrás de Cloudflare y el equipo supuso que eso significaba que el origen estaba “protegido” de picos de tráfico. Marketing lanzó una campaña. Cloudflare hizo su trabajo: aceptó una avalancha de conexiones e intentó alcanzar el origen para misses de caché.

El origen, una sola VM, tenía PHP-FPM configurado años atrás con un pm.max_children conservador. Bajo carga, los workers se saturaron. Las peticiones se encolaron. Nginx empezó a loguear upstream timed out while reading response header. Cloudflare empezó a devolver 502 porque no recibía respuestas a tiempo.

La llamada inicial fue un clásico: “Cloudflare está caído”. No lo estaba. El edge informaba correctamente que el origen no podía seguir el ritmo.

Lo que lo arregló no fue un ticket heroico a Cloudflare. El equipo activó el slowlog de PHP-FPM y encontró un endpoint dominando: llamadas a admin-ajax.php desde un widget frontend de un plugin. El widget llamaba a una API externa en cada vista sin caché.

Cachearon la respuesta de la API, pusieron un timeout corto y redujeron la frecuencia de llamadas del widget. Luego redimensionaron FPM según mediciones de memoria. La suposición murió; el sitio vivió.

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

Un ingeniero bienintencionado “optimizó” la pila subiendo drásticamente pm.max_children. El objetivo era simple: más workers, menos colas, menos 502s.

Funcionó por aproximadamente una hora. Luego la máquina empezó a hacer swap. MySQL se volvió más lento. El IO wait subió. Los workers PHP tardaban más, no menos. Los timeouts de Nginx aumentaron. Los errores de Cloudflare siguieron. El incidente empeoró porque ahora cada petición competía por disco y memoria.

La postmortem fue humillante: habían tratado PHP-FPM como un pool de hilos. No lo es. Cada worker es un proceso con coste real de memoria. Aumentar concurrencia sin aumentar recursos suele aumentar la contención y la latencia cola.

La solución fue aburrida: limitar workers a lo que la RAM puede soportar, mover MySQL a un host separado y añadir caché para que menos peticiones necesitaran PHP. La “optimización” se revirtió y todos acordaron en silencio dejar de afinar producción con cafeína y esperanza.

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

Otra compañía tenía la costumbre: cada incidente empezaba con correlación de logs, no con especulación. También tenían una convención pequeña pero útil: cada línea del access log de Nginx incluía un ID de petición, y ese ID se pasaba a PHP vía fastcgi param y la app lo logueaba.

Cuando llegaron los 502, sacaron una muestra de peticiones fallidas y vieron inmediatamente la misma ruta: un endpoint de checkout que disparaba una consulta de tarifas de envío. El slowlog de PHP-FPM mostró el stack; el ID de petición coincidía con la entrada de Nginx; los tiempos encajaban.

No discutieron sobre Cloudflare, Nginx o PHP-FPM. No lo necesitaron. La cadena de evidencia estaba limpia. La API de envíos externa era lenta y a veces colgaba. Su código tenía un timeout generoso y sin fallback.

Porque practicaban esa disciplina aburrida, la mitigación fue rápida: reducir timeout, añadir tarifas en caché como fallback y degradar con gracia. El incidente terminó sin drama, que es el mejor tipo de incidente.

Orientación operativa que resiste bajo presión

Timeouts: úsalos como guías de protección, no como estilo de vida

Hay timeouts en cada capa: Cloudflare, navegador, Nginx, FastCGI, ejecución PHP, APIs externas, base de datos. Un 502 suele ser una política de timeout aplicada. Si solo subes timeouts, normalmente conviertes fallos rápidos en fallos lentos. Los usuarios siguen perdiendo; solo gastas más cómputo mientras pierdes.

Define timeouts de modo que:

  • las llamadas externas fallen rápido (segundos, no minutos),
  • tu timeout upstream esté ligeramente por encima del percentil 95 para peticiones legítimamente lentas, y
  • tu app degrade con gracia cuando las dependencias fallen.

Una frase para mantener en la cabeza

Idea parafraseada, atribuida a John Allspaw: “La ausencia de culpabilización te ayuda a aprender las razones reales por las que fallan los sistemas”.

Preguntas frecuentes

1) ¿Por qué obtengo 502 a veces y 200 otras veces?

Los 502 intermitentes normalmente significan saturación o inestabilidad. Saturación: la pool de workers está a veces llena. Inestabilidad: recargas/crashes de PHP-FPM, OOM kills o dependencias inestables.

2) ¿Un 502 siempre es culpa de PHP-FPM?

No. El 502 lo produce la puerta de enlace (Cloudflare/Nginx) cuando el upstream falla. El upstream podría ser PHP-FPM, pero también podría ser tu origen desde la perspectiva de Cloudflare, o una dirección upstream mal configurada.

3) ¿Cuál es la forma más rápida de saber si Cloudflare está involucrado?

Revisa encabezados: server: cloudflare y cf-ray. Luego salta Cloudflare con curl --resolve para probar el origen directamente.

4) ¿Debería cambiar Nginx→PHP-FPM de socket UNIX a TCP?

Si peleas con permisos y herramientas de despliegue, TCP puede ser más sencillo de razonar. Los sockets UNIX están bien y son ligeramente más eficientes, pero la ganancia real es fiabilidad y claridad, no microoptimizaciones.

5) ¿Por qué wp-cron.php se correlaciona con 502?

Porque WP-Cron ejecuta tareas programadas en peticiones web normales. Bajo ciertos patrones de tráfico, las tareas se acumulan. Si las tareas son pesadas (envío de emails, sincronización de APIs), fijan workers PHP y causan colas.

6) ¿Puede la caché por sí sola eliminar 502?

Puedes reducir dramáticamente la probabilidad al reducir la carga del origen. Pero si tus endpoints sin caché siguen siendo lentos o tu pool de workers es inestable, la caché no salvará admin, checkout y endpoints API.

7) Veo “max_children reached” pero no 502. ¿Me importa?

Sí. Es una advertencia temprana. Puede que estés ocultando el problema con timeouts generosos o tráfico bajo. Cuando suba el tráfico o una dependencia se ralentice, lo pagarás.

8) ¿Por qué obtengo 502 solo en subidas grandes o operaciones con imágenes?

Las subidas y el procesamiento de imágenes pueden ser intensivos en IO y CPU. Si el tiempo de ejecución PHP es alto o el disco temporal está lleno/lento, los workers se quedan. Arregla moviendo el procesamiento fuera de la petición, aumentando recursos y asegurando espacio y rendimiento del disco.

9) ¿Qué hago si Nginx dice “upstream timed out” pero PHP-FPM parece inactivo?

Entonces “inactivo” puede ser engañoso: los workers pueden estar atascados en IO no interrumpible, o el cuello de botella está en otro lugar (bloqueos en la BD, stalls DNS, llamadas externas). Usa slowlog y revisa processlist de MySQL y IO wait del sistema.

10) ¿Debo reiniciar PHP-FPM cuando veo 502?

Reiniciar puede ser una mitigación si FPM está atascado, pero destruye evidencia. Captura logs primero, toma el estado actual (service status, error logs, slowlog) y reinicia si es necesario.

Conclusión: siguientes pasos que evitan la secuela

Los 502 no son misteriosos. Son específicos: un proxy no recibió una respuesta válida del upstream. Tu trabajo es nombrar la entrega fallida y luego arreglar por qué falló.

Siguientes pasos que realmente haría en producción:

  1. Instrumenta para obtener evidencia: activa slowlog de PHP-FPM, mantén limpios y rotados los logs de Nginx y registra IDs de petición.
  2. Construye una rutina rápida de triage: encabezados → saltar edge → error upstream de Nginx → salud FPM → presión del sistema → slowlog.
  3. Arregla el verdadero cuello de botella: rutas de plugin lentas, stalls en BD, presión de disco o extensiones PHP inestables.
  4. Afina al final, con mediciones: fija workers de FPM según RSS y CPU, no por esperanza.
  5. Haz que el fallo sea seguro: timeouts estrictos para APIs externas, caché, degradación con gracia y límites de tasa sensatos en endpoints abusivos.
← Anterior
Ubuntu 24.04: CIFS extremadamente lento — las opciones de montaje que normalmente arreglan el rendimiento (caso #22)
Siguiente →
Debian 13: APT está roto (“dependencias incumplidas”) — arréglalo sin reinstalar nada

Deja un comentario