Web Server

Nginx

Nginx es el servidor web basado en eventos que se sitúa frente a casi todas las aplicaciones de Node, Python y Go en producción. Se encarga de la terminación TLS, sirve archivos estáticos, balancea la carga y protege tu proceso de internet.

intermediate15 min readUpdated 16 sept 2026
sites-available/app
nginx
# /etc/nginx/sites-available/app
server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.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;
  }
}
Lanzamiento
2004
Modelo
Basado en eventos, async
Roles principales
Web server, reverse proxy, load balancer
Lenguaje de config
Bloques declarativos
TLS
Terminado en Nginx
Upstreams típicos
Node, Python, Go, PHP

Por que importa

Lo que Nginx le quita de encima a tu aplicación

Un único punto de entrada para todo

Una sola dirección pública sirve de front para múltiples servicios. Enruta por hostname o ruta hacia diferentes aplicaciones y muévelas sin cambiar el DNS ni el código del cliente.

TLS y seguridad en el edge

Nginx termina HTTPS, redirecciona HTTP simple, añade cabeceras de seguridad y aplica rate limits al tráfico abusivo antes de que llegue a tu proceso.

Entrega rápida de estáticos y caching

Los archivos estáticos y las respuestas cacheadas se sirven desde el disco con sendfile y caching del kernel, lo cual es mucho más eficiente que pedirle a Node que lo haga.

La imagen completa

Las tres funciones de Nginx

Acepta conexiones de internet, decide qué hacer con cada solicitud y gestiona TLS para que tu aplicación no tenga que hacerlo.

El listener

Aceptar

Nginx vincula los puertos, realiza el handshake TLS y lee la solicitud. Tu aplicación nunca ve el socket raw.

El router

Emparejar

server_name selecciona el host virtual, luego los bloques location emparejan la ruta y deciden si servir, hacer proxy, cachear o rechazar.

El upstream

Reenviar

proxy_pass entrega la solicitud a tu aplicación, añade cabeceras de reenvío y transmite la respuesta de vuelta al cliente.

Nginx de un vistazo

Las directivas que más utilizarás

server blocks

Uno por sitio o hostname, seleccionado mediante server_name y puerto.

location blocks

Emparejan un prefijo de ruta o regex y eligen cómo manejarlo.

proxy_pass

Reenvía una solicitud a una aplicación upstream u otro servidor.

TLS

Los certificados, protocolos y ajustes de cifrado residen en el server block.

upstream

Un pool de backends con un método de balanceo y comprobaciones de salud (health checks).

limit_req & proxy_cache

Limitan la tasa de solicitudes de abusadores y cachean respuestas para reducir la carga del origen.

Flujo

El flujo de una solicitud a través del reverse proxy

Cada solicitud sigue el mismo camino, ya sea que termine en un archivo estático o en tu aplicación.

  1. 1

    El cliente se conecta

    Un navegador abre una conexión TCP al puerto 443 en la dirección pública del servidor e inicia un handshake TLS.

  2. 2

    Nginx termina TLS

    Nginx presenta el certificado y desencripta la solicitud, por lo que la aplicación upstream solo maneja HTTP simple.

  3. 3

    Se elige el host virtual

    Nginx empareja la cabecera Host con server_name para elegir el server block correcto para ese dominio.

  4. 4

    Se empareja una ubicación

    La ruta de la solicitud se empareja con los bloques location en orden, y el emparejamiento más específico decide el manejador.

  5. 5

    La solicitud se sirve o se hace proxy

    Nginx devuelve un archivo estático o reenvía la solicitud al upstream definido por proxy_pass.

  6. 6

    Se añaden cabeceras de reenvío

    Host, X-Real-IP, X-Forwarded-For y X-Forwarded-Proto informan a la app sobre el cliente y el esquema reales.

  7. 7

    Se aplican el caching y los límites

    proxy_cache puede resolver la solicitud desde el disco, y limit_req puede rechazarla, antes de que nada llegue al upstream.

  8. 8

    La respuesta retorna

    Nginx transmite la respuesta del upstream de vuelta al cliente, opcionalmente comprimiéndola y añadiendo cabeceras en el camino.

La guia completa

Nginx: Todo lo que necesitas saber

