Ubuntu 24.04: servidor web muestra 502/504 de repente — la verdadera causa (y cómo arreglarlo rápido)

¿Te fue útil?

Tu servidor web funcionaba bien. No desplegaste nada. Y de repente: 502 Bad Gateway y 504 Gateway Timeout en todo el sitio como un frente de mal tiempo. Los clientes actualizan la página. Los tableros se ponen rojos. Alguien sugiere “reiniciar nginx” como si fuera un ritual sagrado.

A veces un reinicio es un parche. A veces es la forma más rápida de destruir la evidencia. El objetivo aquí es encontrar el verdadero cuello de botella en Ubuntu 24.04 — procesos upstream, sockets, DNS, inanición de CPU o latencia de almacenamiento — y arreglarlo sin adivinar.

Qué significan realmente 502/504 (y qué no significan)

Cuando un usuario ve un 502/504, culpa “al servidor web”. En la práctica, el servidor web suele ser solo el mensajero.

502 Bad Gateway: “Hablé con mi upstream y fue absurdo o no respondió.”

  • Significado típico: Nginx/Apache actuó como proxy y la conexión upstream falló o retornó una respuesta inválida.
  • Causas reales comunes: proceso upstream colapsado, permisos de socket cambiados, puerto de escucha incorrecto, desacuerdo TLS, desajuste de protocolo, backend devolviendo basura porque se está muriendo.

504 Gateway Timeout: “Esperé al upstream y nunca respondió a tiempo.”

  • Significado típico: el proxy estableció una conexión (o lo intentó) pero el upstream no respondió dentro del tiempo de espera.
  • Causas reales comunes: upstream sobrecargado, llamadas a BD lentas, latencia de almacenamiento, DNS bloqueado, agotamiento de pools de conexión, interbloqueos, agotamiento de recursos del kernel.

Aquí está la verdad operativa: 502/504 suelen ser síntomas de un cuello de botella, no bugs de nginx. Nginx debe ser aburrido. Cuando no lo es, es porque otra cosa lo hizo interesante.

Broma #1: Reiniciar nginx para arreglar un 504 es como apagar la radio para arreglar atascos de tráfico. Reduce el ruido, no el problema.

Guía rápida de diagnóstico (primero/segundo/tercero)

Si no haces nada más, haz esto. El objetivo es identificar el cuello de botella en menos de 10 minutos, con evidencia que puedas entregar a la siguiente persona sin vergüenza.

Primero: confirma qué está generando los 502/504 y captura logs inmediatamente

  1. Revisa los logs de error del proxy (nginx o Apache) para la cadena exacta de error del upstream.
  2. Correlaciona marcas de tiempo con los logs del backend (php-fpm, gunicorn, node, etc.).
  3. Revisa el journal de systemd por reinicios, kills OOM, errores de permisos.

Decisión: si ves “connect() failed (111: Connection refused)” es un problema de accesibilidad al upstream. Si ves “upstream timed out” es un problema de rendimiento o interbloqueo. Si ves “permission denied” suele ser un problema de socket/SELinux/AppArmor/modos de archivo.

Segundo: determina si es CPU, memoria, red o almacenamiento

  1. CPU + carga + iowait: si iowait sube, no estás limitado por CPU; estás esperando discos.
  2. Presión de memoria: kills OOM y tormentas de swap parecen timeouts aleatorios.
  3. Red y DNS: el upstream puede estar “lento” porque el proxy no puede resolver o conectar rápido.

Decisión: si wa está alto, ve a las comprobaciones de almacenamiento. Si si/so (swap in/out) está activo, ve a memoria. Si ves muchos SYN-SENT o TIME-WAIT, ve a red/puertos.

Tercero: aisla el salto que falla

  1. Prueba desde el proxy hasta el upstream (loopback, socket unix, puerto local, servicio remoto).
  2. Prueba desde el upstream a sus dependencias (BD, cache, almacenamiento de objetos).
  3. Si solo son algunas peticiones: revisa timeouts, límites de workers, colas y pools de conexión.

Decisión: arregla el punto de estrangulamiento más estrecho primero. No “tunes todo” en pánico. Así es como terminas con una segunda caída más creativa que la primera.

Manos a la obra: 12+ tareas con comandos, salidas y decisiones

Estas son las tareas que ejecuto en producción cuando aparecen 502/504 “de la nada”. Cada comando incluye: qué te dice, qué significa la salida y qué hacer a continuación.

Task 1: Verificar si nginx es el componente que emite los errores

cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/28 09:41:12 [error] 3192#3192: *884 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.25, server: example.com, request: "GET /api/orders HTTP/1.1", upstream: "http://127.0.0.1:8000/api/orders", host: "example.com"
2025/12/28 09:41:15 [error] 3192#3192: *901 connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 198.51.100.9, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example.com"

Significado: Dos modos de fallo distintos: uno es un timeout desde un upstream HTTP en 127.0.0.1:8000; el otro es un problema de permisos en un socket unix de PHP-FPM.

Decisión: No trates esto como un solo incidente. Divide el problema: arregla permisos del socket para rutas PHP y los timeouts/rendimiento para el API upstream.

Task 2: Confirmar la configuración activa y los destinos upstream (evita depurar el archivo equivocado)

cr0x@server:~$ sudo nginx -T | sed -n '1,120p'
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

Significado: Estás viendo la configuración en vivo que nginx usará, incluidas las inclusiones. Esto evita el error clásico: editar un archivo que no está cargado.

Decisión: Busca dentro de esta salida tu stanza upstream o las líneas fastcgi_pass, y verifica que coincidan con la realidad (ruta de socket, puerto, hostname).

Task 3: Comprobar si los procesos upstream están vivos y no en bucle de reinicio

cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
     Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
     Active: active (running) since Sun 2025-12-28 09:10:04 UTC; 33min ago
       Docs: man:php-fpm8.3(8)
    Process: 1258 ExecStartPre=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/8.3/fpm/pool.d/www.conf 83 (code=exited, status=0/SUCCESS)
   Main PID: 1269 (php-fpm8.3)
      Tasks: 21 (limit: 18841)
     Memory: 230.5M
        CPU: 1min 42.902s

Significado: php-fpm está en ejecución. Eso no significa que nginx pueda acceder a su socket. Solo significa que el servicio no se ha caído.

Decisión: Si nginx muestra permiso denegado, inspecciona la propiedad/modo del socket y el usuario de nginx (usualmente www-data).

Task 4: Validar permisos del socket unix para PHP-FPM

cr0x@server:~$ ls -l /run/php/php8.3-fpm.sock
srw-rw---- 1 root www-data 0 Dec 28 09:10 /run/php/php8.3-fpm.sock

Significado: Socket propiedad root:www-data con modo 660. nginx corre como www-data por defecto, así que esto debería funcionar.

Decisión: Si ves srw------- root root o similar, arregla listen.owner, listen.group y listen.mode en la config del pool, luego recarga php-fpm.

Task 5: Confirmar el usuario de los workers de nginx (no des por hecho)

cr0x@server:~$ ps -o user,group,pid,cmd -C nginx | head
USER     GROUP      PID CMD
root     root      3121 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data www-data  3192 nginx: worker process
www-data www-data  3193 nginx: worker process

Significado: Los workers corren como www-data, así que la propiedad de grupo del socket debe incluir www-data o los permisos deben permitir acceso.

Decisión: Si los workers corren bajo otro usuario (contenedores, compilaciones personalizadas), alinea los permisos del socket en consecuencia.

Task 6: Encontrar si el puerto upstream está escuchando y quién lo posee

cr0x@server:~$ sudo ss -ltnp | grep -E ':8000|:8080|:9000'
LISTEN 0      4096       127.0.0.1:8000      0.0.0.0:*    users:(("gunicorn",pid=2204,fd=5))
LISTEN 0      4096       127.0.0.1:9000      0.0.0.0:*    users:(("php-fpm8.3",pid=1269,fd=8))

Significado: gunicorn está escuchando en 127.0.0.1:8000. Si nginx sigue haciendo timeouts, gunicorn está vivo pero lento o atascado, o el path está bloqueando dentro de la app.

Decisión: Pasa a logs del backend y chequeos de contención de recursos. Si el puerto no está escuchando, arregla el servicio o su dirección de bind.

Task 7: Reproducir la petición desde el host proxy (evita la capa proxy)

cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1:8000/api/orders
HTTP/1.1 200 OK
Server: gunicorn
Date: Sun, 28 Dec 2025 09:44:01 GMT
Connection: close
Content-Type: application/json

Significado: El endpoint puede responder rápido ahora mismo. Eso sugiere encolamiento intermitente, agotamiento de workers o picos en dependencias downstream, no una mala configuración permanente.

Decisión: Revisa límites de concurrencia y latencia durante la ventana del incidente; no declares victoria porque un curl funcionó una vez.

