Tutoriales de VPS
Cómo solucionar 502 / 504 Bad Gateway en un VPS (2026): lista de comprobación de Nginx, PHP-FPM, Gunicorn y tiempos de espera del upstream
Cuando un sitio web en un VPS muestra de repente 502 Bad Gateway o 504 Gateway Timeout, normalmente no es un problema de dominio, sino que Nginx no puede conectar con el backend, PHP-FPM/Gunicorn se ha caído, los permisos del socket Unix son incorrectos, el pool de procesos está agotado o el upstream tarda demasiado en responder. Este artículo diagnostica capa por capa mediante logs, puertos, sockets, estado de los servicios, tiempos de espera y uso de recursos.

502 y 504 son los dos errores más comunes al alojar sitios web en un VPS, y también los que más pánico suelen causar.
Abres el sitio web en el navegador y solo ves:
502 Bad Gateway
504 Gateway Timeout
Muchas personas piensan de inmediato que el dominio está roto, que el certificado está roto o que el servidor ha sido bloqueado. En realidad, la mayoría de las veces no es nada de eso. 502/504 normalmente significa: Nginx sigue vivo, pero la aplicación que está detrás no responde correctamente.
Puedes entenderlo así: el portero sigue en la puerta, pero nadie en la cocina toma pedidos, o la cocina toma pedidos demasiado despacio.
Este artículo sigue un orden práctico: primero confirmar la causa desde los logs de Nginx, luego revisar PHP-FPM, Gunicorn, Node y los upstream de Docker, y por último revisar tiempos de espera, pools de procesos, memoria y disco. No reinstales el entorno de inmediato ni te limites a reiniciar todo el VPS.
En pocas palabras:
- 502 Bad Gateway: Nginx no pudo conectar con el backend, o el backend devolvió una respuesta no válida
- 504 Gateway Timeout: Nginx conectó, pero esperó demasiado tiempo la respuesta del backend
Escenarios comunes:
| Error | Causas comunes |
|---|---|
| 502 | PHP-FPM no iniciado, ruta del socket incorrecta, puerto sin escuchar, proceso del backend caído |
| 502 | Permisos del socket Unix incorrectos, Nginx no puede acceder al socket de PHP-FPM |
| 502 | Contenedor Docker caído, puerto del proxy inverso incorrecto |
| 504 | PHP/aplicación se ejecuta demasiado lento, base de datos bloqueada, API bloqueada |
| 504 | proxy_read_timeout / fastcgi_read_timeout de Nginx demasiado cortos |
| 504 | Pool de procesos del backend lleno, solicitudes en cola demasiado tiempo |
Por eso la dirección del diagnóstico no es "el sitio web no abre", sino "dónde se rompe la cadena desde el proxy frontal hasta el backend".
No adivines. Mira primero los logs:
sudo tail -n 100 /var/log/nginx/error.log
Ver en tiempo real:
sudo tail -f /var/log/nginx/error.log
Luego actualiza la página web y mira cuál es el nuevo error.
Tipos comunes:
connect() failed (111: Connection refused) while connecting to upstream
Significa que Nginx intentó conectar con el puerto o socket del backend y se rechazó. Puede que el backend no esté iniciado o que el puerto esté mal.
connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied)
Indica un problema de permisos del socket.
upstream timed out (110: Connection timed out) while reading response header from upstream
Significa que el backend responde demasiado lento, más orientado a 504/tiempo de espera.
no live upstreams while connecting to upstream
Significa que no hay backend disponible en el grupo upstream.
Si entiendes esta línea, básicamente sabes dónde revisar a continuación.
Si has cambiado recientemente la configuración del sitio, revisa Nginx primero:
sudo nginx -t
Si la configuración está bien, recarga:
sudo systemctl reload nginx
No ejecutes restart directamente. nginx -t puede detectar errores de sintaxis de configuración de antemano, y reload es más suave que restart.
Luego revisa el estado:
systemctl status nginx --no-pager
journalctl -u nginx --since "30 minutes ago" --no-pager
La documentación oficial de Nginx también distingue claramente los niveles de error_log y los logs de depuración. Para un diagnóstico normal basta con el error log; los logs de depuración sirven para problemas complejos, así que no los actives al principio, porque los logs se llenarán muy rápido.
En sitios PHP como WordPress, Typecho, Laravel y ThinkPHP, un 502 probablemente esté relacionado con PHP-FPM.
Primero mira el nombre del servicio. En distintos sistemas puede llamarse:
systemctl list-units | grep fpm
Habitualmente es:
php8.1-fpm
php8.2-fpm
php8.3-fpm
Revisa el estado:
systemctl status php8.2-fpm --no-pager
journalctl -u php8.2-fpm --since "30 minutes ago" --no-pager
Si no está iniciado:
sudo systemctl restart php8.2-fpm
Pero no termines después de reiniciar. Sigue mirando los logs para confirmar si es un error de configuración, falta de memoria o fallo al cargar una extensión.
Nginx y PHP-FPM suelen comunicarse mediante un socket Unix o un puerto TCP.
Primero busca qué está escrito en Nginx:
sudo grep -R "fastcgi_pass" /etc/nginx/sites-enabled /etc/nginx/conf.d
Puede que veas:
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
Entonces confirma si el socket existe:
ls -l /run/php/
Si Nginx está configurado con php8.1-fpm.sock pero el sistema en realidad solo tiene php8.2-fpm.sock, es un 502 típico.
Si Nginx está configurado con TCP:
fastcgi_pass 127.0.0.1:9000;
Revisa el puerto:
ss -lntp | grep 9000
En la documentación oficial de FastCGI de Nginx, fastcgi_pass es la directiva clave que determina a qué backend FastCGI se entrega la solicitud. Si la ruta o el puerto están mal, no importa lo sano que esté Nginx.
Si en el log aparece:
Permission denied while connecting to upstream
Primero revisa los permisos del socket:
ls -l /run/php/php8.2-fpm.sock
Luego revisa el usuario con el que se ejecuta Nginx:
ps -o user,group,cmd -C nginx
La configuración del pool de PHP-FPM suele incluir estos elementos:
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Ubicaciones habituales de los archivos de configuración:
/etc/php/8.2/fpm/pool.d/www.conf
Después de cambiar, revisa primero y luego reinicia PHP-FPM:
sudo php-fpm8.2 -t
sudo systemctl restart php8.2-fpm
sudo systemctl reload nginx
Los comandos pueden variar ligeramente según la distribución. Si php-fpm8.2 -t no existe, puedes usar:
php-fpm8.2 -tt
O revisar el nombre del binario de PHP-FPM del sistema actual.
Si aumenta el tráfico del sitio, o alguna solicitud PHP se queda bloqueada, el pool de procesos de PHP-FPM puede agotarse.
En los logs puede aparecer:
server reached pm.max_children setting
Primero revisa los logs de PHP-FPM:
journalctl -u php8.2-fpm --since "1 hour ago" --no-pager
Luego mira la configuración del pool:
sudo grep -E "^pm\.|^request_" /etc/php/8.2/fpm/pool.d/www.conf
Elementos comunes:
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 5
request_terminate_timeout = 60s
La documentación oficial de configuración de PHP-FPM de PHP incluye parámetros como pm.max_children, pm.max_requests, request_terminate_timeout, slowlog y request_slowlog_timeout. No te limites a subir pm.max_children sin límite, porque consume memoria.
Una estimación aproximada de la memoria de los procesos PHP puede hacerse así:
ps -ylC php-fpm8.2 --sort:rss
Si cada proceso hijo PHP usa 80MB y la máquina solo tiene 1GB de memoria, poner pm.max_children en 50 es muy peligroso. Puede que no resuelva el 502 y en cambio lleve el VPS a OOM.
PHP-FPM admite oficialmente una página de status, donde se puede ver el estado del pool de procesos, como procesos activos, listen queue, max children reached y slow requests.
Añade en la configuración del pool:
pm.status_path = /fpm-status
ping.path = /fpm-ping
En Nginx, permite solo acceso local:
location ~ ^/(fpm-status|fpm-ping)$ {
access_log off;
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Después de reload, prueba:
curl http://127.0.0.1/fpm-status
curl http://127.0.0.1/fpm-status?json
curl http://127.0.0.1/fpm-ping
Si listen queue a menudo no es 0, o max children reached crece, significa que PHP-FPM no da abasto.
Si usas Django, Flask o FastAPI, el backend puede ser Gunicorn o Uvicorn.
Primero mira la configuración upstream de Nginx:
sudo grep -R "proxy_pass" /etc/nginx/sites-enabled /etc/nginx/conf.d
Si es:
proxy_pass http://127.0.0.1:8000;
Revisa el puerto:
ss -lntp | grep 8000
Revisa el servicio:
systemctl status gunicorn --no-pager
journalctl -u gunicorn --since "30 minutes ago" --no-pager
Si no estás seguro del nombre del servicio:
systemctl list-units | grep -Ei "gunicorn|uvicorn|app"
ps -ef | grep -Ei "gunicorn|uvicorn|python"
Si Nginx puede conectar con el puerto pero devuelve 504, normalmente hay que revisar los logs de la aplicación, las conexiones a la base de datos, las consultas lentas y los tiempos de espera de API de terceros.
Las aplicaciones Node suelen usar Nginx como proxy inverso a un puerto local.
Revisa Nginx:
sudo grep -R "proxy_pass" /etc/nginx/sites-enabled /etc/nginx/conf.d
Revisa el puerto:
ss -lntp | grep -E ':3000|:8080|:8000'
Si usas PM2:
pm2 status
pm2 logs --lines 100
Causas comunes de 502:
- El proceso Node se cayó
- Se cambió el puerto y no se actualizó Nginx
- Las variables de entorno no se cargaron
- La aplicación arranca lento o se queda bloqueada
- La memoria se desbordó y el sistema la mató
Revisa OOM:
journalctl -k --since "2 hours ago" | grep -Ei "out of memory|killed process"
Cuando ejecutas un sitio web en Docker, Nginx puede estar en el host o en un contenedor.
Primero revisa el estado del contenedor:
docker ps
docker compose ps
Revisa los logs:
docker logs 容器名 --tail 100
docker compose logs --tail 100 服务名
Revisa el mapeo de puertos:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Si Nginx tiene configurado:
proxy_pass http://127.0.0.1:3000;
pero Docker no ha mapeado el 3000 al host, o el servicio dentro del contenedor escucha en 127.0.0.1 en lugar de 0.0.0.0, se produce un 502.
Prueba dentro del contenedor:
docker exec -it 容器名 sh
curl -I http://127.0.0.1:3000
Prueba en el host:
curl -I http://127.0.0.1:3000
Estos dos resultados te ayudan a determinar si el problema está dentro del contenedor o en el mapeo de puertos del contenedor al host.
Si el error es 504, el backend normalmente no está completamente caído, sino demasiado lento.
Parámetros comunes en Nginx:
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
La documentación oficial de Nginx incluye parámetros como fastcgi_read_timeout y fastcgi_connect_timeout. Pero no cambies el timeout a 600 segundos en cuanto veas un 504.
Si una página tarda 5 minutos en ejecutarse, el problema real puede ser:
- Consultas lentas en la base de datos
- API externa bloqueada
- Carga de archivo demasiado grande
- Tareas PHP que no deberían ejecutarse dentro de una solicitud web
- Cola no separada
- CPU / IO saturados
Revisa primero los recursos:
top
free -m
df -h
df -i
iostat -xz 1
Si no hay iostat:
sudo apt install sysstat
Luego revisa los logs de la aplicación y las consultas lentas de la base de datos. Ajustar el timeout solo alivia el dolor, no cura la enfermedad.
Cuando un sitio web da 502/504, normalmente sigo este orden:
curl -I https://example.com
sudo tail -n 100 /var/log/nginx/error.log
sudo nginx -t
systemctl status nginx --no-pager
systemctl list-units | grep -Ei "php|fpm|gunicorn|uvicorn|docker"
ss -lntp
Si es PHP:
systemctl status php8.2-fpm --no-pager
journalctl -u php8.2-fpm --since "30 minutes ago" --no-pager
sudo grep -R "fastcgi_pass" /etc/nginx/sites-enabled /etc/nginx/conf.d
ls -l /run/php/
Si es una aplicación con proxy inverso:
sudo grep -R "proxy_pass" /etc/nginx/sites-enabled /etc/nginx/conf.d
ss -lntp | grep -E ':3000|:8000|:8080'
journalctl -u gunicorn --since "30 minutes ago" --no-pager
pm2 status
Si es Docker:
docker ps
docker compose ps
docker compose logs --tail 100
Primero localiza el punto de ruptura de la cadena y luego decide si reiniciar el servicio, cambiar el socket, ajustar el pool de procesos o investigar solicitudes lentas.
Reiniciar Nginx, PHP-FPM o el servicio de la aplicación a veces recupera el servicio, pero puede que solo borre la escena.
Un orden mejor es:
- Capturar el error.log de Nginx
- Registrar el estado del servicio del backend
- Ver si el puerto/socket existe
- Ver si hay OOM, disco lleno, inodos llenos
- Luego decidir reload/restart
Si solo ejecutas a ciegas:
sudo reboot
La máquina arranca y el sitio web funciona, pero tampoco sabes cuál era la causa raíz. Volverá a ocurrir en la próxima hora punta.
- Se ha confirmado si es 502 o 504
- Se ha revisado
/var/log/nginx/error.log - Se ha ejecutado
nginx -t - Se ha confirmado que el servicio Nginx está normal
- En sitios PHP se ha revisado el estado y los logs de PHP-FPM
- Se ha verificado que existe el socket/puerto de
fastcgi_pass - Se han revisado los permisos del socket y los usuarios de Nginx/PHP-FPM
- Se ha revisado si
pm.max_childrenestá lleno - En aplicaciones Python/Node se ha confirmado el proceso y el puerto de escucha
- En aplicaciones Docker se ha confirmado el estado del contenedor, los logs y el mapeo de puertos
- En 504 se han revisado solicitudes lentas, base de datos, API externas, CPU/IO
- No se ha dependido solo de reiniciar para ocultar la causa raíz
La idea más importante sobre 502/504 es: que Nginx esté accesible no significa que el sitio web lo esté. Lo que debes revisar es la cadena de Nginx al backend: no conecta, no tiene permisos, el backend se cayó, la cola está llena o la respuesta es demasiado lenta. Si separas estas capas y las revisas, el problema normalmente aterriza rápido en un servicio concreto.