Qué es Nginx en realidad

Nginx (pronunciado “engine-x”) es un servidor web construido sobre una arquitectura asíncrona y basada en eventos. En lugar de asignar un hilo o proceso por conexión, un pequeño número de procesos worker gestionan miles de conexiones simultáneamente reaccionando a eventos. Este diseño es la razón por la cual puede situarse delante de una aplicación lenta sin colapsar, y por qué se convirtió en la puerta de entrada predeterminada de la web moderna.

Desempeña tres roles que es fácil confundir. Es un servidor web estático, que devuelve archivos del disco sin problemas. Es un reverse proxy, que reenvía solicitudes a otro servidor y transmite la respuesta. Y es un load balancer, que distribuye las solicitudes entre un grupo de backends. Delante de una aplicación de Node.js, Python, Go o PHP, utilizarás los tres. Nginx no es parte de tu aplicación: es un proceso independiente con su propia configuración, ciclo de vida y logs.

Por qué colocarlo delante de tu aplicación

Podrías vincular Node directamente al puerto 80. Mucha gente lo hace en los tutoriales y funciona… hasta que deja de funcionar. Colocar Nginx delante te otorga un conjunto de capacidades que serían tediosas o peligrosas de implementar directamente en una aplicación.

Terminación TLS. Nginx gestiona el certificado, realiza el handshake y desencripta. Tu aplicación se comunica mediante HTTP simple en localhost y nunca tiene que gestionar la renovación de certificados. Certbot puede incluso reescribir la configuración por ti.

Archivos estáticos, compresión y almacenamiento en caché. Nginx sirve archivos con sendfile y kernel caching, comprime con gzip o brotli, y puede devolver una respuesta almacenada sin siquiera tocar el upstream.

Limitación de tasa (Rate limiting) y límites de solicitudes. El abuso se detiene en el borde, antes de que consuma el pool de conexiones o una consulta a la base de datos.

Un único punto de entrada. Una sola dirección pública y un certificado pueden dar servicio a múltiples servicios, enrutados por hostname o ruta. Puedes mover una aplicación a otro puerto o host sin cambiar el DNS.

Despliegues sin tiempo de inactividad (Zero-downtime deploys). Un upstream puede vaciar un backend mientras este se reinicia, y la recarga de la configuración es gradual (graceful). Node, expuesto directamente, corta todas las conexiones activas al reiniciarse.

El modelo de configuración

La configuración de Nginx es un árbol de contextos y directivas. Una directiva es un ajuste que termina en punto y coma; un contexto es un bloque encerrado entre llaves que contiene más directivas. Las directivas se heredan hacia abajo, y prevalece el contexto más específico.

# /etc/nginx/nginx.conf (main context)
user  www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
  worker_connections 1024;
}

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;
  sendfile      on;
  keepalive_timeout 65;

  gzip on;
  gzip_types text/css application/javascript application/json;

  # site configs are pulled in here
  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

Los contextos en los que trabajarás son:

  • main — ajustes globales como user y worker_processes.
  • events — cómo se gestionan las conexiones.
  • http — todo lo relacionado con HTTP: tipos MIME, logging, compresión y los includes que importan los archivos del sitio.
  • server — un host virtual, seleccionado mediante listen y server_name.
  • location — una ruta dentro de un server, donde se procesan realmente las solicitudes.

En Debian y Ubuntu, la convención es mantener un archivo por sitio en /etc/nginx/sites-available/ y crear enlaces simbólicos (symlinks) de los activos en /etc/nginx/sites-enabled/. Esto te ofrece una forma sencilla de desactivar un sitio sin tener que borrarlo.

sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
sudo rm /etc/nginx/sites-enabled/app

Las directivas no son un lenguaje de scripting. No existen bucles ni variables en el sentido habitual — solo map, if (usados con moderación) e includes. Si sientes que un problema requiere flujo de control, la respuesta suele ser un map o un cambio en la aplicación.

Tu primer server block

Un bloque server es un host virtual. Escucha en un puerto, responde a uno o más nombres y contiene bloques location.

server {
  listen 80;
  server_name app.example.com www.app.example.com;

  root /var/www/app/dist;
  index index.html;

  location / {
    try_files $uri $uri/ =404;
  }
}