Task 8: Buscar timeouts upstream y agotamiento de workers en logs del servicio

cr0x@server:~$ sudo journalctl -u gunicorn --since "2025-12-28 09:35" --no-pager | tail -n 30
Dec 28 09:40:58 app1 gunicorn[2204]: [2025-12-28 09:40:58 +0000] [2204] [CRITICAL] WORKER TIMEOUT (pid:2311)
Dec 28 09:40:58 app1 gunicorn[2311]: [2025-12-28 09:40:58 +0000] [2311] [ERROR] Error handling request /api/orders
Dec 28 09:40:59 app1 gunicorn[2204]: [2025-12-28 09:40:59 +0000] [2204] [INFO] Worker exiting (pid: 2311)
Dec 28 09:41:00 app1 gunicorn[2204]: [2025-12-28 09:41:00 +0000] [2204] [INFO] Booting worker with pid: 2418

Significado: Los workers de gunicorn están haciendo timeout y reiniciándose. Nginx lo ve como timeouts/502s dependiendo del timing.

Decisión: No solo aumentes el timeout de gunicorn. Encuentra por qué las peticiones cuelgan: locks en BD, latencia de almacenamiento, deadlocks de hilos, DNS bloqueado, llamadas externas lentas.

Task 9: Comprobar carga del sistema, CPU e iowait durante el evento

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 812344  48216 923400    0    0    24    18  210  390  6  2 90  2  0
 3  1      0 789120  48220 921900    0    0   120  2040  480 1200  8  4 58 30  0
 5  2      0 760112  48220 920100    0    0    80  1990  520 1350 10  5 50 35  0
 4  2      0 748000  48224 918500    0    0    60  2100  510 1300  9  4 52 31  0
 2  1      0 770200  48224 920300    0    0    40   980  360  900  7  3 78 12  0

Significado: iowait (wa) sube a 30–35%. Eso no es un problema web. Es el kernel diciéndote que tus procesos esperan almacenamiento.

Decisión: Ve a comprobaciones de latencia de disco, saturación de sistema de archivos y qué está golpeando el disco (journald, logrotate, backups, checkpoints de BD, antivirus, lo que sea).

Task 10: Identificar qué proceso está causando presión en disco

cr0x@server:~$ sudo iotop -oPa -n 3
Total DISK READ:         1.65 M/s | Total DISK WRITE:        38.90 M/s
TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN  IO>  COMMAND
2210 be/4  postgres    0.00 B/s   22.30 M/s  0.00 %  9.10 % postgres: checkpointer
3140 be/4  root        0.00 B/s    8.40 M/s  0.00 %  2.20 % systemd-journald
987  be/4  www-data    1.20 M/s    0.00 B/s  0.00 %  1.30 % gunicorn: worker [app]

Significado: El checkpointer de Postgres está escribiendo mucho. journald también está escribiendo. Esas escrituras pueden bloquear lecturas dependiendo del almacenamiento.

Decisión: Si esto coincide con los timeouts, revisa la salud de Postgres y la latencia del almacenamiento. Puede que necesites ajustar checkpointing, mover logs a disco más rápido o detener un vecino ruidoso.

Task 11: Comprobar latencia real del dispositivo de bloque y saturación

cr0x@server:~$ sudo iostat -x 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           7.82    0.00    3.21   28.44    0.00   60.53

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz aqu-sz  %util
nvme0n1          32.0   1824.0     0.0    0.0    8.20    57.0     620.0  41280.0     0.0    0.0   42.10    66.6   6.20   98.0

Significado: El dispositivo está al 98% de utilización y write await es 42ms. Eso no está “bien”. Es una cola.

Decisión: Reduce la amplificación de escrituras (logs, checkpoints), verifica que el almacenamiento subyacente no esté siendo estrangulado y confirma que no estás alcanzando límites de IOPS en la nube.

Task 12: Buscar kills OOM y presión de memoria (asesino silencioso)

cr0x@server:~$ sudo journalctl -k --since "2025-12-28 09:00" | grep -i -E 'oom|killed process' | tail -n 20
Dec 28 09:38:11 app1 kernel: Out of memory: Killed process 2311 (gunicorn) total-vm:1452200kB, anon-rss:612400kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:2200kB oom_score_adj:0

Significado: El kernel mató a un worker de gunicorn. Nginx ve que el backend desaparece a mitad de petición: 502s, 504s, o ambos.

