Qué es un proxy inverso
Un proxy inverso recibe las peticiones de los clientes y las reenvía al servidor interno correspondiente. El cliente nunca se conecta directamente al backend. Esto permite centralizar HTTPS, añadir cabeceras de seguridad, cachear contenido y distribuir carga. Es habitual usarlo delante de servicios autoalojados como Nextcloud o Gitea para exponerlos con un dominio propio y HTTPS en vez de un puerto suelto.
Clientes (HTTPS, 443)
│
▼
Nginx — proxy inverso, certificado TLS único
│
├── app.ejemplo.com → 127.0.0.1:3000 (Nextcloud, Gitea...)
├── api.ejemplo.com → 127.0.0.1:8080
└── otro.ejemplo.com → upstream backend_app (varios backends,
balanceo de carga)
Instalación
# RHEL / Oracle Linux / Rocky
sudo dnf install nginx
# Ubuntu/Debian
sudo apt install nginx
sudo systemctl enable --now nginx
Configuración básica
Un proxy que redirige todo el tráfico de un dominio a una aplicación interna en el puerto 3000:
server {
listen 80;
server_name app.ejemplo.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $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;
}
}
Las cabeceras X-Real-IP y X-Forwarded-For permiten que la aplicación conozca la IP real del cliente en lugar de ver siempre 127.0.0.1.
Añadir HTTPS con Let’s Encrypt
Aquí el flujo específico para Nginx; para entender cómo funciona Certbot y la renovación automática en más detalle, consulta la guía de certificados SSL con Certbot y Let’s Encrypt.
# RHEL/Rocky/Oracle Linux
sudo dnf install epel-release -y
# En RHEL/Rocky 9 puede ser necesario habilitar también el repositorio crb:
# sudo dnf config-manager --set-enabled crb
sudo dnf install certbot python3-certbot-nginx
sudo certbot --nginx -d app.ejemplo.com
Certbot modifica automáticamente la configuración de Nginx para escuchar en el puerto 443 y redirigir el tráfico HTTP a HTTPS.
Para renovar automáticamente:
sudo systemctl enable --now certbot.timer
Múltiples aplicaciones en un servidor
# App principal
server {
listen 443 ssl;
server_name app.ejemplo.com;
ssl_certificate /etc/letsencrypt/live/app.ejemplo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.ejemplo.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $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;
}
}
# API
server {
listen 443 ssl;
server_name api.ejemplo.com;
ssl_certificate /etc/letsencrypt/live/api.ejemplo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.ejemplo.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $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;
}
}
Balanceo de carga
Si tienes varios backends para la misma aplicación:
upstream backend_app {
server 192.168.1.10:3000;
server 192.168.1.11:3000;
server 192.168.1.12:3000 backup;
}
server {
listen 443 ssl;
server_name app.ejemplo.com;
location / {
proxy_pass http://backend_app;
proxy_set_header Host $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;
}
}
El tercer servidor con backup solo recibe tráfico si los dos primeros están caídos.
Subidas grandes y timeouts
Dos ajustes por defecto de Nginx sorprenden en cuanto proxeas algo más que una web ligera: el tamaño máximo de subida (client_max_body_size) es de solo 1 MB, y los timeouts de proxy son de 60 segundos. Detrás de un Nextcloud, por ejemplo, ambos límites se alcanzan enseguida — el primero al subir cualquier archivo de más de 1 MB (error 413), el segundo en operaciones largas como sincronizar una carpeta con muchos archivos:
server {
listen 443 ssl;
server_name app.ejemplo.com;
client_max_body_size 512M;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_set_header Host $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;
}
}
client_max_body_size puede ajustarse en http, server o location — cuanto más cerca del location concreto que lo necesita, menos abres el límite en el resto del sitio.
Limitar la tasa de peticiones
Para frenar abuso o fuerza bruta contra un endpoint concreto (un login, una API pública) sin depender solo de fail2ban a nivel de conexión:
# en el bloque http {} — define la zona compartida y el límite
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
server {
# ...
location /login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}
rate=1r/s fija el ritmo sostenido permitido por IP; burst=5 deja pasar hasta 5 peticiones de golpe por encima de ese ritmo antes de empezar a rechazar, y nodelay las procesa inmediatamente en vez de encolarlas artificialmente.
Compresión con gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1024;
Comprime las respuestas de texto antes de enviarlas, reduciendo el tráfico en respuestas de API y assets estáticos que el backend no comprima ya por su cuenta. gzip_min_length evita gastar CPU comprimiendo respuestas tan pequeñas que no compensa.
Cabeceras de seguridad
Añade estas cabeceras a nivel global o por servidor:
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
## Nota
No incluyas
X-XSS-Protection: es una cabecera deprecada que los navegadores modernos ignoran (Chrome eliminó su filtro XSS en 2019, Firefox nunca lo implementó, Safari lo retiró en 2022), y en algunos casos llegó a introducir vulnerabilidades XSS en sitios que sin ella eran seguros. La protección real hoy la da unaContent-Security-Policybien configurada.
WebSockets
Si tu aplicación usa WebSockets, necesitas pasar las cabeceras de upgrade:
location /ws {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
Verificar la configuración
## Importante
Ejecuta siempre
nginx -tantes de recargar. Si la configuración tiene un error de sintaxis y recargas a ciegas, nginx puede fallar al aplicar los cambios y tumbar todos los sitios que proxea, no solo el que estabas editando.
sudo nginx -t
sudo systemctl reload nginx
502 Bad Gateway: las dos causas más comunes
Un 502 significa que Nginx sí recibió la petición, pero no consiguió comunicarse con el backend. Antes de sospechar de la aplicación:
- El backend no está escuchando — confírmalo con
curl http://127.0.0.1:3000desde el propio servidor; si falla ahí, el problema es la aplicación o su puerto, no Nginx. - SELinux bloqueando la conexión saliente (RHEL/Rocky/Oracle Linux con SELinux en modo
enforcing) — la política por defecto no permite que Nginx abra conexiones de red salientes hacia el backend. Se ve en el log de Nginx comoconnect() failed (13: Permission denied), y se soluciona habilitando el booleano correspondiente:
sudo setsebool -P httpd_can_network_connect 1
## Nota
httpd_can_network_connectpermite conectar a cualquier puerto TCP; si tu backend corre en un puerto ya etiquetado como HTTP (80, 443, 8008, 8443, 9000), el booleano más restrictivohttpd_can_network_relaycubre el caso sin abrir tanto. Para el resto de puertos (3000, 5000, 8080…) necesitashttpd_can_network_connect.
Conclusión
Nginx como proxy inverso es el estándar para exponer aplicaciones internas de forma segura. Con HTTPS centralizado, cabeceras de seguridad y balanceo de carga, un solo Nginx puede gestionar decenas de servicios internos sin que cada uno tenga que preocuparse por certificados o seguridad a nivel de transporte.
## Nota
✍️ Transparencia: Este artículo ha sido creado con el apoyo de herramientas de inteligencia artificial. Toda la información técnica ha sido revisada y validada por el autor antes de su publicación.