server_name se compara con el encabezado Host. El primer server block de un puerto es el default server y captura las solicitudes cuyo host no coincida con nada más; por lo tanto, conviene configurarlo como un catch-all explícito que devuelva un error 444 o una página de mantenimiento, en lugar de exponer un sitio no deseado.

El orden de coincidencia es importante y suele causar confusiones. Los bloques location se prueban primero como coincidencias exactas (=), luego la coincidencia de prefijo más larga y, finalmente, las expresiones regulares (~ y ~*). Un prefijo ^~ le indica a Nginx que se detenga y utilice ese prefijo sin verificar las expresiones regulares. Cuando el comportamiento parece incorrecto, generalmente se debe a este orden.

location = /favicon.ico { log_not_found off; access_log off; }
location ^~ /assets/    { expires 1y; }
location ~* \.(jpg|png|css|js)$ { expires 30d; }
location /              { try_files $uri /index.html; }

Proxying a aplicación Node.js

La directiva principal es proxy_pass. Apúntala hacia tu aplicación y Nginx se convertirá en un reverse proxy.

upstream app {
  server 127.0.0.1:3000;
  keepalive 32;
}

server {
  listen 80;
  server_name app.example.com;

  location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_read_timeout 30s;
    proxy_connect_timeout 5s;
  }
}

Definir un bloque upstream en lugar de escribir la dirección directamente no es solo para cuando tienes múltiples servidores. Es lo que te permite configurar keepalive, para que Nginx reutilice las conexiones al backend en lugar de abrir una nueva conexión TCP por cada solicitud. proxy_http_version 1.1 es necesario para el keepalive y para los WebSockets.

Los timeouts requieren atención. proxy_connect_timeout limita cuánto tiempo espera Nginx para establecer la conexión; proxy_read_timeout limita cuánto tiempo espera el siguiente byte desde el upstream. El read timeout debe ser superior a tu respuesta legítima más lenta, o de lo contrario Nginx devolverá un 504 mientras la aplicación aún esté procesando.

La regla de la barra final (trailing-slash) es el error clásico. proxy_pass http://app; reenvía la ruta completa sin cambios. proxy_pass http://app/; reemplaza el prefijo de la ubicación coincidente con /. Dentro de location /api/, el primero envía /api/users y el segundo envía /users. Elige con cuidado y revisa el log del upstream.

Reenvío de headers y por qué es importante para tu app

Por defecto, el upstream ve la solicitud como si proviniera de Nginx: la dirección del cliente es 127.0.0.1, el scheme es http y el header Host podría faltar. Esto rompe el logging, las redirecciones, las cookies y el rate limiting dentro de la app.

location / {
  proxy_pass http://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;
  proxy_set_header X-Forwarded-Host  $host;
  proxy_set_header X-Forwarded-Port  $server_port;
}

Cada header tiene su razón de ser:

  • Host preserva el dominio original, para que la app pueda construir URLs absolutas y virtual hosts correctos.
  • X-Real-IP es la dirección inmediata del cliente.
  • X-Forwarded-For se añade a una cadena; $proxy_add_x_forwarded_for mantiene las entradas existentes y añade el cliente actual. Usa la primera entrada como el cliente original y nunca confíes en ella ciegamente.
  • X-Forwarded-Proto le indica a la app si el usuario se conectó a través de HTTPS, lo que soluciona los bucles de redirección y los errores de secure-cookie.
  • X-Forwarded-Host y X-Forwarded-Port son útiles cuando Nginx escucha en un puerto no estándar.

Debes configurar tu framework para que confíe en este proxy. En Express, app.set("trust proxy", 1) hace que req.ip y req.protocol lean los valores reenviados. Si omites esto, cada solicitud parecerá provenir de localhost, lo que romperá silenciosamente la geolocalización y el rate limiting por IP.

Sirviendo archivos estáticos y el fallback de la SPA

Nginx es excelente manejando archivos estáticos, así que deja que se encargue de esa tarea. Una división común es servir el frontend compilado desde el disco y hacer proxy únicamente para la API.

