Abres tu sitio web y, en lugar de la página, ves una pantalla blanca con el texto 502 Bad Gateway, 504 Gateway Time-out, 403 Forbidden o 500 Internal Server Error. El número del error asusta, pero en realidad indica con bastante precisión en qué tramo del camino entre el navegador y el sitio se ha roto algo, y la causa real queda anotada en el registro de Nginx en una sola línea. En este artículo veremos cómo es ese camino, aprenderemos a encontrar la causa en un par de minutos y solucionaremos uno por uno los cuatro errores con los ejemplos que se dan con más frecuencia. Hemos reproducido todos los ejemplos en un servidor de pruebas con Debian 12, y la versión de Nginx en el momento de la prueba era la 1.22.1 y la de PHP, la 8.2.34.
Cómo es el camino del navegador al sitio
Cuando un visitante abre una página, su navegador envía una petición al servidor web, es decir, a Nginx. Si hace falta una imagen normal o una página HTML ya hecha, Nginx simplemente toma el archivo del disco y lo entrega. Pero la mayoría de los sitios no son solo archivos. La página hay que construirla, por ejemplo consultando una base de datos, y de eso se encarga un programa aparte al que se llama backend. Puede ser PHP-FPM, que ejecuta los scripts de WordPress, o una aplicación en Python o Node.js que escucha en su puerto. En este esquema, Nginx hace de portero. Recibe la petición, se la pasa al backend, espera la respuesta y se la reenvía al navegador. Por eso dos de los cuatro errores llevan en el nombre la palabra «pasarela» (Gateway), porque en ese momento Nginx hace de pasarela entre el visitante y la aplicación.
La primera cifra del código indica de qué lado está el fallo. El código 403 pertenece a la familia 4xx, es decir, a las peticiones que no se pueden atender tal como llegaron. Nginx entendió perfectamente lo que quería el navegador, pero se negó a dar la respuesta, porque no hay nada que mostrar o porque no tiene permisos para leer los archivos necesarios. Los códigos de la familia 5xx indican un fallo del lado del servidor. El código 500 dice que algo se ha roto al procesar la petición, casi siempre en el propio script o en la aplicación. El código 502 significa que Nginx no ha recibido del backend una respuesta coherente, por ejemplo porque el backend no está en marcha. El código 504 significa que el backend está conectado, pero no ha tenido tiempo de responder en el plazo fijado. Por eso, ante cualquiera de estos errores, primero hay que entender en qué eslabón de la cadena se ha producido el corte.
El código nombra el eslabón, pero no la causa, y la causa hay que buscarla en el registro. Veamos, por ejemplo, una situación en la que se ha detenido PHP-FPM. El navegador sigue llamando a Nginx, este intenta pasar la petición al socket de PHP-FPM, pero no puede conectarse, y por eso él mismo dibuja una página 502 corta con el número de su versión abajo, y en el registro anota a qué no ha podido conectarse exactamente. Si, en cambio, PHP-FPM funciona, pero el script se ha caído con un error fatal, Nginx recibe de él una respuesta con el código 500 y el cuerpo vacío y la reenvía tal cual, y el texto del error lo dibuja ya el propio navegador. Vimos las dos situaciones en el servidor de pruebas, y por el aspecto de la página se puede adivinar quién la ha generado, Nginx o la aplicación. Aun así, la respuesta exacta está en el registro de errores, al que pasamos ahora.
Qué necesitas
- Un servidor con Debian 12 en el que ya funcione Nginx, por ejemplo el plan DE-RZ9-1. Los ejemplos del artículo son muy ligeros, y para elegir el plan adecuado para tu sitio te ayudará el artículo sobre VPS. Si todavía no tienes servidor, contrátalo siguiendo la guía.
- Conexión por SSH, por ejemplo con Termius. Los comandos se ejecutan como
root. - Nginx y PHP-FPM. Cómo instalarlos lo describimos en el artículo sobre la instalación de Nginx. PHP-FPM solo hace falta para los ejemplos con PHP.
- Un sitio o una aplicación en el que veas el error.
Direcciones y nombres en los ejemplos
En el servidor de pruebas, cada ejemplo funcionaba en su propio puerto, por eso en los comandos y en las capturas del terminal, en lugar del dominio, aparece una dirección del tipo 127.0.0.1:8091. A tu sitio llámalo por el dominio, por ejemplo https://example.com. Las carpetas errors-demo-… y el servicio errors-app también están inventados para el artículo, y tú tendrás tus propios nombres. Las capturas del navegador están hechas en esos mismos sitios de prueba, y el dominio example.com de la barra de direcciones es ficticio.
Paso 1. Averigua el código de respuesta
Empieza con el comando curl. Accede al sitio igual que el navegador, pero muestra solo lo que el servidor ha respondido de verdad. El parámetro -I pide que envíe solo las cabeceras de la respuesta, y el código está en la primera línea.
# Mostramos las cabeceras de la respuesta, en la primera línea estará el código
curl -I https://example.comSi solo necesitas la línea con el código, añade el parámetro -s y pasa la salida al comando head.
# Solo la primera línea de la respuesta
curl -sI https://example.com | head -1Una línea del tipo HTTP/1.1 502 Bad Gateway confirma que el error lo ha dado el servidor, y no tu red ni el navegador. De paso, en la cabecera Server se ve la versión de Nginx.
Paso 2. Lee el registro de errores de Nginx
Nginx anota cada rechazo en el archivo /var/log/nginx/error.log. Abre el registro en modo seguimiento y luego actualiza en el navegador la página con el error. La nueva línea aparecerá en ese mismo momento.
# Seguimos el registro, para salir pulsa Ctrl+C
tail -f /var/log/nginx/error.logLas líneas antiguas no molestan
El comando tail -f primero muestra las últimas diez líneas del registro y solo después espera las nuevas. Para que las entradas antiguas no distraigan, añade el parámetro -n 0, como en la captura de abajo.

