Despliegue práctico de una aplicación web detrás de Nginx

Capítulo 10

Tiempo estimado de lectura: 8 minutos

+ Ejercicio

Objetivo del proyecto final

Vas a desplegar una aplicación web completa donde Nginx cumple dos roles en un mismo sitio: (1) servir el frontend estático (HTML/CSS/JS) y (2) actuar como reverse proxy hacia una API. Opcionalmente, la API podrá estar replicada en varios nodos para balanceo. El resultado será una configuración mantenible (archivos por sitio), con endpoints claros, políticas básicas de caché/seguridad y un flujo de verificación y rollback seguro.

Arquitectura y endpoints

Componentes

  • Nginx: punto de entrada HTTP/HTTPS, terminación TLS, caché de estáticos, proxy a la API.
  • Frontend: archivos estáticos en disco (por ejemplo, build de React/Vue/Angular o HTML simple).
  • API: servicio(s) backend escuchando en localhost o en red privada (por ejemplo, 127.0.0.1:3000 o 10.0.0.10:3000).

Endpoints principales recomendados

  • /: frontend (SPA o sitio estático).
  • /assets/ o /_static/: recursos versionados (caché agresiva).
  • /api/: proxy hacia backend(s) API.
  • /health: salud del edge (Nginx) o passthrough a la API (según tu diseño).

Estructura limpia de configuración (archivos por sitio)

La idea es que el sitio tenga su propio archivo y que los fragmentos reutilizables (cabeceras comunes, parámetros de proxy, etc.) vivan en snippets/. Un esquema típico:

/etc/nginx/  ├─ nginx.conf  ├─ conf.d/  ├─ sites-available/  │   └─ app.example.com.conf  ├─ sites-enabled/  │   └─ app.example.com.conf -> ../sites-available/app.example.com.conf  └─ snippets/      ├─ proxy_api.conf      ├─ security_headers.conf      └─ ssl_params.conf

Si tu distribución no usa sites-available/sites-enabled, puedes adaptar el patrón con conf.d/, pero mantén el principio: un archivo por sitio y snippets para piezas repetidas.

Preparación del proyecto (rutas y permisos)

1) Directorios de despliegue

Ejemplo de rutas (ajústalas a tu entorno):

  • Frontend: /var/www/app/current
  • Releases (para rollback): /var/www/app/releases/2026-02-03_1200
  • Logs del sitio (opcional): /var/log/nginx/app/

Un enfoque práctico es desplegar el frontend en un directorio de release y luego apuntar current a ese release mediante un symlink. Así puedes revertir rápido sin tocar Nginx.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

2) Variables de entorno (mentalmente) y puertos

  • API en un nodo: 127.0.0.1:3000
  • API con balanceo: 10.0.0.10:3000, 10.0.0.11:3000, 10.0.0.12:3000

Configuración del sitio: estáticos + API (con opción de balanceo)

1) Snippet de proxy para la API

Crea /etc/nginx/snippets/proxy_api.conf con parámetros consistentes para el proxy:

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_connect_timeout 5s;proxy_send_timeout 30s;proxy_read_timeout 30s;proxy_buffering on;proxy_buffers 16 16k;proxy_buffer_size 16k;

Esto estandariza cabeceras y timeouts. Si tu API usa WebSockets, añade Upgrade/Connection en un snippet específico para ese endpoint.

2) Snippet de cabeceras básicas

Crea /etc/nginx/snippets/security_headers.conf:

add_header X-Content-Type-Options nosniff always;add_header X-Frame-Options DENY always;add_header Referrer-Policy strict-origin-when-cross-origin always;

Para una SPA moderna, Content-Security-Policy requiere ajuste fino; si no lo tienes probado, es preferible no bloquear por defecto en este proyecto final.

3) (Opcional) Upstream para balanceo de la API

En el archivo del sitio o en un archivo incluido, define un upstream. Ejemplo:

upstream api_upstream {    least_conn;    server 10.0.0.10:3000 max_fails=3 fail_timeout=10s;    server 10.0.0.11:3000 max_fails=3 fail_timeout=10s;    server 10.0.0.12:3000 max_fails=3 fail_timeout=10s;    keepalive 32;}

Si solo tienes una API local, omite el upstream y usa proxy_pass http://127.0.0.1:3000;.

4) Archivo del sitio (server block)

Crea /etc/nginx/sites-available/app.example.com.conf. Ejemplo completo (ajusta dominio, rutas y certificados):

server {    listen 80;    server_name app.example.com;    return 301 https://$host$request_uri;}server {    listen 443 ssl http2;    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;    include /etc/nginx/snippets/ssl_params.conf;    include /etc/nginx/snippets/security_headers.conf;    access_log /var/log/nginx/app.access.log;    error_log  /var/log/nginx/app.error.log warn;    root /var/www/app/current;    index index.html;    location = /health {        default_type text/plain;        return 200 'ok';    }    location ^~ /assets/ {        try_files $uri =404;        expires 30d;        add_header Cache-Control "public, max-age=2592000, immutable";    }    location / {        try_files $uri $uri/ /index.html;        expires 5m;        add_header Cache-Control "public, max-age=300";    }    location ^~ /api/ {        include /etc/nginx/snippets/proxy_api.conf;        proxy_pass http://api_upstream;        proxy_intercept_errors on;    }    location = /api {        return 301 /api/;    }    client_max_body_size 10m;}

Notas prácticas:

  • SPA routing: try_files ... /index.html permite rutas del frontend sin 404.
  • Caché: recursos versionados en /assets/ con immutable; HTML con caché corta para facilitar despliegues.
  • API: se agrupa bajo /api/ para separar claramente frontend y backend.

5) Activar el sitio