Decisión: Detén la hemorragia: reduce concurrencia o huella de memoria, añade RAM, arregla fugas y agrega límites de cgroup para que el “kill” sea predecible (reinicio de servicio) en lugar de caótico.

Task 13: Inspeccionar estados de conexión por agotamiento de puertos o congestión upstream

cr0x@server:~$ ss -s
Total: 3087 (kernel 0)
TCP:   1987 (estab 412, closed 1450, orphaned 0, synrecv 0, timewait 1450/0), ports 0

Transport Total     IP        IPv6
RAW       0         0         0
UDP       12        10        2
TCP       537       509       28
INET      549       519       30
FRAG      0         0         0

Significado: TIME_WAIT es alto. Eso no es automáticamente un problema, pero puede serlo si haces muchas conexiones efímeras upstream y te quedas sin puertos efímeros.

Decisión: Si observas “cannot assign requested address” en logs, cambia a keepalive/pooling y examina net.ipv4.ip_local_port_range y configuraciones de reutilización con cuidado.

Task 14: Confirmar que DNS no esté bloqueando búsquedas upstream

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (ens5)
Current Scopes: DNS
     Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.0.0.2
       DNS Servers: 10.0.0.2 10.0.0.3

Significado: systemd-resolved está en juego. Si el servidor DNS es lento o inaccesible, la resolución de nombres upstream puede bloquear el manejo de peticiones.

Decisión: Si el upstream usa hostnames (no IPs), verifica la velocidad de resolución y el cacheo. Para nginx, considera resolver al inicio con IPs estables o usar la directiva resolver con timeouts.

Task 15: Confirmar que AppArmor no esté bloqueando acceso a sockets/archivos (Ubuntu lo usa mucho)

cr0x@server:~$ sudo dmesg | grep -i apparmor | tail -n 10
[ 8921.224911] audit: type=1400 audit(1766914872.112:188): apparmor="DENIED" operation="connect" profile="/usr/sbin/nginx" name="/run/php/php8.3-fpm.sock" pid=3192 comm="nginx" requested_mask="wr" denied_mask="wr" fsuid=33 ouid=0

Significado: nginx tiene denegado conectar al socket PHP-FPM por AppArmor. Eso parece exactamente “permission denied”, pero chmod no lo arreglará.

Decisión: Ajusta el perfil de AppArmor para permitir esa ruta de socket o alinea la ruta con la esperada. Luego recarga AppArmor y nginx.

Las razones reales detrás de 502/504 repentinos en Ubuntu 24.04

Las caídas “repentinas” suelen ser fallos de humo lento que no viste hasta que el proxy empezó a emitir códigos HTTP honestos.

1) El backend está vivo pero atascado (encolamiento y bloqueo cabeza-de-línea)

La mayoría de los backends web tienen un límite de concurrencia: workers de gunicorn, hijos de PHP-FPM, loop de eventos de Node, pools de hilos de Java. Cuando la concurrencia se satura, las nuevas peticiones se encolan. El proxy espera. Finalmente expira el timeout.

Cómo se ve: el log de errores de nginx dice “upstream timed out”, los logs del backend muestran timeouts de workers, peticiones lentas o nada porque los workers están bloqueados.

Qué hacer: encuentra qué está bloqueando. Los culpables más rápidos: locks en la base de datos, llamadas a APIs externas sin timeouts, I/O de disco lento para plantillas/subidas/sesiones y logging sincrónico bajo presión.

2) La latencia de almacenamiento es el upstream oculto (y castiga a todos por igual)

Si iowait sube, el sistema no está “ocupado”. Está parado. Tus hilos de aplicación esperan lecturas/escrituras de disco: archivos de BD, stores de sesiones, persistencia de cache, escrituras de logs, archivos temporales, carga de paquetes, resets de opcache, lo que sea.

En Ubuntu 24.04 también podrías ver logging más agresivo o valores por defecto distintos respecto a instaladores anteriores. No porque Ubuntu sea malo. Porque tu carga de trabajo lo es.

Qué hacer: prueba la latencia con iostat -x, identifica escritores con iotop, y busca jobs periódicos (backups, logrotate, mantenimiento BD). Arregla reduciendo volumen de escritura, moviendo rutas calientes a almacenamiento más rápido o aumentando IOPS aprovisionadas en discos cloud.

3) Regresiones de sockets/permiso (sockets unix: rápidos, excelentes, a veces quisquillosos)