El código de respuesta y la línea del registro que explica la causa
Analizar una línea así no es difícil. Tomemos la que obtuvimos cuando la aplicación que hay detrás de Nginx estaba detenida.
2026/10/21 14:53:33 [error] 124894#124894: *3 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8098/", host: "example.com"- La hora y el nivel al principio de la línea. La palabra entre corchetes indica la gravedad de la entrada, y nosotros vimos
[error]y[crit]. - La esencia del problema justo después del número de proceso. Aquí es
connect() failed (111: Connection refused), es decir, Nginx no ha podido conectarse porque el puerto está cerrado. - client muestra quién ha hecho la petición, server y host muestran a qué sitio, y request contiene la propia petición.
- upstream muestra a dónde intentó Nginx pasar la petición exactamente. Es la dirección del backend, y sugiere qué programa revisar a continuación.
El registro es común a todos los sitios del servidor, por eso, con mucho tráfico, conviene buscar la entrada que necesitas por la dirección de la página.
# Últimas entradas en las que aparece la dirección buscada
grep '/slow' /var/log/nginx/error.log | tail -n 5Puedes leer más sobre cómo consultar los logs y dónde están en el artículo sobre los logs en Linux.
Paso 3. Comprueba el programa que hay detrás de Nginx
Si en la línea del registro aparece la palabra upstream, a continuación hay que averiguar qué pasa con el backend. Para una aplicación que funciona como servicio de systemd, comprueba su estado y los últimos mensajes. En lugar de myapp, pon el nombre de tu servicio, y para PHP será php8.2-fpm.
# Estado del servicio
systemctl status myapp --no-pager
# Últimos 50 mensajes del servicio
journalctl -u myapp -n 50 --no-pager
# Quién escucha en el puerto que necesitas, en lugar de 8098 pon el tuyo
ss -tlnp | grep 8098En la línea Active debe poner active (running). Los valores failed e inactive (dead) significan que el programa se ha detenido, y la causa suele contarla el registro del servicio. El último comando muestra los programas que escuchan en los puertos. Una respuesta vacía significa que nadie escucha en ese puerto y que Nginx no tiene a quién pasarle las peticiones. Cómo gestionar los servicios y leer sus logs se describe con detalle en el artículo sobre los servicios systemd.

El servicio está en estado failed, la causa está en el registro y nadie escucha en el puerto 8098
En la captura, el servicio errors-app se cayó nada más arrancar. En el registro se ve la causa, FileNotFoundError, es decir, a la aplicación le faltaba el archivo de configuración. Con el número del error y la línea del registro, ya se puede pasar al análisis de la causa concreta.
Error 502 Bad Gateway
La página 502 significa que Nginx ha aceptado la petición, pero no ha podido obtener respuesta del backend. Se ve como una página blanca corta con el número de versión de Nginx abajo.