server {
  listen 80;
  server_name app.example.com;
  root /var/www/app/dist;

  location /assets/ {
    try_files $uri =404;
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  location /api/ {
    proxy_pass http://app;
    proxy_set_header Host $host;
  }

  location / {
    try_files $uri /index.html;
  }
}

Para una aplicación de una sola página (SPA), try_files $uri /index.html es el fallback que permite que el enrutamiento del lado del cliente funcione: si la ruta solicitada no es un archivo, devuelve index.html y deja que el router lo gestione. Para una API, es mejor usar el mismo patrón con =404, ya que devolver HTML para un endpoint inexistente oculta errores.

Los assets que tienen hashes de contenido en sus nombres pueden cachearse indefinidamente: immutable y una expiración de un año son seguros porque cualquier cambio genera un nuevo nombre de archivo. index.html nunca debe cachearse de esa manera, o los usuarios seguirán cargando una versión antigua de la aplicación.

TLS con Let’s Encrypt y certbot

Certbot obtiene certificados gratuitos de Let’s Encrypt y puede configurar Nginx automáticamente. Instala el plugin, ejecútalo una vez por dominio y él se encargará del resto.

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com -d www.app.example.com
sudo certbot renew --dry-run

Certbot escribe las rutas de los certificados en tu server block e instala un temporizador de renovación. Los certificados duran 90 días; la renovación es automática, pero solo funciona si la verificación de renovación puede alcanzar tu servidor a través del puerto 80, así que no lo bloquees completamente con el firewall.

El bloque resultante se ve así, con un servidor HTTP que redirecciona y un servidor HTTPS que termina la conexión.

server {
  listen 80;
  server_name app.example.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
  ssl_protocols       TLSv1.2 TLSv1.3;
  ssl_session_cache   shared:SSL:10m;
  ssl_session_timeout 1d;

  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;

  location / {
    proxy_pass http://app;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

TLSv1.2 y TLSv1.3 son los únicos protocolos que vale la pena habilitar hoy en día. HSTS indica a los navegadores que rechacen el HTTP simple durante un año, así que actívalo solo cuando estés seguro de que HTTPS funciona en todas partes; es difícil de revertir.

HTTP/2 y HTTP/3

HTTP/2 multiplexa múltiples solicitudes a través de una sola conexión y se habilita por servidor. En versiones modernas de Nginx (1.25.1 y posteriores), la directiva es http2 on;. En versiones anteriores, es un flag en la línea listen: listen 443 ssl http2;.

server {
  listen 443 ssl;
  http2 on;
  # ...
}

HTTP/3 se ejecuta sobre QUIC y UDP. Requiere una compilación con el módulo QUIC, una línea listen 443 quic reuseport; independiente y un encabezado Alt-Svc: h3=":443"; ma=86400 para anunciarlo. Considéralo como una optimización para añadir una vez que HTTP/2 sea estable, no como un primer paso. Los navegadores negocian ambos automáticamente a través de TLS, por lo que no hay nada que cambiar en tu app.

Métodos de balanceo de carga

Un bloque upstream define un pool, y el método de balanceo decide qué servidor recibe la siguiente solicitud. El valor predeterminado es round-robin.

upstream app {
  least_conn;
  server 10.0.0.11:3000 weight=2 max_fails=3 fail_timeout=15s;
  server 10.0.0.12:3000;
  server 10.0.0.13:3000 backup;
  keepalive 64;
}
  • Round-robin (predeterminado) rota entre los servidores. Es simple y equitativo cuando las solicitudes tienen un costo similar.
  • least_conn envía la solicitud al servidor con menos conexiones activas. Es mejor cuando la duración de las solicitudes varía considerablemente, algo común en las API.
  • ip_hash vincula un cliente a un servidor específico mediante el hash de la dirección. Es útil para sesiones en memoria, aunque a costa de una carga desigual y la pérdida de persistencia si un servidor falla.
  • weight sesga el tráfico hacia una máquina más potente o durante un despliegue canary.
  • backup marca un servidor para que solo reciba tráfico cuando los primarios estén caídos.

max_fails y fail_timeout proporcionan un chequeo de salud pasivo: tras max_fails fallos dentro de fail_timeout, Nginx marca el servidor como caído durante ese intervalo. proxy_next_upstream decide entonces qué fallos se reintentan en otro backend.

proxy_next_upstream error timeout http_502 http_503 http_504;

Reintentar es potente pero no es gratuito: solo tiene sentido para solicitudes idempotentes. No reintentes a ciegas un POST que pueda haber cambiado ya el estado, a menos que el upstream sea idempotente por diseño.

Health checks y manejo de fallos

Nginx OSS cuenta con comprobaciones pasivas; las comprobaciones de salud activas (health_check con match) requieren Nginx Plus. Las comprobaciones pasivas junto con un endpoint a nivel de aplicación suelen ser suficientes para la mayoría de los equipos: expón /healthz que verifique las dependencias necesarias —base de datos, caché— y responda rápidamente, y luego apunta la liveness probe de tu plataforma hacia él. Úsalo para controlar los despliegues iniciando un nuevo backend, esperando hasta que esté saludable y luego eliminando el anterior del upstream.

Dado que las recargas de configuración son graduales (graceful), el patrón para lograr cero tiempo de inactividad consiste en editar el upstream para que apunte a la nueva instancia, nginx -t, recargar y luego vaciar (drain) y detener la instancia antigua.

Almacenamiento en caché con proxy_cache

proxy_cache almacena las respuestas del upstream en disco y las sirve sin contactar al backend. Para los endpoints con alta carga de lectura que cambian lentamente, esta es la mejora individual más significativa que se puede implementar en el edge.

proxy_cache_path /var/cache/nginx levels=1:2
                 keys_zone=app:10m max_size=1g inactive=60m;

server {
  listen 80;
  server_name app.example.com;

  location /api/public/ {
    proxy_pass http://app;
    proxy_cache app;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    proxy_cache_valid 200 302 10s;
    proxy_cache_valid 404 1m;
    proxy_cache_bypass $http_authorization $cookie_session;
    proxy_no_cache $http_authorization;
    add_header X-Cache-Status $upstream_cache_status;
  }
}

proxy_cache_valid define cuánto tiempo se almacena en caché cada código de estado. proxy_cache_bypass omite la caché para las solicitudes que llevan un encabezado de sesión o de autorización; proxy_no_cache evita almacenar sus respuestas, asegurando que los datos privados de un usuario nunca se sirvan a otro. El encabezado X-Cache-Status (HIT, MISS, BYPASS, EXPIRED) es la forma más rápida de comprobar que la caché funciona.

Dos advertencias. Almacena en caché únicamente las respuestas que sea seguro compartir —datos públicos y no personalizados— y asegúrate de que el upstream envíe un encabezado Cache-Control compatible con tu configuración. Una clave de caché que ignore una variante (un encabezado de idioma, un parámetro de consulta) servirá alegremente el contenido incorrecto.

Rate limiting con limit_req

El rate limiting protege los endpoints de login, las búsquedas y las APIs públicas contra el abuso y las inundaciones accidentales de tráfico. Se define en el contexto http y se aplica en una location.

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_req_status 429;

server {
  listen 80;
  server_name app.example.com;

  location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://app;
  }

  location = /api/login {
    limit_req zone=login burst=5 nodelay;
    proxy_pass http://app;
  }
}

La clave de la zona suele ser $binary_remote_addr (la IP del cliente), y la tasa se define en peticiones por segundo o por minuto. burst permite picos cortos de tráfico, y nodelay procesa el burst inmediatamente en lugar de espaciarlo, que es lo ideal para endpoints interactivos. Sin nodelay, las peticiones excedentes se retrasan, lo que puede hacer que la UI se sienta lenta en lugar de simplemente devolver un 429.

Cuando hay otro proxy o un CDN delante, $binary_remote_addr puede ser la dirección del proxy, por lo que el limitador tratará a todos los usuarios como un único cliente. Soluciona esto configurando set_real_ip_from y real_ip_header para que Nginx vea al cliente real. Además, devuelve 429 en lugar del 503 predeterminado para que los clientes puedan distinguir el rate limiting de una caída del servicio.

Upgrade de WebSocket

Una conexión WebSocket comienza como una solicitud HTTP con un encabezado Upgrade y luego se convierte en un flujo bidireccional de larga duración. Para realizar el proxy de esta conexión, se necesitan los encabezados de upgrade y un valor Connection que depende de la solicitud, por lo que se utiliza un map.

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;

  location /ws/ {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host       $host;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
  }
}

El timeout de lectura prolongada es importante: un WebSocket inactivo con un timeout corto es cerrado por Nginx, y el cliente experimenta una desconexión misteriosa. Ten en cuenta que HTTP/2 no transporta WebSocket de la misma manera; los navegadores abren una conexión HTTP/1.1 independiente para el upgrade, la cual Nginx gestiona automáticamente en el mismo puerto.

Ejecutando Nginx en Docker

La imagen oficial de nginx es una forma conveniente de distribuir la configuración y los archivos estáticos con tu aplicación. La única regla es que el proceso principal del contenedor debe permanecer en primer plano, por lo que el comando termina con daemon off;.

# Dockerfile
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/app.conf
COPY dist/ /usr/share/nginx/html/
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

En Compose, el servicio de la aplicación es accesible mediante su nombre de servicio, por lo que el host upstream es simplemente app. Nginx inicia antes de que la aplicación esté lista, así que puedes añadir un healthcheck y hacer que dependa de él, o dejar que Nginx reintente la conexión.

# docker-compose.yml
services:
  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/app.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      app:
        condition: service_healthy
  app:
    build: .
    expose:
      - "3000"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
      interval: 5s
      retries: 10

Ten en cuenta el uso de expose en lugar de ports en la aplicación: esta solo debe ser accesible dentro de la red de Compose, nunca directamente desde el host. El upstream de la configuración de Nginx pasa a ser server app:3000;.

Pruebas, recarga y cambios de configuración sin tiempo de inactividad

Cada cambio de configuración debe seguir el mismo ciclo: validar, recargar y verificar. Una recarga (reload) es gradual; un reinicio (restart) no lo es.

sudo nginx -t                 # test the configuration
sudo nginx -T                 # test and dump the full effective config
sudo systemctl reload nginx   # graceful, no dropped connections
sudo systemctl status nginx
curl -I https://app.example.com

nginx -T es una herramienta infrautilizada y extremadamente útil: imprime la configuración combinada exactamente como la ve Nginx, lo que resuelve cualquier duda sobre la herencia y los includes. Si -t falla, el error indica el archivo y la línea exacta.

Al recargar, mantén una terminal abierta en una segunda sesión para que una configuración errónea no te deje fuera del sistema. En un contenedor, el equivalente es enviar SIGHUP al proceso maestro o ejecutar nginx -s reload. Nunca reinicies un Nginx de producción con tráfico para aplicar un cambio de configuración cuando una recarga es suficiente.

Mejores prácticas

  • Ejecuta nginx -t antes de cada recarga; nunca edites y reinicies a ciegas.
  • Mantén un archivo por sitio y actívalo mediante un symlink para que los cambios sean reversibles.
  • Define un bloque upstream con keepalive en lugar de escribir la dirección del backend directamente (inline).
  • Reenvía siempre Host, X-Real-IP, X-Forwarded-For y X-Forwarded-Proto, y configura trust proxy en la aplicación.
  • Sirve los assets estáticos y los bundles hasheados desde el disco con cabeceras de caché largas e inmutables.
  • Redirige HTTP a HTTPS y activa HSTS solo después de haber comprobado que HTTPS funciona correctamente.
  • Almacena en caché únicamente respuestas públicas y no personalizadas, y nunca crees una clave de caché sin sus variantes.
  • Implementa rate limit en el login y en los endpoints públicos, y devuelve un 429 en lugar de un 503.
  • Utiliza timeouts largos para las ubicaciones de WebSocket y cortos para los health checks.
  • Mantén los secretos y certificados fuera de las imágenes; móntalos en modo lectura durante el runtime.

Errores comunes

  • Omitir nginx -t y tirar el sitio con un error de sintaxis.
  • Olvidar X-Forwarded-Proto y terminar depurando un bucle infinito de redirecciones HTTPS.
  • No habilitar trust proxy en la aplicación, haciendo que cada solicitud parezca provenir de localhost.
  • Confundir proxy_pass con y sin la barra final (trailing slash) y recibir errores 404.
  • Configurar proxy_read_timeout con un tiempo menor que una respuesta lenta legítima y devolver errores 504.
  • Cachear respuestas personalizadas y servir los datos de un usuario a otro.
  • Hacer proxy de WebSockets sin el map de upgrade, provocando que las conexiones se caigan inmediatamente.
  • Dejar el servidor por defecto como el primer bloque de sitio y exponer una aplicación no deseada.
  • Habilitar HSTS antes de que HTTPS funcione en todas partes y bloquear el acceso a los usuarios durante un año.
  • Exponer el servicio de la aplicación con ports en Compose en lugar de expose.

Próximos pasos

Nginx es la puerta de entrada, y la guía de HTTP / HTTPS explica el protocolo que termina y redirige. Para ejecutarlo junto a tu aplicación de forma reproducible, lee Docker & Deployment, y para mantener saludable el host donde se ejecuta, vuelve a consultar Linux. Cuando estés listo para lanzarlo a internet con certificados, DNS y autoscaling, la guía de Cloud Deployment une todas las piezas.

En la practica

Las cuatro configuraciones que más escribirás

Empieza con un proxy, añade TLS, escala a un pool y luego implementa caching y rate limiting.

sites-available/app
upstream app {
  server 127.0.0.1:3000;
  keepalive 32;
}

server {
  listen 80;
  server_name app.example.com;

  location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    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_read_timeout 30s;
  }
}