Los sockets unix son excelentes para proxy local: baja sobrecarga, control de acceso claro. También fallan de formas específicas —usualmente después de una actualización de paquete, deriva de configuración o una nueva unidad service que cambia directorios de runtime.

Cómo se ve: log de errores de nginx: “connect() to unix:/run/… failed (13: Permission denied)” o “No such file or directory.”

Qué hacer: verifica que el socket exista, la propiedad/modo, el usuario de los workers de nginx y las denegaciones de AppArmor. Arregla la config del pool, no el síntoma.

4) Bucles de reinicio de systemd y comportamiento “útil” del watchdog

Ubuntu 24.04 está sin complejos con systemd. Si tu backend se cae, systemd puede reiniciarlo rápido. Eso crea ráfagas de fallo donde nginx ve resets/refusos de conexión.

Cómo se ve: “connection refused” en logs del proxy, journalctl -u muestra reinicios frecuentes, quizá un ExitCode que apunta a errores de config, variables de entorno faltantes o migraciones no ejecutadas.

Qué hacer: detén la tormenta de reinicios el tiempo suficiente para leer logs. Usa systemctl edit para añadir RestartSec y StartLimitIntervalSec sensatos si hace falta, y luego arregla la causa raíz del crash.

5) Bloqueos en resolución DNS y el mito de “es solo un hostname”

Algunos proxies resuelven hostnames upstream al inicio. Otros resuelven periódicamente. Otros dependen del resolvedor del SO y se bloquean cuando este es lento. Si DNS falla, las conexiones upstream se bloquean. Los timeouts siguen.

Cómo se ve: 504s intermitentes, especialmente tras cambios de red. Los logs pueden mostrar host not found in upstream o nada obvio salvo incrementos en tiempos de conexión.

Qué hacer: valida la salud del resolvedor, confirma cacheo, reduce la dependencia de DNS en upstreams críticos o configura resolvers explícitos y timeouts en nginx cuando proceda.

6) Agotamiento de recursos del kernel/red: puertos efímeros, conntrack, descriptores

Cuando sube el tráfico, no solo te quedas sin CPU. Puedes quedarte sin cosas: descriptores de archivos, puertos locales, entradas conntrack, backlog de escucha, cola de accept. El kernel empieza a rechazar peticiones. Nginx lo convierte en 502/504 según dónde falle.

Cómo se ve: muchas TIME_WAIT, errores como “cannot assign requested address”, “too many open files” o advertencias de accept queue. Los usuarios ven timeouts más que errores claros.

Qué hacer: inspecciona estados de conexión, sube límites con precaución, habilita keepalive donde sea seguro y evita crear una conexión TCP nueva por petición como si fuera 2009.

7) Timeouts desalineados entre capas (proxy vs backend vs load balancer)

Los timeouts son un deporte de equipo. Si tu balanceador hace timeout a 60s, nginx a 30s y gunicorn a 120s, verás una procesión de fallos parciales y reintentos que amplifican la carga.

Cómo se ve: el cliente recibe 504 a 30s, el backend sigue trabajando y luego intenta escribir a un socket cerrado. Los logs muestran broken pipes, reset by peer, duraciones largas de petición.

Qué hacer: define un presupuesto de timeout coherente. Luego asegúrate de que los upstreams tengan sus propios timeouts internos para BD y llamadas externas, más cortos que el timeout del proxy.

Una idea para tener en la pared, parafraseada de una voz notable en ops: parafraseado: Todo falla, todo el tiempo — tu trabajo es hacer que la falla sea predecible y visible. — Werner Vogels

Broma #2: Un 504 es la forma que tiene tu servidor de decir “No te ignoro, solo estoy pensando muy intensamente.”

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

1) Síntoma: 502 con “connect() failed (111: Connection refused)”

Causa raíz: el proceso upstream está caído, ligado a una dirección distinta o en bucle de reinicio; estás apuntando nginx al puerto equivocado.

Solución: verifica listeners con ss -ltnp; revisa systemctl status y journalctl -u; corrige la dirección de bind (127.0.0.1 vs 0.0.0.0) y asegura que el servicio arranca correctamente.

2) Síntoma: 502 con “Permission denied” a socket unix

Causa raíz: desajuste de propietario/grupo/modo del socket, o denegación por AppArmor.

Solución: ajusta listen.owner/listen.group/listen.mode del pool de PHP-FPM y alinea el usuario de nginx; si AppArmor deniega, actualiza el perfil para permitir /run/php/php8.3-fpm.sock.

3) Síntoma: 504 “upstream timed out while reading response header”