Así se ve el error 502 en el navegador
Hemos reproducido las tres causas más frecuentes, y cada una tiene su propia línea en el registro.
La aplicación no está en marcha
Esta es la causa más sencilla y más frecuente. En la configuración de Nginx, el sitio pasa las peticiones a la aplicación.
location / {
proxy_pass http://127.0.0.1:8098;
}Si la aplicación se ha detenido, se ha caído o no ha llegado a arrancar después de reiniciar el servidor, en su puerto no hay nadie. El registro contiene entonces la línea que ya conoces.
2026/10/21 14:53:33 [error] 124894#124894: *3 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8098/", host: "example.com"El código 111: Connection refused significa literalmente que la conexión ha sido rechazada, porque el puerto está cerrado. En nuestro ejemplo, la causa la mostró el registro del servicio, que viste en la captura anterior. La aplicación no arrancó porque habíamos renombrado su archivo de configuración. Devuelve el archivo a su sitio e inicia el servicio.
# Devolvemos el archivo de configuración a su sitio
mv /opt/errors-demo/config.json.old /opt/errors-demo/config.json
# Iniciamos el servicio y comprobamos la respuesta del sitio
systemctl start errors-app
curl -I http://127.0.0.1:8091
El servicio está en marcha y el sitio vuelve a responder con el código 200
Para que este error no se repita después de cada fallo, encarga a systemd que levante la aplicación él mismo. Para ello, añade dos líneas en la sección [Service] del archivo del servicio.
[Service]
# Reiniciamos la aplicación si se ha caído
Restart=on-failure
# Esperamos tres segundos antes de reiniciar
RestartSec=3Después vuelve a leer la configuración y activa el arranque automático al iniciar el servidor.
# Aplicamos los cambios del archivo del servicio
systemctl daemon-reload
# Activamos el arranque junto con el servidor
systemctl enable errors-appLa comprobación fue muy gráfica. Finalizamos el proceso a la fuerza con el comando systemctl kill -s KILL errors-app. Justo después, el sitio respondía con el código 502, y tres segundos más tarde systemd volvió a arrancar la aplicación y el sitio se abrió de nuevo. Fíjate en que, antes de activar el arranque automático, en la línea Loaded de la captura ponía disabled. Significa que, después de reiniciar el servidor, el servicio no arranca por sí solo.
PHP-FPM, socket no encontrado
En los sitios con PHP, Nginx pasa las peticiones no a un puerto, sino a un socket, es decir, un archivo especial a través del cual se comunica con PHP-FPM. La ruta está indicada en la directiva fastcgi_pass.
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}Si ese archivo no existe, Nginx responde 502, y el registro lo dice con todas las letras.
2026/10/21 14:53:33 [crit] 124894#124894: *5 connect() to unix:/run/php/php8.1-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.1-fpm.sock:", host: "example.com"El código 2: No such file or directory significa que el archivo del socket no existe. Casi siempre hay dos causas. O bien en la configuración está el nombre de otra versión de PHP, por ejemplo el de una guía para PHP 8.1, y tú tienes la 8.2, o bien el servicio PHP-FPM está detenido y su socket ha desaparecido con él. Mira qué sockets hay en el servidor.
# Lista de sockets de PHP-FPM
ls /run/php/
En la configuración está php8.1-fpm.sock, pero en el servidor solo existe php8.2-fpm.sock
En nuestro ejemplo, en la carpeta estaban php8.2-fpm.sock y php-fpm.sock. Corrige el nombre en fastcgi_pass, comprueba la configuración y aplícala.
# Cambiamos la versión en el nombre del socket
sed -i 's/php8.1-fpm.sock/php8.2-fpm.sock/' /etc/nginx/sites-available/example.com
# Comprobamos la configuración y la aplicamos
nginx -t && systemctl reload nginxEl archivo php-fpm.sock
En Debian, junto al socket de una versión concreta hay también php-fpm.sock. Es un enlace al socket de la versión de PHP instalada, y en nuestro servidor apuntaba a php8.2-fpm.sock. Si indicas en fastcgi_pass precisamente este, no tendrás que cambiar el nombre en la configuración al cambiar de versión de PHP. Comprobamos que un sitio con esa ruta responde con normalidad.
Si el nombre es correcto y aun así no hay socket, es que se ha detenido el propio PHP-FPM. Lo detuvimos con el comando systemctl stop php8.2-fpm y obtuvimos el mismo error 502 con la línea connect() to unix:/run/php/php8.2-fpm.sock failed (2: No such file or directory). El comando systemctl status php8.2-fpm mostró inactive (dead), y después de iniciar el servicio el sitio volvió a funcionar.

