Saltar a contenido

⚙️ Nginx - Proxy Reverso

Configuración de Nginx como proxy reverso para enrutar subdominios a contenedores Docker.


Índice


Instalación

Instalar Nginx

sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx

Verificar instalación

sudo systemctl status nginx

Resultado esperado:

● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded
   Active: active (running)

Abrir puertos en UFW

sudo ufw allow 'Nginx Full'
sudo ufw status

"Nginx Full" permite: - Puerto 80 (HTTP) - Puerto 443 (HTTPS)

Probar Nginx

Abre un navegador y ve a http://YOUR_SERVER_IP

✅ Deberías ver la página: "Welcome to nginx!"


Conceptos Básicos

¿Qué es un Proxy Reverso?

Nginx actúa como intermediario entre Internet y tus aplicaciones:

Internet → Nginx → Aplicación Docker (puerto local)

Ventajas: - ✅ Un solo servidor maneja múltiples dominios/subdominios - ✅ SSL/TLS centralizado - ✅ Balanceo de carga - ✅ Caché - ✅ Compresión - ✅ Seguridad adicional

Flujo de una petición

https://labs.josejordan.dev
    Cloudflare
    Nginx (puerto 443)
    Comprueba server_name
    Redirige a localhost:3000
    Contenedor Docker

Estructura de Archivos

Directorios principales

/etc/nginx/
├── nginx.conf              # Configuración principal (no tocar)
├── sites-available/        # Configuraciones disponibles
   ├── default            # Sitio por defecto
   ├── labs               # Tu subdominio labs
   └── api                # Tu subdominio api
├── sites-enabled/          # Configuraciones activas (symlinks)
   ├── default -> ../sites-available/default
   ├── labs -> ../sites-available/labs
   └── api -> ../sites-available/api
└── snippets/              # Fragmentos reutilizables

Flujo de trabajo

  1. Crear configuración en sites-available/
  2. Habilitar creando symlink en sites-enabled/
  3. Probar configuración con nginx -t
  4. Recargar Nginx con systemctl reload nginx

Configuración de Virtual Hosts

Template básico (HTTP)

server {
  listen 80;
  listen [::]:80;
  server_name subdominio.ejemplo.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Template completo (HTTP + HTTPS)

# HTTP -> redirige a HTTPS
server {
  listen 80;
  listen [::]:80;
  server_name labs.josejordan.dev;
  return 301 https://$host$request_uri;
}

# HTTPS + proxy
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name labs.josejordan.dev;

  # Certificados SSL
  ssl_certificate     /etc/letsencrypt/live/labs.josejordan.dev/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/labs.josejordan.dev/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;

  # IP real detrás de Cloudflare
  real_ip_header CF-Connecting-IP;

  # Forzar no-caché para apps dinámicas
  proxy_hide_header Cache-Control;
  proxy_hide_header Expires;
  proxy_hide_header Pragma;
  add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
  add_header Pragma "no-cache" always;
  add_header Expires "0" always;

  # Reverse proxy
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host              $host;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection "upgrade";
  }

  # Cabeceras de seguridad
  add_header X-Frame-Options "SAMEORIGIN" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  # HSTS (activar cuando todo sea 100% HTTPS):
  # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

Crear y habilitar sitio

# Crear archivo de configuración
sudo nano /etc/nginx/sites-available/nombre-subdominio

# Habilitar el sitio (crear symlink)
sudo ln -s /etc/nginx/sites-available/nombre-subdominio /etc/nginx/sites-enabled/

# Verificar configuración
sudo nginx -t

# Recargar Nginx
sudo systemctl reload nginx

Deshabilitar sitio default (recomendado)

El sitio default puede causar conflictos:

# Deshabilitar sitio default
sudo rm /etc/nginx/sites-enabled/default

# Recargar Nginx
sudo systemctl reload nginx

Cabeceras y Proxy

Cabeceras básicas de proxy

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;

Explicación: - Host: Dominio solicitado - X-Real-IP: IP del cliente - X-Forwarded-For: Cadena de IPs (proxies) - X-Forwarded-Proto: http o https

Para WebSockets y Next.js

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

IP real detrás de Cloudflare

real_ip_header CF-Connecting-IP;

Esto hace que Nginx vea la IP real del visitante, no la de Cloudflare.

Cabeceras de seguridad

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;

HSTS (HTTP Strict Transport Security)

Solo activar cuando todo funcione 100% con HTTPS:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Advertencia: Una vez activado, el navegador SOLO aceptará HTTPS durante max-age segundos (1 año en el ejemplo).

Forzar no-caché (apps dinámicas)

proxy_hide_header Cache-Control;
proxy_hide_header Expires;
proxy_hide_header Pragma;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;

Comandos Útiles

Gestión del servicio

# Ver estado
sudo systemctl status nginx

# Iniciar
sudo systemctl start nginx

# Parar
sudo systemctl stop nginx

# Reiniciar (con downtime)
sudo systemctl restart nginx

# Recargar configuración (sin downtime)
sudo systemctl reload nginx

# Habilitar al inicio
sudo systemctl enable nginx

# Deshabilitar al inicio
sudo systemctl disable nginx

Validación y testing

# Verificar sintaxis de configuración
sudo nginx -t

# Ver configuración parseada
sudo nginx -T

# Ver versión de Nginx
nginx -v

