Solucionar el error nginx 502 Bad Gateway: causas y soluciones
502 Bad Gateway significa que nginx no ha recibido una respuesta válida del backend. Las cinco causas más frecuentes, la línea correcta en el log de errores y cómo demostrar que el arreglo funciona de verdad.
Qué significa realmente "502 Bad Gateway"
Un 502 no viene de tu aplicación, viene de nginx. nginx aceptó la petición, la pasó a un backend (PHP-FPM, Node, Python, otro servidor web) y de allí no recibió ninguna respuesta utilizable. Por eso la página de error no dice nada aprovechable.
Distinguirlo de los códigos vecinos ahorra mucho tiempo cuando la cosa se pone seria:
- 500 Internal Server Error: el backend respondió, pero la respuesta fue un error. La causa está en el código de la aplicación. Hay que leer el log de la aplicación, no el de nginx.
- 502 Bad Gateway: la conexión con el backend no llegó a establecerse, o se cortó antes de que hubiera una respuesta completa.
- 504 Gateway Time-out: la conexión estaba en pie, el backend simplemente tardó demasiado en decir algo y nginx perdió la paciencia.
Esta distinción es la palanca más importante cuando se agotan los tiempos de espera, porque la misma página lenta aparece unas veces como 502 y otras como 504, según quién corte primero. Más abajo lo vemos en detalle.
La situación de los paquetes en Debian 13, Debian 12, Ubuntu 24.04 y Ubuntu 22.04
nginx se comporta igual ante los errores 502 en los cuatro sistemas y las directivas se llaman exactamente igual. Las diferencias están casi por completo del lado de PHP, y de ahí salen justamente la mayoría de los 502 después de cambiar de distribución.
| Sistema | nginx | PHP | Nombre del servicio | Socket |
| Debian 13 (Trixie) | 1.26.3 | 8.4 | php8.4-fpm | /run/php/php8.4-fpm.sock |
| Debian 12 (Bookworm) | 1.22.1 | 8.2 | php8.2-fpm | /run/php/php8.2-fpm.sock |
| Ubuntu 24.04 LTS | 1.24.0 | 8.3 | php8.3-fpm | /run/php/php8.3-fpm.sock |
| Ubuntu 22.04 LTS | 1.18.0 | 8.1 | php8.1-fpm | /run/php/php8.1-fpm.sock |
En todos los comandos que siguen, sustituye el número de versión por el de tu sistema. Todos los ejemplos parten de una shell de root; si no, antepón sudo. Qué versión está instalada se ve echando un vistazo a los binarios de FPM, incluso aunque el servicio no llegue a arrancar:
ls /usr/sbin/php-fpm*
ls /etc/php/
Primero el log de errores: encontrar la línea correcta
El fallo más habitual al diagnosticar es buscar en el log equivocado. nginx tiene un log de errores global y, a menudo, uno propio por cada host virtual. Qué archivo se aplica está en la configuración:
grep -Rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-enabled/
Fíjate en la -R mayúscula. En Debian y Ubuntu, dentro de /etc/nginx/sites-enabled/ solo hay symlinks hacia sites-available, y GNU grep no sigue ningún symlink durante el descenso recursivo cuando usas la -r minúscula. Con -rn obtienes por tanto únicamente las coincidencias de nginx.conf, mientras que la línea error_log propia del vhost queda invisible: justo la que se busca en un caso de 502, porque el log global no contiene el error de FastCGI en cuanto el vhost desvía la salida. Si prefieres quedarte con -r, grepea directamente los directorios de origen:
grep -rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-available/ /etc/nginx/conf.d/
Sin una indicación propia en el bloque server, todo acaba en /var/log/nginx/error.log. El camino más fiable hasta la línea correcta pasa por una captura en vivo: mantén el log abierto en una terminal, lanza la petición en una segunda y mira las líneas que aparecen nuevas en ese momento.
tail -f /var/log/nginx/error.log
La alternativa es filtrar por la marca de tiempo. nginx escribe la hora local en el formato 2026/07/26 09:14:22, no en UTC. Compararla con un reloj de otra zona horaria sale mal una y otra vez.
Una línea de 502 sigue siempre el mismo patrón. Ejemplo:
2026/07/26 09:14:22 [error] 812#812: *3 connect() to unix:/run/php/php8.2-fpm.sock
failed (2: No such file or directory) while connecting to upstream,
client: 203.0.113.7, server: example.com,
request: "GET /index.php HTTP/1.1",
upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
Cuatro elementos concentran toda la información:
- La llamada al sistema:
connect(),recv(),send().connect()significa que nunca llegó a establecerse una conexión.recv()significa que la conexión estaba en pie y luego se cortó. - El número de error entre paréntesis, véase la tabla de más abajo. Ese es el diagnóstico propiamente dicho.
- La fase:
while connecting to upstreamfrente awhile reading response header from upstream. Lo primero es un problema de accesibilidad, lo segundo un problema de ejecución o una caída del proceso. - El campo
upstream:. Ahí figura la ruta o la dirección que nginx ha usado realmente. No lo que supones que pone en la configuración, sino lo que está cargado y activo.
| Mensaje | Significado | Apartado |
| 2: No such file or directory | El archivo de socket no existe | Servicio caído o ruta incorrecta |
| 13: Permission denied | El socket existe, pero nginx no puede acceder a él | Permisos |
| 111: Connection refused | Nada escucha en esa dirección y ese puerto | Backend inaccesible |
| 110: Connection timed out | Ninguna respuesta dentro del plazo | Tiempo de espera agotado |
| 104: Connection reset by peer | El proceso del backend murió en mitad de la petición | Caídas y límites |
| 11: Resource temporarily unavailable | La cola del socket está llena | Caídas y límites |
En nginx, la línea de 13: Permission denied suele llevar el nivel [crit] en lugar de [error]. Quien filtra solo por [error] se la pierde. Es mejor filtrar por el texto:
grep -n "upstream" /var/log/nginx/error.log
La otra mitad de la verdad está en el log de PHP-FPM, por defecto en /var/log/php8.2-fpm.log. En caídas y límites ahí figura el motivo, mientras que nginx solo ve el síntoma.
Causa 1: PHP-FPM no está en ejecución
El clásico número de error 2. Comprueba primero el estado del servicio:
systemctl is-active php8.2-fpm
systemctl status php8.2-fpm --no-pager -l
is-active responde con una sola palabra, suficiente para un script. Si aparece inactive o failed, saca el motivo del journal, y hazlo con una ventana de tiempo en lugar de con las últimas diez líneas:
journalctl -u php8.2-fpm --since "30 min ago" --no-pager
Muy a menudo la causa es una configuración de pool rota que quedó ahí tras un reload. FPM trae su propia prueba de sintaxis, que se ejecuta sin reiniciar:
php-fpm8.2 -t
Errores de arranque típicos, literales, y qué significan:
ERROR: [pool www] cannot get uid for user 'webuser': el usuario del sistema indicado enuser =ya no existe, por ejemplo tras una migración.ERROR: unable to bind listening socket for address '/run/php/php8.2-fpm.sock': No such file or directory (2): falta el directorio/run/php. Está en un tmpfs y se crea al arrancar el servicio. Quien apuntalistena una ruta fuera de ahí tiene que encargarse de que ese directorio se cree.ERROR: An another FPM instance seems to already listen on ...: un proceso de un reinicio fallido sigue colgado del socket.
Cómo saber que está realmente resuelto: no porque systemctl restart haya terminado sin mostrar nada. Un master de FPM arranca también cuando ni un solo proceso de trabajo es capaz de aceptar peticiones. Lo concluyente es que el socket sea visible en el sistema y que FPM responda en él.
ss -lx | grep php
Para la prueba de respuesta de verdad, activa en /etc/php/8.2/fpm/pool.d/www.conf la línea ping.path = /ping, recarga FPM y consulta el socket directamente, saltándote por completo a nginx:
apt-get install -y libfcgi-bin
SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /run/php/php8.2-fpm.sock
Si vuelve pong, el lado de PHP está en orden y el fallo está entre nginx y el socket. Si no vuelve nada, ni siquiera hace falta que sigas buscando en nginx.
Causa 2: ruta de socket incorrecta
En Debian y Ubuntu esta es, con diferencia, la causa más frecuente: el socket lleva el número de versión de PHP en el nombre, la configuración de nginx cablea ese nombre de forma fija, y una actualización de distribución separa ambas cosas.
En concreto: una actualización de Debian 12 a Debian 13 sube PHP de 8.2 a 8.4. El socket antiguo /run/php/php8.2-fpm.sock desaparece, pero en el archivo del vhost sigue estando. El resultado es un 502 en absolutamente todas las páginas PHP, inmediatamente después del reinicio. Lo mismo ocurre al pasar de Ubuntu 22.04 a 24.04 (de 8.1 a 8.3).
Hay una segunda trampa en la configuración de ejemplo que viene incluida. En /etc/nginx/sites-available/default hay un bloque comentado cuyo fastcgi_pass apunta a una versión de PHP que dejó de ser actual hace años. Quien se limita a descomentar esas líneas acaba de crear el 502 él mismo.
Compara los dos lados. Lo que nginx quiere usar:
grep -Rn "fastcgi_pass" /etc/nginx/
Lo que FPM ofrece realmente:
grep -n "^listen *=" /etc/php/*/fpm/pool.d/*.conf
El añadido *= en el patrón de búsqueda es intencionado: exige detrás de listen cualquier cantidad de espacios y después un signo igual. Un simple ^listen coincide también con listen.owner, listen.group y listen.mode, y la línea que buscas se pierde entre las demás coincidencias. En cambio, el comodín /etc/php/*/ está bien así, porque cubre cualquier versión de PHP instalada. Y lo que existe en el sistema en marcha:
ls -l /run/php/
Las tres salidas tienen que mostrar la misma ruta. Importante: usa grep -R sobre /etc/nginx/ y no solo sobre el único archivo del que sospechas. Los fragmentos incorporados mediante include son un escondite muy socorrido, igual que los archivos viejos en sites-available que siguen activos por un symlink olvidado en sites-enabled. También aquí vale lo mismo: solo la -R mayúscula sigue esos symlinks y te enseña así qué archivo está realmente activo.
ls -l /etc/nginx/sites-enabled/
Antes de cambiar nada, haz una copia. Cuesta dos segundos y, en caso de duda, te ahorra una restauración desde el backup:
mkdir -p /root/backups
cp -a /etc/nginx/sites-available/default /root/backups/default.bak
Si vas a intervenir varias veces, es mejor añadir una marca de tiempo (default.bak.$(date +%F-%H%M)), porque cp -a sobrescribe sin avisar un .bak que ya exista.
Después de corregir, siempre primero probar y luego cargar. reload en lugar de restart, para que no se corten las conexiones existentes:
nginx -t
systemctl reload nginx
Ten en cuenta que nginx -t comprueba únicamente la sintaxis. Una ruta de socket que no existe cuenta como configuración perfectamente válida. Por eso un syntax is ok en verde no demuestra que el 502 haya desaparecido.
Causa 3: permisos del socket
Número de error 13. El socket está ahí, lo que pasa es que nginx no puede abrirlo. En Debian y Ubuntu nginx corre como usuario www-data, y el pool estándar de FPM crea el socket en consecuencia. Esto se ve en el archivo del pool:
grep -n "listen.owner\|listen.group\|listen.mode" /etc/php/*/fpm/pool.d/*.conf
Con listen.owner = www-data, listen.group = www-data y modo 0660, la combinación funciona sin que haya que hacer nada. La cosa puede torcerse en tres situaciones:
- Un pool propio por proyecto. Si
userygroupse ponen a un usuario del proyecto,listen.grouptiene que seguir siendo un grupo al que nginx pertenezca. Lo habitual eslisten.owner = usuarioproyectojunto conlisten.group = www-data. - Socket fuera de /run. No basta con que el archivo de socket sea accesible: cada directorio del camino hasta él necesita permiso de ejecución para nginx. Un socket dentro de un directorio personal con modo 0700 no es accesible para nadie salvo para su propietario.
- nginx con el
usercambiado en/etc/nginx/nginx.conf.
La sospecha se confirma sin adivinar nada: intenta el acceso exactamente con el usuario que lo necesita en producción:
id www-data
sudo -u www-data test -w /run/php/php8.2-fpm.sock && echo "acceso disponible" || echo "sin acceso"
Aún más concluyente es la llamada a cgi-fcgi del apartado anterior, también con sudo -u www-data delante. Si FPM responde como root pero no como www-data, el diagnóstico es inequívoco.
Fija los valores en el archivo del pool, no con chmod sobre el archivo de socket. Un chmod 666 aguanta exactamente hasta el siguiente reinicio de FPM: entonces FPM vuelve a crear el socket con los permisos configurados y el error está de vuelta, normalmente en el peor momento posible.
En Debian y Ubuntu, AppArmor está activo. El perfil para nginx que viene incluido no se aplica de forma obligatoria en el estado de fábrica, pero puede haberse activado a través de plantillas de bastionado. Si un mensaje del tipo 13 persiste pese a tener los permisos correctos, merece la pena mirar aa-status y journalctl -k | grep DENIED.
Causa 4: tiempo de espera agotado en peticiones largas
Aquí se separa el método limpio de la adivinanza, porque el simple vencimiento del plazo de nginx produce un 504, no un 502. Cuando una petición larga acaba en 502, casi siempre ha sido PHP-FPM el que ha eliminado antes el proceso de trabajo, y nginx solo vio una conexión cortada. En el log aparece entonces, de forma típica:
recv() failed (104: Connection reset by peer) while reading response header from upstream
Tres plazos actúan a la vez, y su orden decide el código de estado:
max_execution_timeenphp.ini, 30 segundos por defecto en modo FPM. Cuenta solo el tiempo de ejecución del script. La espera dentro de llamadas al sistema, por ejemplo en una consulta de base de datos colgada, no suma en Linux. Por eso este valor no salva la situación justo en el problema en el que uno esperaría que lo hiciera.request_terminate_timeouten el archivo del pool, desactivado en el estado de fábrica. Termina el proceso de trabajo por las bravas, sin importar de qué esté colgado. Ese es el valor que produce los 502.fastcgi_read_timeouten nginx, 60 segundos por defecto. Si vence, hay un 504.
El orden útil es creciente de dentro hacia fuera, para que actúe siempre primero la capa que todavía puede generar un mensaje de error comprensible. Por ejemplo 60, luego 75 y luego 90 segundos. Ordenado al revés, obtienes 502 en lugar de errores de PHP legibles.
grep -rn "request_terminate_timeout" /etc/php/*/fpm/pool.d/*.conf
Lo que FPM ha eliminado figura en su propio log en texto claro:
WARNING: [pool www] child 1234, script '/var/www/html/import.php'
(request: "POST /import.php") execution timed out (76.271849 sec), terminating
Antes de subir los plazos, averigua adónde se va el tiempo. FPM trae para eso un log propio que, al superarse el umbral, escribe una pila de llamadas de PHP completa. Se activa en el archivo del pool:
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s
Tras un systemctl reload php8.2-fpm, en la siguiente petición lenta aparecerá ahí la función que se queda colgada, con su número de línea. En la práctica, en cuatro de cada cinco casos se trata de una consulta de base de datos sin índice o de una llamada a una API externa sin plazo propio. Subir los plazos solo alarga entonces el tiempo hasta el error y, además, mantiene bloqueados procesos de trabajo.
Causa 5: backend inaccesible
Afecta a cualquier backend al que se llegue por TCP: FPM en el puerto 9000, una aplicación Node, un servicio Java, un container. El mensaje guía es el número de error 111.
connect() to 127.0.0.1:3000 failed (111: Connection refused) while connecting to upstream
Comprueba primero si hay algo escuchando y, sobre todo, en qué:
ss -ltnp
Este comando lista únicamente sockets TCP, y de ahí nace un malentendido muy extendido: un pool de PHP-FPM en su estado de fábrica escucha en un socket Unix bajo /run/php/ y no aparece en absoluto en esa lista, aunque esté funcionando perfectamente. Solo se hace visible así:
ss -lxn | grep php-fpm
ls -l /run/php/
Para FPM, por tanto, ss -ltnp solo resulta concluyente si el pool se ha pasado a TCP a propósito con listen = 127.0.0.1:9000. En cambio, para backends Node, Java o en container es justo el comando adecuado.
Tres puntos donde se tropieza y que rara vez aparecen en los tutoriales:
- localhost resuelve primero a ::1. Si en nginx pone
proxy_pass http://localhost:3000;pero la aplicación solo escucha en127.0.0.1, nginx prueba la dirección IPv6 y recibe "Connection refused". El servicio funciona, el puerto está abierto y aun así sale un 502. Solución: escribir127.0.0.1de forma explícita en nginx, o hacer que la aplicación escuche en ambas familias de direcciones. - nginx resuelve los nombres una sola vez, al cargar. Si en
proxy_passhay un nombre de host, nginx se queda con la dirección. Si el backend cambia de dirección IP, por ejemplo un container recién arrancado, las peticiones se pierden en el vacío hasta el siguientereload. - Firewall en el camino de vuelta. Con un backend en otro servidor aparece
113: No route to hosto un tiempo de espera agotado en lugar de "Connection refused". Compruébalo conufw statusy con una prueba de conexión directa desde el servidor de nginx.
Si se usa un bloque upstream con varios destinos, aparece además un mensaje propio:
no live upstreams while connecting to upstream
Significa que nginx ha sacado de circulación todos los destinos durante fail_timeout, después de varios intentos fallidos. Incluso una vez reparado el backend, hay que esperar a que venza ese plazo para que vuelvan a pasar peticiones. Un systemctl reload nginx restablece el estado de inmediato.
El 502 que no encaja en ninguna de las cinco causas
Dos casos parecen una caída sin serlo, y por eso cuestan bastante más tiempo de lo normal.
Cabecera de respuesta demasiado grande. La aplicación funciona perfectamente, solo algunas peticiones sueltas devuelven 502:
upstream sent too big header while reading response header from upstream
El detonante son cookies grandes o datos de sesión en las cabeceras: el búfer de nginx se queda corto. En el bloque server o location:
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 64k;
Con proxy_pass, las directivas se llaman proxy_buffer_size y proxy_buffers. Lo típico es que solo se vean afectados los usuarios con la sesión iniciada, mientras que la página de inicio carga sin problema.
Se acabaron los procesos de trabajo. Bajo carga aparece en el log de FPM:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Las peticiones nuevas esperan entonces en la cola del socket. Si esa también se llena, nginx informa de 11: Resource temporarily unavailable. Antes de subir pm.max_children, echa un cálculo rápido: la RAM disponible dividida entre el consumo real de un proceso de trabajo. Un valor demasiado alto cambia los 502 por un estado del sistema en el que se agota la memoria, y eso golpea también a la base de datos.
Caídas. Las líneas con exited on signal 11 (SIGSEGV) apuntan a una extensión de PHP defectuosa, con frecuencia tras un cambio de versión de PHP en el que han quedado módulos de la versión anterior.
Cuando la intervención no sirve de nada: el camino de vuelta
Dos reglas mantienen el daño acotado. Primera: un cambio detrás de otro, con copia del archivo original en /root/backups, nunca dentro del directorio web. Segunda: verificar después de cada paso, en lugar de cambiar tres cosas a la vez y no saber luego cuál de ellas ayudó.
Si nginx se cae del todo tras un cambio, restaura la copia y recarga:
cp -a /root/backups/default.bak /etc/nginx/sites-available/default
nginx -t
systemctl reload nginx
Si nginx ya no arranca tras un restart, systemctl status nginx rara vez dice lo suficiente. Más informativo:
journalctl -u nginx --since "10 min ago" --no-pager
El motivo más frecuente de un restart fallido con una configuración sintácticamente impecable es un puerto 80 o 443 ocupado, casi siempre por un proceso de la ejecución anterior. ss -ltnp | grep ':80' muestra al culpable.
Cómo saber que está realmente resuelto
Un comando sin mensaje de error no demuestra nada. systemctl reload también se queda callado cuando en el fondo no ha cambiado nada, y nginx -t solo comprueba la sintaxis. Estas cuatro pruebas sí son sólidas:
- Consultar el código de estado directamente en el servidor, para que ni una caché ni un servicio situado por delante falseen el resultado:
Lo esperado es un 200, no un 502. Con varios hosts virtuales, indica el nombre:curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1/curl -H "Host: example.com" ... - El log de errores se queda en silencio. Vacíalo antes de la prueba con
truncate -s 0 /var/log/nginx/error.log, lanza varias peticiones y vuelve a mirar dentro. Un archivo vacío es la prueba de verdad. - FPM responde en el socket, saltándose a nginx, con
cgi-fcgiy comowww-data. Así resuelves la cuestión de los permisos y la de la ruta en un solo paso. - Un reinicio no cambia nada. El punto más importante y el que más se salta la gente. Muchas medidas de urgencia (permisos puestos a mano, directorios creados manualmente bajo
/run, un servicio arrancado pero no habilitado) no sobreviven a un reinicio. Compruebasystemctl is-enabled php8.2-fpm nginxy reinicia el servidor una vez de forma controlada, mientras todavía estás mirando, en lugar de dejarlo para la siguiente ventana de mantenimiento.
En los servidores root KVM y en los servidores dedicados de KernelHost, ese reinicio lo haces desde el área de cliente, con acceso a consola incluido, incluso cuando el servicio web no está accesible en ese momento. Los servidores están en el centro de datos maincubes de Frankfurt am Main (TÜV TIER3+), conectados a una red propia con protección DDoS. Para seguir leyendo: solucionar el error nginx 504 Gateway Time-out y calcular correctamente pm.max_children en PHP-FPM.
Lista de comprobación rápida para una emergencia
tail -f /var/log/nginx/error.log, lanzar la petición, anotar el número de error.- Número 2 o 111: comprobar si el servicio corre y si la ruta es correcta.
ss -lx | grep phpfrente agrep -Rn "fastcgi_pass" /etc/nginx/. - Número 13: permisos en el archivo del pool, no con
chmod. - Número 104 o 110: leer el log de FPM y el
slowlog, y solo después hablar de plazos. - Mensaje "too big header": aumentar los tamaños de búfer.
- Después del arreglo: vaciar el log, volver a probar y reiniciar el servidor una vez.
Preguntas frecuentes
¿Por qué veo un 502 y no un 504, si la página simplemente va lenta?
Tras actualizar a Debian 13, todas las páginas PHP dan 502. ¿Qué tengo que cambiar?
¿Basta con 'nginx -t' como prueba de que el fallo está resuelto?
¿Cómo encuentro en el log de errores la línea que corresponde a mi 502?
El 502 solo aparece con usuarios que han iniciado sesión, la página de inicio carga con normalidad. ¿A qué se debe?
He corregido los permisos del socket con chmod y, tras un reinicio, el error ha vuelto. ¿Por qué?
2026 KernelHost GmbH. Todos los derechos reservados. Esta guía está protegida por derechos de autor. Su publicación en otros sitios web, aunque sea de forma parcial o modificada, no está permitida sin nuestro consentimiento por escrito. Las citas con indicación de la fuente y un enlace son muy bienvenidas.