Causa raíz: el backend acepta conexiones pero no responde rápido — agotamiento de workers, lentitud BD, iowait de almacenamiento o bloqueos externos.

Solución: confirma timeouts de workers del backend; revisa iowait con vmstat/iostat; encuentra dependencias lentas; añade timeouts a llamadas externas; reduce encolamiento escalando u optimizando.

4) Síntoma: errores aumentan durante ventana de logrotate/backup

Causa raíz: saturación de almacenamiento, compresión de logs, snapshotting o backups consumiendo I/O y CPU. A menudo oculto en discos cloud compartidos.

Solución: reprograma jobs pesados, limita I/O, mueve logs/BD a volúmenes separados, ajusta checkpoints de BD y asegúrate de que los backups sean incrementales y no “copiar todo cada noche”.

5) Síntoma: solo algunos endpoints 504, otros bien

Causa raíz: un path de código específico desencadena query BD lenta, contención de locks, lecturas de archivos grandes o llamadas sincrónicas a dependencias.

Solución: instrumenta latencia por endpoint; examina logs de queries lentas; añade índices o caching; transmite respuestas grandes; aisla trabajos pesados fuera del path de petición.

6) Síntoma: 502 después de actualizar paquetes (refresco Ubuntu 24.04)

Causa raíz: cambios en unidades service, saltos de versión de PHP, cambios en rutas de socket, config incompatible o módulos deshabilitados.

Solución: revalida upstream/fastcgi paths en nginx; ejecuta nginx -T; revisa el servicio php-fpm y rutas de socket; confirma sitios y módulos habilitados.

7) Síntoma: 504 solo detrás de un proxy/load balancer corporativo

Causa raíz: desajuste de timeout en el borde, health checks fallando o demoras en el handshake TLS. A veces el LB reintenta y duplica la carga.

Solución: alinea presupuestos de timeout entre LB/nginx/app; valida que el endpoint de health check sea rápido y ligero en dependencias; asegúrate de que keepalive y TLS estén configurados sensatamente.

Tres mini-historias corporativas (anonimizadas, plausibles, técnicamente precisas)

Mini-historia 1: Incidente causado por una suposición errónea

Tuvieron una configuración ordenada: nginx en Ubuntu, PHP-FPM en el mismo host, socket unix entre ellos. Había funcionado durante años. Luego reconstruyeron la imagen de VM para Ubuntu 24.04 y el sitio empezó a tirar 502s a los minutos del primer tráfico.

El on-call hizo lo que los on-call hacen: reinició nginx. Los errores bajaron, luego volvieron. Alguien culpó “un mal deploy”, pero no había cambiado el código. Era cierto. Tampoco importaba.

La suposición errónea fue sutil: asumieron que los permisos de archivo eran iguales que antes. En la nueva imagen, un paso de hardening había cambiado el usuario master/worker de nginx y endurecido perfiles de AppArmor. El socket parecía correcto a simple vista. La propiedad estaba bien. El modo estaba bien. Pero AppArmor denegaba la operación connect.

Cuando vieron en dmesg la denegación, la solución fue vergonzantemente sencilla: permitir la ruta del socket en el perfil de nginx (o alinear el socket a la ruta esperada). Los 502s desaparecieron al instante.

El postmortem no fue “AppArmor es malo.” Fue “asumimos que un control de seguridad no afectaría al runtime.” Esa suposición es la que te hace recibir páginas.

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

Otro equipo quería reducir tiempos de respuesta y CPU. Activaron logging de acceso más agresivo y añadieron campos de timing por petición para graficar “dónde va el tiempo”. Funcionó. Los dashboards quedaron más bonitos. El ego creció en consecuencia.

Luego el tráfico subió y empezaron a aparecer 504s en horas punta. El equipo de la app juró que no cambió nada. El equipo de BD juró que no era culpa suya. Los logs del proxy mostraban timeouts upstream, pero las métricas del backend parecían “más o menos normales”.

El culpable: la optimización creó un nuevo path caliente. El logging se convirtió en escrituras síncronas al disco en un volumen cloud con IOPS limitadas. Bajo carga, la utilización de disco tocó techo. El iowait subió. Los threads de worker se bloquearon. El proxy esperó y luego hizo timeout. Clásico 504.

Lo arreglaron haciendo el logging menos caro: buffering, menos verbosidad y moviendo logging pesado fuera del disco primario. La corrección más significativa fue organizativa: tratar la observabilidad como carga de producción, no como caramelo gratis.

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