PHP-FPM detenido, no hay socket, y tras iniciar el servicio el sitio vuelve a abrirse
Si el servicio no arranca, la causa la mostrará journalctl -u php8.2-fpm -n 50 --no-pager. Y después de cambiar la configuración de PHP-FPM, compruébala con el comando php-fpm8.2 -t, como mostraremos más adelante.
PHP-FPM, sin acceso al socket
A veces el socket está en su sitio, pero Nginx no puede usarlo. En el registro aparece entonces otro código.
2026/10/21 14:57:38 [crit] 133790#133790: *56 connect() to unix:/run/php/errors-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "HEAD / HTTP/1.1", upstream: "fastcgi://unix:/run/php/errors-fpm.sock:", host: "127.0.0.1:8099"
El socket está en su sitio, pero pertenece a root y Nginx no puede conectarse a él
El código 13: Permission denied significa que Nginx no tiene permisos sobre ese archivo. Nginx funciona con el usuario www-data, y el acceso al socket lo determinan los parámetros listen.owner, listen.group y listen.mode de la configuración del pool de PHP-FPM. Según la documentación de PHP, si no se indica propietario, el socket recibe el usuario con el que se ha iniciado el servicio y los permisos 0660. En nuestro ejemplo creamos un pool aparte, errors, en el archivo /etc/php/8.2/fpm/pool.d/errors.conf y no indicamos propietario.
[errors]
user = nobody
group = nogroup
listen = /run/php/errors-fpm.sock
pm = ondemand
pm.max_children = 2
pm.process_idle_timeout = 10sEl servicio se inicia como root, por eso el socket recibió el propietario root y los permisos srw-rw----, y www-data no pertenece a ese grupo. Añade al pool dos líneas con el propietario.
# Vamos a la carpeta con la configuración de los pools
cd /etc/php/8.2/fpm/pool.d
# Permitimos que el usuario de Nginx trabaje con el socket
printf 'listen.owner = www-data\nlisten.group = www-data\n' >> errors.conf
# Comprobamos la configuración y reiniciamos PHP-FPM
php-fpm8.2 -t && systemctl restart php8.2-fpm
# Vemos el propietario del socket y comprobamos el sitio
ls -l /run/php/errors-fpm.sock
curl http://127.0.0.1:8099/
El socket pertenece a www-data y el sitio responde
Hace falta restart, no reload
Cuando, después de cambiar la configuración, hicimos systemctl reload php8.2-fpm, el propietario del socket siguió siendo el mismo, root, y el error 502 no desapareció. El socket cambió de propietario solo después de systemctl restart php8.2-fpm.
Comprueba siempre la configuración antes de recargar
Acostúmbrate a ejecutar nginx -t antes de cada comando reload. Si hay un error en la configuración, la comprobación lo mostrará, y Nginx, que está funcionando, seguirá respondiendo con la configuración antigua. Para PHP-FPM existe la misma comprobación, el comando php-fpm8.2 -t.
Error 504 Gateway Time-out
La página 504 indica que el backend está vivo y ha aceptado la petición, pero tarda demasiado en preparar la respuesta. Nginx espera un tiempo limitado y, si no la recibe, interrumpe él mismo la espera. El tiempo de espera lo fija la directiva proxy_read_timeout, que por defecto son 60 segundos, y en PHP-FPM desempeña el mismo papel fastcgi_read_timeout. Un detalle importante de la documentación de Nginx es que este tiempo se cuenta entre dos lecturas consecutivas, y no para toda la respuesta completa. Si el backend envía la siguiente porción de datos más a menudo que una vez cada 60 segundos, la conexión no se corta.
Para verlo con tus propios ojos, añadimos a la aplicación de pruebas la dirección /slow, que responde solo al cabo de 75 segundos, como un informe pesado o una consulta larga a la base de datos. La página estuvo abriéndose exactamente un minuto, y después apareció el error.