Reverse proxy vs exponer la app directamente

Ejecutar Node en el puerto 80 implica renunciar a TLS, caching de estáticos, rate limiting y la capacidad de desplegar sin tiempo de inactividad. Nginx cuesta un proceso y te da todo ello.

Preferir
server {
  listen 443 ssl;
  server_name app.example.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
  }
}
Evitar
# Node listening on :80 directly
# - no TLS termination
# - no static file cache
# - no rate limiting
# - restart drops connections
# - one process owns the port

ip_hash vs round-robin

Round-robin distribuye la carga uniformemente. ip_hash ancla un cliente a un backend, lo cual es útil para sesiones persistentes (sticky sessions) pero crea una carga desigual y pierde ese anclaje si el servidor cae.

Carga uniforme
upstream app {
  least_conn;
  server 10.0.0.11:3000;
  server 10.0.0.12:3000;
}
# Stateless apps and shared
# session stores scale cleanly.
Sesiones persistentes
upstream app {
  ip_hash;
  server 10.0.0.11:3000;
  server 10.0.0.12:3000;
}
# Pins clients, but a restart
# reshuffles their sessions.

Compromisos

¿Vale la pena Nginx como proceso extra?

Nginx añade un lenguaje de configuración y un segundo elemento que desplegar. Para cualquier servicio público, las funciones de edge compensan ese coste rápidamente.