# Ver versión y módulos compilados
nginx -V

Gestión de sitios

# Ver sitios disponibles
ls -la /etc/nginx/sites-available/

# Ver sitios habilitados
ls -la /etc/nginx/sites-enabled/

# Habilitar sitio
sudo ln -s /etc/nginx/sites-available/nombre /etc/nginx/sites-enabled/

# Deshabilitar sitio
sudo rm /etc/nginx/sites-enabled/nombre

# Ver configuración de un sitio
sudo cat /etc/nginx/sites-available/nombre

Logs

# Ver logs de acceso en tiempo real
sudo tail -f /var/log/nginx/access.log

# Ver logs de errores en tiempo real
sudo tail -f /var/log/nginx/error.log

# Ver últimas 50 líneas de errores
sudo tail -n 50 /var/log/nginx/error.log

# Buscar errores específicos
sudo grep "error" /var/log/nginx/error.log

# Ver logs por sitio (si se configuran logs personalizados)
sudo tail -f /var/log/nginx/labs-access.log

Testing

# Probar desde el VPS
curl http://localhost
curl -H "Host: labs.josejordan.dev" http://localhost

# Ver headers de respuesta
curl -I https://labs.josejordan.dev

# Probar SSL
curl -v https://labs.josejordan.dev

# Test completo con tiempos
curl -w "@-" -o /dev/null -s https://labs.josejordan.dev <<'EOF'
    time_namelookup:  %{time_namelookup}s\n
       time_connect:  %{time_connect}s\n
    time_appconnect:  %{time_appconnect}s\n
      time_redirect:  %{time_redirect}s\n
   time_pretransfer:  %{time_pretransfer}s\n
 time_starttransfer:  %{time_starttransfer}s\n
                    ----------\n
         time_total:  %{time_total}s\n
EOF

Buenas Prácticas

1. Siempre verificar antes de recargar

sudo nginx -t && sudo systemctl reload nginx

Si la configuración tiene errores, Nginx NO se recargará y mantendrá la configuración anterior.

2. Mantener sesión SSH abierta

Cuando hagas cambios importantes (SSL, redirects, etc.), mantén una sesión SSH abierta por si algo falla.

3. Usar reload en lugar de restart

# ✅ Recomendado (sin downtime)
sudo systemctl reload nginx

# ❌ Evitar (corta conexiones)
sudo systemctl restart nginx

4. Logs personalizados por sitio

server {
  server_name labs.josejordan.dev;

  access_log /var/log/nginx/labs-access.log;
  error_log /var/log/nginx/labs-error.log;

  # ... resto de configuración
}

5. Snippets para configuraciones reutilizables

Crear archivo /etc/nginx/snippets/proxy-params.conf:

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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Luego incluirlo:

location / {
  proxy_pass http://127.0.0.1:3000;
  include snippets/proxy-params.conf;
}

6. Límites de tamaño para uploads

server {
  # Permitir uploads de hasta 50MB
  client_max_body_size 50M;

  # ...
}

7. Timeouts para proxies

location / {
  proxy_pass http://127.0.0.1:3000;

  # Timeouts (útil para APIs lentas)
  proxy_connect_timeout 60s;
  proxy_send_timeout 60s;
  proxy_read_timeout 60s;
}

Configuración de Ejemplo Real

Archivo: /etc/nginx/sites-available/labs

# HTTP -> HTTPS redirect
server {
  listen 80;
  listen [::]:80;
  server_name labs.josejordan.dev;
  return 301 https://$host$request_uri;
}

# HTTPS server
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name labs.josejordan.dev;

  # SSL
  ssl_certificate     /etc/letsencrypt/live/labs.josejordan.dev/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/labs.josejordan.dev/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

  # Logs
  access_log /var/log/nginx/labs-access.log;
  error_log /var/log/nginx/labs-error.log;

  # Cloudflare
  real_ip_header CF-Connecting-IP;

  # No cache
  proxy_hide_header Cache-Control;
  add_header Cache-Control "no-store, no-cache" always;

  # Proxy
  location / {
    proxy_pass http://127.0.0.1:3000;
    include snippets/proxy-params.conf;
  }

  # Security headers
  add_header X-Frame-Options "SAMEORIGIN" always;
  add_header X-Content-Type-Options "nosniff" always;
}

Troubleshooting

Nginx no arranca

# Ver qué está usando el puerto 80
sudo netstat -tulpn | grep :80
# o
sudo ss -tulpn | grep :80

# Ver logs de error
sudo tail -f /var/log/nginx/error.log

# Verificar configuración
sudo nginx -t

Ver "Welcome to nginx!" en lugar del contenedor

El sitio default está capturando las peticiones:

sudo rm /etc/nginx/sites-enabled/default
sudo systemctl reload nginx

Error "conflicting server name"

Dos sitios usan el mismo server_name:

# Ver todos los server_name configurados
sudo grep -r "server_name" /etc/nginx/sites-enabled/

Cambios no se aplican

# Verificar sintaxis primero
sudo nginx -t

# Recargar (no restart)
sudo systemctl reload nginx

# Si no funciona, reiniciar
sudo systemctl restart nginx

# Ver logs
sudo tail -f /var/log/nginx/error.log

⬅️ Anterior: Docker | Volver al índice | ➡️ Siguiente: SSL y Cloudflare