El error 504 aparece después de un minuto de espera
El comando curl con el parámetro -w permite medir el tiempo y ver el código de un vistazo.
# Mostramos el código de respuesta y el tiempo de espera
curl -s -o /dev/null -w '%{http_code} en %{time_total} s\n' http://127.0.0.1:8093/slowEn el registro apareció entonces esta entrada.
2026/10/21 15:04:14 [error] 144934#144934: *81 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /slow HTTP/1.1", upstream: "http://127.0.0.1:8098/slow", host: "127.0.0.1:8093"
Código 504 a los 60,06 segundos y la línea upstream timed out en el registro
Las palabras while reading response header significan que Nginx se conectó con éxito, envió la petición y esperaba el comienzo de la respuesta. La medición mostró 60,06 segundos, es decir, Nginx cortó la espera exactamente en el valor por defecto.
Conviene arreglar el 504 empezando por la causa. Averigua qué es exactamente lo que tarda, por ejemplo una consulta pesada a la base de datos, una llamada a un servicio ajeno o un informe grande, y acelera eso. Si la tarea es larga por naturaleza, por ejemplo una exportación o una importación, aumenta el tiempo de espera para la dirección que corresponda. Añade en el bloque location la directiva proxy_read_timeout.
location / {
proxy_pass http://127.0.0.1:8098;
# Esperamos la respuesta de la aplicación hasta dos minutos
proxy_read_timeout 120s;
}Comprueba la configuración y aplícala, y luego repite la petición.
# Comprobamos la configuración y la aplicamos
nginx -t && systemctl reload nginx
# Repetimos la petición
curl -s -o /dev/null -w '%{http_code} en %{time_total} s\n' http://127.0.0.1:8093/slow
La misma petición ahora devuelve 200 a los 75 segundos
En los sitios con PHP-FPM se hace lo mismo, solo que la directiva se llama fastcgi_read_timeout y va en el bloque location ~ \.php$, junto a fastcgi_pass. Lo comprobamos con un script que responde a los 70 segundos. Con la configuración por defecto daba 504 exactamente a los 60 segundos, y con la línea fastcgi_read_timeout 120s; respondió con el código 200 a los 70 segundos. La línea del registro tenía entonces este aspecto.
2026/10/21 15:07:46 [error] 153814#153814: *89 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /slow.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock", host: "127.0.0.1:8096"El tiempo de espera de Nginx no cancela el límite de PHP
PHP tiene su propio límite de tiempo de ejecución de un script, el parámetro max_execution_time. En el archivo /etc/php/8.2/fpm/php.ini de nuestro servidor es de 30 segundos. Ejecutamos un script que ocupaba el procesador sin parar, y PHP lo detuvo exactamente a los 30 segundos. Como respuesta llegó no un 504, sino un error 500, y en el registro apareció la línea PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/errors-demo-php/busy.php on line 3. Un script que simplemente esperaba con la llamada sleep(70) no quedó sujeto a esta restricción, porque, según la documentación de PHP, el tiempo de espera de las llamadas al sistema en Linux no se cuenta. Si tu script choca justo con el límite de PHP, sube max_execution_time, y no solo el tiempo de espera de Nginx.
Error 403 Forbidden
La página 403 significa que Nginx ha entendido la petición, pero se ha negado a responderla. En la práctica hay dos causas. O bien en la carpeta no hay nada que mostrar, o bien Nginx no tiene permisos para leer el archivo. En el registro estos casos se distinguen fácilmente, por eso empieza siempre por él.