Una empresa con la que trabajé tenía una política que todos se burlaban: “No reiniciar servicios hasta haber capturado 10 minutos de evidencia.” La gente se quejaba de que ralentizaba la respuesta a incidentes. La dirección lo mantuvo porque ya los habían quemado antes.

Una tarde aparecieron 502s en un cluster web multi-tenant. La opción fácil era rebotar todo. No lo hicieron. Capturaron logs de error de nginx, journals del backend y un snapshot rápido de iostat/vmstat de dos nodos afectados.

La evidencia mostró un patrón: picos de iowait exactamente cuando corría un job programado. Ese job rotaba y comprimía enormes logs localmente, chocando con un burst de checkpoints de la BD. El subsistema de almacenamiento no podía seguir, y las peticiones upstream se apilaron detrás.

Como tenían datos, el arreglo fue rápido y quirúrgico: reprogramar y limitar el job, ajustar checkpointing de BD y dividir logs a otro volumen. Nada de “creo que”. Nada de adivinanzas heroicas. Solo disciplina aburrida que previno una recurrencia.

Listas de verificación / plan paso a paso

Checklist A: Detener la hemorragia sin borrar evidencia

  1. Captura las últimas 200 líneas de los logs de error y access del proxy para la ventana fallida.
  2. Captura logs del servicio backend para las mismas marcas de tiempo.
  3. Captura vmstat 1 5 y iostat -x 1 3 una vez, desde un nodo afectado.
  4. Si el sitio se está derritiendo: reduce carga (rate limits, deshabilita endpoints no críticos temporalmente, reduce features costosas) antes de reiniciar todo.

Checklist B: Confirmar el salto que falla

  1. Desde el host proxy, curl al upstream directamente (loopback/puerto/socket).
  2. Desde el host upstream, prueba dependencias (BD/cache) con timeouts cortos.
  3. Revisa estados de conexión (ss -s) y descriptores de archivos si sospechas.
  4. Confirma la velocidad de resolución DNS si el upstream usa hostnames.

Checklist C: Arreglar de forma segura (el cambio mínimo que restaura servicio)

  1. Si es permiso/socket/AppArmor: arregla la política/config; recarga servicios; verifica con una única petición.
  2. Si es agotamiento de workers: reduce tráfico, aumenta capacidad de workers con cuidado y arregla la dependencia lenta o la llamada bloqueante.
  3. Si es saturación de almacenamiento: detén el escritor ruidoso, reprograma jobs o mueve I/O caliente a un disco mejor.
  4. Alinea timeouts entre LB → nginx → app → dependencias para no crear tormentas de reintentos.

Checklist D: Prevenir recurrencia

  1. Añade alertas sobre iowait, %util del disco y profundidad de cola del backend — no solo tasa de errores HTTP.
  2. Rastrea percentiles de tiempo de respuesta upstream y cantidad de peticiones con timeout.
  3. Documenta la topología upstream en vivo (puertos, sockets, servicios) y mantenla actualizada.
  4. Haz pruebas de carga tras upgrades del SO y después de “mejoras de observabilidad”.

Datos interesantes y contexto histórico

  • HTTP 502 y 504 son errores de gateway: fueron diseñados para intermediarios — proxies, gateways y load balancers — que se quejan de upstreams, no de sí mismos.
  • Nginx se convirtió en proxy inverso por defecto en gran parte porque manejaba alta concurrencia con poca memoria comparado con modelos antiguos por proceso por conexión.
  • Los sockets de dominio unix existen desde antes de los stacks web modernos y siguen siendo uno de los mecanismos IPC más simples y rápidos en Linux para servicios locales.
  • systemd normalizó “restart on failure” en flotas Linux; mejoró disponibilidad pero también facilitó ocultar bucles de crash si no miras los logs.
  • AppArmor es un control de seguridad mainstream en Ubuntu; a menudo explica “permission denied” cuando permisos de filesystem parecen correctos.
  • Las tormentas de TIME_WAIT son antiguas: han afectado a servicios de alto tráfico desde los primeros días de arquitecturas web intensivas en TCP, especialmente sin keepalive.
  • Los discos en la nube frecuentemente tienen techos de rendimiento (IOPS/throughput). Los picos de latencia pueden parecer “repentinos” cuando cruzas un umbral.
  • Timeouts desalineados crean amplificación por reintentos: una capa hace timeout, reintentos aumentan carga y el sistema colapsa más rápido que si hubieras esperado.