Strengths

  • Seguridad en el edge

    TLS, cabeceras de seguridad, límites de tamaño de solicitud y rate limiting se aplican antes de que la solicitud llegue al código de la aplicación, exponiendo menos los bugs.

  • Rendimiento gratuito

    Los archivos estáticos, gzip y las respuestas cacheadas son servidos por un servidor en C basado en eventos, liberando el hilo único de Node para el trabajo real.

  • Despliegues sin tiempo de inactividad

    Recargar Nginx es un proceso fluido, y un upstream puede vaciar un backend mientras se reinicia, por lo que los usuarios nunca ven un 'connection refused'.

Trade-offs

  • Otra herramienta que aprender y operar

    La sintaxis de configuración, las reglas de orden y la semántica de recarga tienen su complejidad. Un error tipográfico puede tirar un sitio si omites nginx -t.

  • La terminación TLS mueve el límite

    El tráfico entre Nginx y el upstream es HTTP simple. En un host o red compartida, cifra o aísla también ese salto.

  • Puede ocultar problemas de la aplicación

    El caching y el buffering enmascaran orígenes lentos, y las cabeceras de reenvío son fáciles de olvidar, lo que lleva a IPs de cliente incorrectas y redirecciones rotas.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Nginx / Reverse Proxy?

Nuestro tutorial interactivo te guia a traves de Nginx / Reverse Proxy paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.