Así se ve el error 403 en el navegador
No hay archivo de índice
Cuando un visitante abre la dirección de una carpeta, por ejemplo https://example.com/, Nginx busca en ella la página principal. El nombre del archivo lo fija la directiva index, que por defecto es index.html. Si no existe ese archivo, Nginx podría mostrar la lista de archivos de la carpeta, pero por defecto esa opción está desactivada (autoindex off), y por eso responde 403.
2026/10/21 14:53:33 [error] 124894#124894: *7 directory index of "/var/www/errors-demo-403a/" is forbidden, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com"La frase directory index of … is forbidden significa justamente que en la carpeta no hay archivo de índice y que la lista de archivos está prohibida. Mira qué hay en la carpeta del sitio.
# Vemos el contenido de la carpeta del sitio
ls -la /var/www/errors-demo-403a
En la carpeta del sitio no hay página principal, por eso Nginx responde 403
La carpeta estaba vacía. Lo más habitual es que ocurra cuando el sitio se ha subido a una carpeta equivocada o se ha olvidado subir el propio archivo de la página principal. Comprueba que la directiva root apunta a la carpeta donde está realmente el sitio y que en ella hay un archivo con el nombre de la directiva index. Si en un sitio PHP es index.php, asegúrate de que está indicado en index index.html index.php;. Después de subir el archivo, la respuesta cambia al instante y no hace falta recargar Nginx.
# Creamos la página principal
echo '<h1>El sitio funciona</h1>' > /var/www/errors-demo-403a/index.html
# Comprobamos la respuesta
curl -sI http://127.0.0.1:8094/ | head -1Después de esto, el servidor respondió HTTP/1.1 200 OK.
Si lo que necesitas es justamente la lista de archivos, por ejemplo para una carpeta con archivos comprimidos, actívala con la directiva autoindex on; en el bloque location que corresponda. Comprobamos que en ese caso Nginx devuelve la página Index of / con la lista de archivos de la carpeta.
La lista de archivos la ve todo el mundo
Con autoindex on, cualquier visitante verá los nombres de todos los archivos de la carpeta y podrá descargar cada uno de ellos. Actívalo solo para las carpetas que estés dispuesto a mostrar por completo.
Sin permisos sobre el archivo
En el segundo caso, el archivo está en su sitio, pero Nginx no puede leerlo. El registro lo comunica así.
2026/10/21 14:53:33 [error] 124894#124894: *8 open() "/var/www/errors-demo-403b/index.html" failed (13: Permission denied), client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com"El código 13: Permission denied significa una denegación de acceso a nivel de Linux. Para entender de quién son los permisos que importan, averigua con qué usuario funciona Nginx y luego mira los permisos de cada elemento de la ruta con el comando namei.
# Con qué usuario funciona Nginx
grep ^user /etc/nginx/nginx.conf
# Permisos de cada carpeta y archivo de la ruta
namei -l /var/www/errors-demo-403b/index.html
Nginx funciona como www-data, y el archivo está cerrado para todos excepto root
Nginx funciona con el usuario www-data, y el archivo tiene los permisos -rw------- y pertenece a root, es decir, solo puede leerlo el propietario. Para www-data es un usuario ajeno y no tiene acceso. Esta situación la obtuvimos creando el archivo como root con los permisos 600. Para un sitio sirve una regla sencilla. A las carpetas se les pone 755, a los archivos 644, y como propietario se pone el usuario del servidor web. Cómo funcionan los permisos y qué significan esas cifras se explica en el artículo sobre los permisos de acceso en Linux.
# Pasamos el sitio al usuario del servidor web
chown -R www-data:www-data /var/www/errors-demo-403b
# 755 para las carpetas, 644 para los archivos
find /var/www/errors-demo-403b -type d -exec chmod 755 {} +
find /var/www/errors-demo-403b -type f -exec chmod 644 {} +
El archivo se puede leer y el sitio responde con el código 200
Para leer el archivo, a Nginx le basta el permiso r para «otros». Comprobamos que, con el propietario root y los permisos 644, el sitio también se abre, y que el cambio de propietario hace falta para que el propio sitio pueda guardar los archivos que se suben.
No arregles el 403 con permisos 777
Los permisos 777 permiten a todos los usuarios del servidor leer y modificar el archivo, y el error, en efecto, desaparece. Pero con él desaparece también la protección del sitio, por eso es mejor no aplicar ese consejo de internet. Pon como propietario al usuario www-data y asigna 755 y 644.
Sin permisos sobre una carpeta de la ruta al archivo
A veces el propio archivo es accesible para todos y el error sigue ahí. Para llegar al archivo, Nginx debe pasar por cada carpeta de la ruta, y para eso www-data necesita el permiso x en cada una de ellas. Basta con que una sola carpeta esté cerrada. El registro en este caso se ve algo distinto, porque no contiene la palabra open().
2026/10/21 15:08:10 [error] 156517#156517: *109 "/var/www/errors-demo-403c/public/index.html" is forbidden (13: Permission denied), client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "127.0.0.1:8095"En nuestro ejemplo, el archivo index.html tiene los permisos 644, pero la carpeta que está por encima está cerrada. Para encontrarla te ayudará el mismo comando namei.
# Buscamos la carpeta cerrada en la ruta al archivo
namei -l /var/www/errors-demo-403c/public/index.html
La carpeta errors-demo-403c está cerrada para todos excepto root, y tras chmod 755 el sitio se abre
En la salida, la carpeta errors-demo-403c tiene los permisos drwx------, y las demás carpetas están abiertas. Ábrela para lectura y acceso.
# Permitimos entrar en la carpeta a todos los usuarios
chmod 755 /var/www/errors-demo-403cEsta situación es típica de los sitios que están en una carpeta cerrada, por ejemplo la personal. En nuestro servidor, la carpeta /root tiene los permisos 700, por eso no conviene colocar un sitio en ella. Para los sitios usa /var/www.
Error 500 Internal Server Error
Al código 500 se le llama el más impreciso, porque significa simplemente «algo ha ido mal al procesar la petición». Por eso aquí es especialmente importante mirar el registro. Hemos reproducido dos causas que se encuentran con más frecuencia que otras. La primera es de PHP y la segunda, del propio Nginx.
Error fatal en PHP
Si un script PHP se cae, PHP-FPM responde con el código 500. Por defecto, PHP no muestra al visitante el texto del error, y en nuestro caso el parámetro display_errors en /etc/php/8.2/fpm/php.ini estaba desactivado y log_errors, activado. El cuerpo de la respuesta estaba vacío, por eso el mensaje lo dibujó el propio navegador, y no Nginx.

