--- title: "Cómo configurar Nginx como proxy inverso en Ubuntu 24.04" description: "Configura Nginx como proxy inverso en Ubuntu 24.04 delante de una aplicación, con las cabeceras correctas, HTTPS, WebSockets, balanceo de carga y caché." category: "Servidores Web" language: es source: https://cubepath.com/es/docs/servidores-web/nginx-como-proxy-inverso updated: 2026-09-25 --- # Cómo configurar Nginx como proxy inverso en Ubuntu 24.04 Un proxy inverso recibe las peticiones de los clientes y las reenvía a una o varias aplicaciones que escuchan en la red interna. Colocar Nginx delante de una aplicación Node.js, Python, Go o de un contenedor te permite terminar HTTPS en un único punto, servir varias aplicaciones con la misma IP, repartir la carga entre instancias y no exponer los puertos de la aplicación a Internet. En este tutorial configurarás Nginx como proxy inverso en Ubuntu 24.04, primero con una sola aplicación y después con HTTPS, WebSockets, balanceo de carga y caché. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, y un usuario no root con privilegios `sudo`. - Un dominio con un registro DNS A apuntando a la IP del servidor. En la guía se usa `your_domain` como marcador. - Una aplicación que escuche en un puerto local. Si todavía no tienes una, en el paso 2 se levanta un backend de prueba con Python, que viene instalado en Ubuntu. ## Paso 1: Instalar Nginx y abrir el firewall Instala Nginx desde los repositorios de Ubuntu: ```bash sudo apt update sudo apt install nginx ``` El servicio arranca y queda habilitado automáticamente. Compruébalo: ```bash systemctl is-active nginx ``` ```output active ``` Permite el tráfico HTTP y HTTPS en UFW: ```bash sudo ufw allow 'Nginx Full' ``` ## Paso 2: Preparar una aplicación de prueba Para comprobar el proxy necesitas algo detrás. Si ya tienes una aplicación en `127.0.0.1:3000`, salta al paso 3. Si no, crea un directorio con una página y sírvela con el servidor HTTP de Python, escuchando solo en la interfaz local: ```bash mkdir -p ~/app1 echo "Respuesta del backend 1" > ~/app1/index.html python3 -m http.server 3000 --bind 127.0.0.1 --directory ~/app1 > /dev/null 2>&1 & ``` El `&` deja el proceso en segundo plano en tu sesión. Es suficiente para este tutorial; una aplicación real debe ejecutarse como servicio de systemd. Comprueba que responde: ```bash curl http://127.0.0.1:3000 ``` ```output Respuesta del backend 1 ``` Que la aplicación escuche en `127.0.0.1` y no en `0.0.0.0` es importante: así solo es accesible a través de Nginx, y nadie puede saltarse el proxy conectándose directamente al puerto 3000. ## Paso 3: Crear el bloque de servidor del proxy Crea un archivo de configuración para el sitio: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` ```nginx server { listen 80; listen [::]:80; server_name your_domain www.your_domain; location / { proxy_pass http://127.0.0.1:3000; include proxy_params; } } ``` `proxy_pass` indica a dónde se reenvían las peticiones. El archivo `/etc/nginx/proxy_params`, incluido en el paquete de Ubuntu, añade las cabeceras que la aplicación necesita para conocer la petición original: ```nginx proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; ``` Sin ellas, la aplicación vería todas las peticiones como procedentes de `127.0.0.1` y no sabría si el cliente usó HTTPS, lo que rompe redirecciones, enlaces absolutos y registros de acceso. Configura tu framework para que confíe en estas cabeceras cuando vengan del proxy (por ejemplo, `app.set('trust proxy', 'loopback')` en Express). Activa el sitio y desactiva el sitio por defecto de Nginx: ```bash sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/ sudo rm /etc/nginx/sites-enabled/default ``` Valida la configuración y recarga: ```bash sudo nginx -t sudo systemctl reload nginx ``` ```output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Prueba el proxy desde tu equipo: ```bash curl -i http://your_domain ``` ```output HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) Content-Type: text/html ... Respuesta del backend 1 ``` La cabecera `Server: nginx` y el contenido del backend confirman que la petición ha pasado por el proxy. > Nota: la barra final en `proxy_pass` cambia el comportamiento. Con `location /api/ { proxy_pass http://127.0.0.1:3000/; }`, una petición a `/api/users` llega al backend como `/users`. Sin la barra final (`proxy_pass http://127.0.0.1:3000;`), llega como `/api/users`. ## Paso 4: Activar HTTPS Con el proxy funcionando por HTTP, obtén un certificado de Let's Encrypt. Certbot añade la configuración TLS y la redirección al mismo bloque de servidor: ```bash sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d your_domain -d www.your_domain ``` Comprueba que el proxy responde por HTTPS: ```bash curl -I https://your_domain ``` ```output HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) ``` A partir de aquí la aplicación recibe `X-Forwarded-Proto: https`, aunque la conexión entre Nginx y el backend sea HTTP sin cifrar dentro del servidor. ## Paso 5: Ajustar tiempos de espera y tamaño de subida Los valores por defecto de Nginx cortan una respuesta que tarde más de 60 segundos y rechazan cuerpos de petición de más de 1 MB. Si tu aplicación genera informes largos o acepta subidas de archivos, ajústalos en el bloque `server` del puerto 443: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` ```nginx client_max_body_size 50m; location / { proxy_pass http://127.0.0.1:3000; include proxy_params; proxy_connect_timeout 5s; proxy_read_timeout 120s; proxy_send_timeout 120s; } ``` - `client_max_body_size`: tamaño máximo del cuerpo de la petición. Si se supera, Nginx responde `413 Request Entity Too Large`. - `proxy_connect_timeout`: tiempo para establecer la conexión con el backend. En local debe ser casi inmediato, así que un valor bajo detecta antes un backend caído. - `proxy_read_timeout`: tiempo máximo entre dos lecturas de la respuesta del backend. Si se supera, Nginx devuelve `504 Gateway Timeout`. Valida y recarga con `sudo nginx -t && sudo systemctl reload nginx`. ## Paso 6: Soportar WebSockets Las conexiones WebSocket empiezan como una petición HTTP con las cabeceras `Upgrade` y `Connection`, que Nginx no reenvía por defecto. Crea un `map` en el contexto `http` para fijar `Connection` según la petición: ```bash sudo nano /etc/nginx/conf.d/websocket-map.conf ``` ```nginx map $http_upgrade $connection_upgrade { default upgrade; '' ''; } ``` Si la petición trae `Upgrade`, se envía `Connection: upgrade`; si no, la cabecera `Connection` se omite, lo que permite reutilizar conexiones con el backend (paso 7). Los archivos de `/etc/nginx/conf.d/` se incluyen dentro del bloque `http`, por eso el `map` va aquí y no en el sitio. Añade después al `location` del proxy: ```nginx proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 1h; ``` `proxy_http_version 1.1` es necesario porque el mecanismo de `Upgrade` no existe en HTTP/1.0, que es lo que Nginx usa por defecto con el backend. El `proxy_read_timeout` largo evita que Nginx cierre conexiones WebSocket inactivas a los 60 segundos. Si solo una ruta usa WebSockets (por ejemplo `/socket.io/`), pon estas líneas en un `location` propio para esa ruta. Valida y recarga Nginx. ## Paso 7: Repartir la carga entre varias instancias Un bloque `upstream` agrupa varios backends bajo un nombre y Nginx reparte las peticiones entre ellos. Para probarlo, levanta una segunda instancia del backend de prueba en el puerto 3001: ```bash mkdir -p ~/app2 echo "Respuesta del backend 2" > ~/app2/index.html python3 -m http.server 3001 --bind 127.0.0.1 --directory ~/app2 > /dev/null 2>&1 & ``` Define el grupo en el archivo del sitio, fuera de cualquier bloque `server`: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` ```nginx upstream app_backend { server 127.0.0.1:3000; server 127.0.0.1:3001; keepalive 16; } ``` Y cambia el `proxy_pass` del `location` para que apunte al grupo: ```nginx proxy_pass http://app_backend; ``` `keepalive 16` mantiene abiertas hasta 16 conexiones inactivas por proceso de Nginx con los backends, lo que evita abrir una conexión TCP por petición. Requiere `proxy_http_version 1.1`, que ya añadiste en el paso 6. Valida, recarga y haz varias peticiones seguidas: ```bash sudo nginx -t && sudo systemctl reload nginx for i in 1 2 3 4; do curl -s https://your_domain; done ``` ```output Respuesta del backend 1 Respuesta del backend 2 Respuesta del backend 1 Respuesta del backend 2 ``` Por defecto Nginx usa reparto circular (round robin). Puedes cambiarlo con una directiva al inicio del bloque `upstream`: | Directiva | Comportamiento | |---|---| | (ninguna) | Round robin: cada petición va al siguiente servidor. | | `least_conn;` | Envía la petición al servidor con menos conexiones activas. Útil con peticiones de duración muy variable. | | `ip_hash;` | La misma IP de cliente va siempre al mismo servidor. Sirve para aplicaciones con sesiones en memoria. | También puedes ajustar cada servidor: `weight=3` le da más peticiones, `max_fails=3 fail_timeout=30s` lo retira temporalmente tras tres fallos, y `backup` solo lo usa cuando los demás están caídos. Para comprobarlo, detén una instancia con `kill %1` (o el identificador de trabajo que muestre `jobs`) y verás que todas las respuestas pasan a venir del otro backend. ## Paso 8 (opcional): Cachear respuestas del backend Si la aplicación sirve contenido que no cambia en cada petición, Nginx puede guardar las respuestas y servirlas sin consultar al backend. Define la zona de caché en el contexto `http`: ```bash sudo nano /etc/nginx/conf.d/proxy-cache.conf ``` ```nginx proxy_cache_path /var/cache/nginx/app levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m use_temp_path=off; ``` `keys_zone` reserva 10 MB de memoria para las claves (unas 80.000 entradas), `max_size` limita el disco a 1 GB e `inactive` elimina lo que no se pide en 60 minutos. Nginx crea el directorio al arrancar. Activa la caché en el `location` del proxy: ```nginx proxy_cache app_cache; proxy_cache_valid 200 301 10m; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; add_header X-Cache-Status $upstream_cache_status always; ``` `proxy_cache_use_stale` sirve la copia guardada si el backend falla, lo que da cierta tolerancia a caídas. Nginx respeta las cabeceras `Cache-Control` y `Set-Cookie` del backend: no cachea respuestas marcadas como `private` o `no-store`, ni las que establecen cookies, así que las páginas personalizadas no se mezclan entre usuarios siempre que la aplicación las marque correctamente. Valida, recarga y pide la misma URL dos veces: ```bash sudo nginx -t && sudo systemctl reload nginx curl -sI https://your_domain | grep X-Cache-Status curl -sI https://your_domain | grep X-Cache-Status ``` ```output X-Cache-Status: MISS X-Cache-Status: HIT ``` ## Solución de problemas Revisa siempre primero el log de errores de Nginx, que indica la causa exacta: ```bash sudo tail -n 20 /var/log/nginx/error.log ``` - **`502 Bad Gateway` con `connect() failed (111: Connection refused)`**: el backend no está escuchando en la dirección de `proxy_pass`. Compruébalo con `sudo ss -tlnp | grep 3000` y `curl http://127.0.0.1:3000`. - **`502` con `upstream prematurely closed connection`**: la aplicación ha cerrado la conexión o se ha caído durante la petición. Revisa los logs de la aplicación. - **`504 Gateway Timeout`**: el backend tarda más que `proxy_read_timeout`. Súbelo para esa ruta o haz que la aplicación responda antes. - **`413 Request Entity Too Large`**: aumenta `client_max_body_size`. - **La aplicación genera redirecciones a `http://` o a `127.0.0.1:3000`**: no está leyendo `Host` y `X-Forwarded-Proto`. Comprueba que `include proxy_params;` está en el `location` y que el framework confía en el proxy. - **Las conexiones WebSocket se cierran nada más abrirse**: falta `proxy_http_version 1.1` o las cabeceras `Upgrade` y `Connection`. - **En Rocky Linux o AlmaLinux, `502` con `(13: Permission denied)`**: SELinux impide a Nginx abrir conexiones de red. Permítelo con `sudo setsebool -P httpd_can_network_connect 1`, sin desactivar SELinux. ## Conclusión Has configurado Nginx como proxy inverso delante de una aplicación local, con las cabeceras que la aplicación necesita, HTTPS, soporte para WebSockets, balanceo de carga entre varias instancias y caché de respuestas. La aplicación queda accesible solo a través de Nginx, que concentra TLS, límites y registros. Como siguientes pasos puedes: - Ejecutar tu aplicación como servicio de systemd para que arranque con el servidor y se reinicie si falla. - Limitar el número de peticiones por IP con `limit_req_zone` y `limit_req` para proteger los endpoints sensibles. - Activar HTTP/2 en el bloque del puerto 443.