Preguntas frecuentes (FAQ)

1) ¿Debo reiniciar nginx cuando veo 502/504?

Sólo después de haber capturado logs y señales básicas del sistema. Reiniciar puede limpiar temporalmente workers o sockets atascados, pero también borra pistas. Si es un cuello de botella real (BD/almacenamiento), los errores volverán.

2) ¿Cuál es la forma más rápida de distinguir la causa de 502 vs 504?

Lee la línea del log de errores de nginx. “Connection refused” apunta a alcanzabilidad/servicio caído. “Upstream timed out” apunta a upstream lento/colgado o dependencia. “Permission denied” apunta a permisos de socket o AppArmor.

3) ¿Por qué veo 504 pero los logs del backend muestran respuestas 200?

Porque el backend siguió trabajando después de que el proxy se rindió. El timeout del proxy expiró, cerró la conexión y el backend más tarde escribió la respuesta a nadie. Alinea timeouts y añade timeouts internos para dependencias.

4) ¿Puede el almacenamiento realmente causar 504 aunque la CPU esté baja?

Sí. CPU baja con iowait alta es una firma clásica. Tus hilos están bloqueados en el kernel esperando I/O de disco. El proxy también espera hasta que hace timeout.

5) ¿Por qué Ubuntu 24.04 “de repente” empezó a hacer esto?

Ubuntu 24.04 normalmente no es la causa; la actualización a menudo cambia versiones y valores por defecto (versiones php-fpm, unidades service, perfiles de seguridad). Esos cambios exponen fragilidad existente: límites ajustados, rutas de socket frágiles o margen de almacenamiento que nunca fue real.

6) ¿Cómo sé si AppArmor está implicado?

Busca mensajes AppArmor DENIED en dmesg o en el journal del kernel. Si ves denegaciones que involucran nginx, php-fpm sockets o archivos backend, trátalo como un problema de política, no de chmod.

7) ¿Aumentar nginx proxy_read_timeout es una solución real?

A veces, pero suele ser un parche, no una solución. Aumenta timeouts sólo cuando el trabajo es legítimamente largo y controlado (exports, reports), y sólo si el backend puede manejarlo sin retener workers indefinidamente.

8) ¿Cómo distingo sobrecarga del backend de una dependencia lenta puntual?

La sobrecarga de backend muestra aumento de tiempo en cola, agotamiento de workers y latencias generales. Una dependencia lenta muestra endpoints específicos fallando, correlacionados con locks BD, misses de cache o latencia de APIs externas. Usa métricas por endpoint y correlaciona logs por request ID si las tienes.

9) ¿Por qué 502/504 aparecen por ráfagas?

Porque el sistema oscila: el tráfico lo empuja a saturación, timeouts provocan reintentos, workers reinician, caches se calientan/enfrían, colas de almacenamiento se vacían y obtienes un patrón de fallo repetido. Arregla el cuello de botella y la oscilación se detiene.

Siguientes pasos que deberías tomar

Si estás en medio de un incidente: toma la guía rápida de diagnóstico y ejecútala de principio a fin. No “hagas un poco de todo”. Recoge evidencia, identifica el salto que falla y arregla la restricción más estrecha primero.

Si ya pasaste el incidente y quieres evitar que vuelva a ocurrir:

  • Haz el almacenamiento visible: alerta sobre %util del disco, await e iowait. Los timeouts web suelen comenzar como latencia de disco.
  • Alinea timeouts: asegura LB, nginx, servidor de aplicaciones y llamadas a BD con un presupuesto de timeout coherente.
  • Codifica expectativas de sockets y seguridad: si dependes de sockets unix, documenta rutas y permisos; incluye comprobaciones de AppArmor en tu validación de rollout.
  • Reduce trabajos sorpresa: encuentra backups/logrotate/tareas periódicas y asegúrate de que no compartan la misma ruta de I/O que la latencia de peticiones.
  • Practica ops basadas en evidencia: el hábito aburrido de capturar logs y estado del sistema antes de reinicios es lo que evita folklore de 504 recurrente.

502/504 no son misterios. Son el proxy diciéndote exactamente dónde mirar. Escúchalo, recoge evidencia y arregla el sistema en vez del síntoma.

← Anterior
Supervisión DNS: Alertas antes de que los usuarios las noten (comprobaciones simples y efectivas)
Siguiente →
Fallas SPF de correo: los 5 errores en registros que rompen la entrega (y cómo solucionarlos)

Deja un comentario