Con una respuesta vacía, el navegador muestra su propia página con el código HTTP ERROR 500
En cambio, todo lo que quería decir PHP llega al registro de Nginx.
2026/10/21 15:09:26 [error] 156517#156517: *123 FastCGI sent in stderr: "PHP message: PHP Fatal error: Uncaught Error: Call to undefined function mb_strlen() in /var/www/errors-demo-php/page.php:3
Stack trace:
#0 {main}
thrown in /var/www/errors-demo-php/page.php on line 3" while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /page.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "127.0.0.1:8096"
Código 500 y la línea del registro con el texto del error de PHP
La entrada empieza con las palabras FastCGI sent in stderr, y tras ellas viene el propio mensaje de PHP. Hay que leerlo así. El tipo de error es PHP Fatal error, luego va la esencia, Call to undefined function mb_strlen(), es decir, se ha llamado a una función que no existe, y al final el archivo y la línea donde ocurrió. En nuestro ejemplo, la función mb_strlen() pertenece al módulo mbstring, que no está en una instalación nueva de PHP. Este módulo figura en la lista que recomendamos instalar para WordPress en el artículo sobre la instalación de Nginx. Instala el módulo que falta.
# Instalamos el módulo mbstring para PHP
apt install -y php-mbstringDespués de la instalación no reiniciamos nada, y la página funcionó enseguida.

El módulo está instalado y la página devuelve 200 y el resultado de la función
Otra causa frecuente del 500 en PHP es un error de sintaxis después de editar un archivo. Puedes comprobar el archivo en el servidor con el comando php -l, que también te dirá en qué línea está el error.
# Comprobamos la sintaxis del archivo
php -l /var/www/errors-demo-php/parse.phpPHP Parse error: syntax error, unexpected token "echo", expecting "," or ";" in parse.php on line 3
Errors parsing parse.phpNo actives la visualización de errores en un sitio en producción
El parámetro display_errors se puede activar para ver el error directamente en el navegador, pero en un sitio en producción no conviene hacerlo. El texto del error contiene rutas de archivos del servidor, y lo verá cualquier visitante. El registro de Nginx da la misma información, pero solo a ti.
Bucle de redirecciones internas
La segunda causa no tiene nada que ver con la aplicación, es un error en la configuración del propio Nginx. Veamos una configuración en la que, para cualquier dirección en la que no haya un archivo adecuado, se entrega la página principal /index.html.
root /var/www/errors-demo-cycle;
location / {
try_files $uri /index.html;
}El último parámetro de try_files funciona como una redirección interna. Nginx empieza el procesamiento de nuevo, ya para la dirección /index.html, y vuelve a caer en el mismo location. Si el archivo index.html existe, todo termina bien. Pero si no existe, por ejemplo porque se ha olvidado subir la compilación del sitio o porque root apunta a otro sitio, Nginx vuelve a redirigir la petición a /index.html, y así en círculo. Según la documentación de Nginx, ese bucle se interrumpe después de diez intentos, y el cliente recibe un error 500.