En distribuciones con sites-enabled:

ln -s /etc/nginx/sites-available/app.example.com.conf /etc/nginx/sites-enabled/app.example.com.conf

Si ya existe, revisa que el symlink apunte a la versión correcta.

Políticas básicas de caché y seguridad aplicadas al proyecto

Caché: reglas mínimas que funcionan bien

  • Assets versionados (hash en nombre): caché largo + immutable.
  • HTML: caché corto para que el navegador reciba rápido la nueva versión tras despliegue.
  • API: por defecto sin caché en Nginx (a menos que tengas endpoints idempotentes y estrategia clara). Si quieres forzarlo, hazlo por endpoint y con métricas.

Seguridad: mínimos razonables para el proyecto

  • Redirección HTTP→HTTPS.
  • Cabeceras anti-sniff y anti-iframe.
  • Límites de tamaño de carga (client_max_body_size) acordes a tu API.
  • Separación de rutas: /api/ no comparte reglas de caché del frontend.

Pasos de verificación (checklist operativo)

1) Validación de configuración antes de recargar

nginx -t

Debe indicar sintaxis correcta y que puede cargar los archivos incluidos.

2) Recarga sin downtime

nginx -s reload

La recarga aplica cambios sin cortar conexiones activas en la mayoría de escenarios.

3) Pruebas con curl: frontend

Comprueba redirección a HTTPS:

curl -I http://app.example.com/

Esperado: 301 y cabecera Location: https://....

Comprueba HTML principal:

curl -I https://app.example.com/

Esperado: 200, Content-Type: text/html y Cache-Control con max-age corto.

Comprueba assets con caché largo:

curl -I https://app.example.com/assets/app.abcdef1234.js

Esperado: 200 y Cache-Control: public, ... immutable.

4) Pruebas con curl: API

Endpoint de salud (si existe en tu API):

curl -i https://app.example.com/api/health

Esperado: 200 (o el código que defina tu API) y respuesta consistente.

Verifica que Nginx reenvía cabeceras útiles:

curl -i https://app.example.com/api/whoami

Si tu API expone diagnóstico, valida que reciba X-Forwarded-For y X-Forwarded-Proto.

5) Revisión de logs (confirmar rutas y errores)

Mientras haces pruebas, inspecciona:

tail -f /var/log/nginx/app.access.log
tail -f /var/log/nginx/app.error.log

Qué buscar:

  • 404 en assets: suele indicar ruta de root incorrecta o build incompleto.
  • 502/504 en /api/: backend caído, DNS/red, o timeouts insuficientes.
  • 301 inesperados: conflictos de location o normalizaciones (por ejemplo /api vs /api/).

6) Validación de TLS

Verifica el handshake y el certificado presentado:

openssl s_client -connect app.example.com:443 -servername app.example.com < /dev/null

Comprueba:

  • El CN/SAN corresponde al dominio.
  • La cadena es válida (sin errores de verificación).
  • El servidor responde con el certificado esperado (no uno por defecto).

Procedimiento de rollback (configuración y despliegue)

1) Versionar y respaldar el archivo del sitio antes de cambios

Antes de editar, guarda una copia con marca de tiempo:

cp /etc/nginx/sites-available/app.example.com.conf /etc/nginx/sites-available/app.example.com.conf.bak_$(date +%Y%m%d_%H%M%S)

2) Rollback de configuración (si algo falla)

Si tras un cambio el sitio falla o nginx -t no pasa:

  • Restaura el backup:
cp /etc/nginx/sites-available/app.example.com.conf.bak_YYYYMMDD_HHMMSS /etc/nginx/sites-available/app.example.com.conf
  • Valida y recarga:
nginx -t && nginx -s reload

3) Rollback del frontend (si usas releases)

Si el problema es del build estático (no de Nginx), revierte el symlink:

ln -sfn /var/www/app/releases/2026-02-02_1800 /var/www/app/current

Luego fuerza que Nginx sirva el contenido actualizado (normalmente no requiere recarga si solo cambias archivos, pero puedes recargar si cambiaste permisos o rutas):

nginx -s reload

Criterios de éxito medibles (check final)

CriterioCómo medirResultado esperado
Disponibilidad del sitiocurl -I https://app.example.com/200 consistente en múltiples intentos
Redirección a HTTPScurl -I http://app.example.com/301 a https://...
Frontend sirve SPA correctamentecurl -I https://app.example.com/ruta/inexistente200 (sirve index.html)
Caché de assetscurl -I https://app.example.com/assets/...Cache-Control con max-age alto e immutable
API accesible detrás de Nginxcurl -i https://app.example.com/api/health200 (o código definido) y latencia estable
Estabilidad tras recargasEjecutar nginx -t y nginx -s reload varias veces mientras haces curlSin caídas, sin picos de 5xx en logs
TLS válidoopenssl s_client ...Certificado correcto, cadena válida, SNI correcto
Observación de errorestail de logs durante pruebasSin 502/504 persistentes, sin 404 inesperados en assets

Ahora responde el ejercicio sobre el contenido:

¿Cuál es la razón principal de desplegar el frontend en un directorio de release y apuntar /var/www/app/current a ese release mediante un symlink?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Usar releases y un symlink current permite revertir a una versión anterior del frontend de forma inmediata, cambiando el enlace simbólico. Esto reduce el riesgo y evita tocar la configuración del servidor web.

Portada de libro electrónico gratuitaNginx para Principiantes: Domina el Servidor Web Moderno desde Cero
100%

Nginx para Principiantes: Domina el Servidor Web Moderno desde Cero

Nuevo curso

10 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.