Aquí el error 500 lo dibujó el propio Nginx, por eso abajo se ve su versión
En el registro, la entrada lo dice directamente.
2026/10/21 14:53:33 [error] 124894#124894: *11 rewrite or internal redirection cycle while internally redirecting to "/index.html", client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com"La frase rewrite or internal redirection cycle significa un bucle de redirecciones, y entre comillas está la dirección a la que Nginx iba en círculo. Comprueba si existe ese archivo en la carpeta indicada en root y, si hace falta, súbelo.
# Vemos qué hay en la carpeta del sitio
ls -la /var/www/errors-demo-cycle
# Ponemos el archivo en su sitio
echo '<h1>El sitio funciona</h1>' > /var/www/errors-demo-cycle/index.html
# Comprobamos la respuesta
curl -sI http://127.0.0.1:8097/ | head -1
El bucle se detiene en cuanto el archivo necesario aparece en la carpeta del sitio
Tras crear el archivo, la respuesta pasó a ser HTTP/1.1 200 OK. No hizo falta recargar Nginx, porque lo que había que arreglar no era la configuración, sino el contenido de la carpeta.
Preguntas frecuentes
upstream prematurely closed connection while reading response header from upstream
Esta línea del registro acompaña al error 502. Nginx se conectó a la aplicación y envió la petición, pero la aplicación cortó la conexión sin enviar respuesta. Por ejemplo, podría haberse caído justo durante el procesamiento de la petición. Mira el registro del servicio con el comando journalctl -u nombre-servicio -n 50 --no-pager, y ahí debería estar la causa. Lo reprodujimos con una línea del tipo upstream prematurely closed connection while reading response header from upstream, … request: "GET /close HTTP/1.1", upstream: "http://127.0.0.1:8098/close".
upstream sent too big header while reading response header from upstream
El error 502 aparece cuando la aplicación envía cabeceras de respuesta demasiado largas, por ejemplo cookies enormes. Nginx lee la primera parte de la respuesta en un búfer, cuyo tamaño por defecto es de una página de memoria, es decir, 4 u 8 KB. Si las cabeceras no caben en él, la respuesta se considera no válida. Lo comprobamos con una cabecera de 9000 caracteres. Aumenta el búfer en el bloque location.
location / {
proxy_pass http://127.0.0.1:8098;
# Un búfer algo mayor para las cabeceras de respuesta
proxy_buffer_size 16k;
proxy_buffers 8 16k;
}No se puede omitir la línea proxy_buffers 8 16k;. Cuando añadimos solo proxy_buffer_size 16k;, nginx -t informó del error "proxy_busy_buffers_size" must be less than the size of all "proxy_buffers" minus one buffer, y esa configuración no se puede aplicar. Con las dos líneas la comprobación pasó, y la página se abrió con el código 200.
nginx -t muestra [emerg] después de cambiar la configuración
Significa que hay un error en el archivo de configuración y que Nginx no aplicará esa configuración. Mientras no ejecutes systemctl reload nginx, el sitio sigue funcionando con la configuración antigua, por eso ejecuta siempre nginx -t && systemctl reload nginx, y no solo el comando reload. El texto del mensaje indica qué es lo que falla. Por ejemplo, "proxy_busy_buffers_size" must be less than the size of all "proxy_buffers" minus one buffer habla de tamaños de búfer incoherentes en el bloque location.
El sitio muestra un error, pero error.log está vacío
Lo más probable es que ese sitio tenga su propio registro de errores. Se fija con la directiva error_log en el bloque server, y entonces las entradas van al archivo indicado y no al error.log general. Lo comprobamos. Con la directiva error_log /var/log/nginx/errors-demo.log; en el bloque del sitio, la línea sobre el error 403 apareció solo en ese archivo. Puedes encontrar todos los registros que conoce Nginx con el comando de abajo.
# Mostramos todas las directivas error_log de toda la configuración
nginx -T 2>/dev/null | grep -n error_logEn nuestro ejemplo, el comando mostró error_log /var/log/nginx/error.log; del archivo principal y una línea con la dirección del registro del sitio.
curl: (7) Failed to connect to … port … Couldn't connect to server
Esto ya no es uno de los cuatro errores. Nginx no ha enviado ninguna respuesta, porque en ese puerto no escucha nadie. En ese caso, el navegador muestra su propia página, que en Chrome es ERR_CONNECTION_REFUSED. Recibimos este mensaje al acceder a un puerto que nadie ocupaba, curl: (7) Failed to connect to 127.0.0.1 port 8090 after 0 ms: Couldn't connect to server. Comprueba que el propio Nginx está en marcha con el comando systemctl status nginx y que los puertos 80 y 443 están abiertos en el cortafuegos.
PHP-FPM registra Maximum execution time of 30 seconds exceeded
El script ha funcionado más tiempo del que permite el parámetro max_execution_time, que por defecto son 30 segundos, y PHP lo ha detenido. En ese caso, Nginx devuelve 500, y no 504. Abre /etc/php/8.2/fpm/php.ini, aumenta el valor del parámetro y reinicia el servicio con el comando systemctl restart php8.2-fpm. Si el script debe funcionar más de un minuto, no olvides subir también fastcgi_read_timeout en la configuración de Nginx.
Nuestros recursos
Canal de Telegram: https://t.me/f3cloud_news
Web del hosting: https://f3cloud.com