Casi toda configuración de Nginx en la web real se reduce a tres patrones: servir archivos estáticos, servir una SPA con rutas de cliente, o hacer de proxy inverso hacia una aplicación. Si entiendes bien esos tres bloques y dos o tres directivas clave, puedes mantener cualquier servidor. Este artículo los desmonta con la configuración exacta.
Patrón 1: sitio estático
server {
listen 80;
http2 off;
server_name midominio.com;
root /var/www/midominio;
index index.html index.htm;
client_max_body_size 10M;
location / {
try_files $uri $uri/ =404;
}
}
La directiva que hace todo el trabajo es try_files: prueba las opciones en orden y entrega la primera que exista. Aquí: ¿existe un archivo que coincida exactamente con la URI? ¿Un directorio? Si nada existe, devuelve 404. Sin try_files, Nginx usaría su comportamiento por defecto, mucho menos predecible con URLs limpias.
Patrón 2: SPA (React, Vue, Svelte)
Una SPA de routing cliente recibe peticiones como /usuarios/42/editar que no corresponden a ningún archivo real — el router de JavaScript debe encargarse. Cambia una sola cosa respecto al patrón anterior:
location / {
try_files $uri $uri/ /index.html;
}
El fallback final ya no es =404 sino /index.html: cualquier ruta desconocida sirve la aplicación, que arranca, lee la URL del navegador y renderiza la vista correspondiente. Es literalmente el cambio de una palabra el que separa un servidor que funciona con React Router de uno que devuelve 404 al refrescar cualquier página interior.
Patrón 3: proxy inverso
Aquí Nginx deja de servir archivos y pasa a reenviar cada petición a tu aplicación (Node, Python, Go...) escuchando en otro puerto:
server {
listen 80;
http2 off;
server_name midominio.com;
client_max_body_size 10M;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
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;
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 60s;
}
}
Las cabeceras proxy_set_header no son decorativas:
X-Forwarded-Foracumula la cadena de IPs de clientes reales. Sin ella, tu aplicación ve todas las peticiones como provenientes de127.0.0.1— fatal para rate limiting, logs y geolocalización.X-Forwarded-Protoinforma de que el cliente vino por HTTPS aunque la conexión interna sea HTTP. Sin ella, frameworks que generan URLs absolutas producenhttp://en producción.Upgrade+Connection "upgrade"habilitan WebSockets, que necesitan un salto de protocolo que el proxy debe permitir explícitamente.client_max_body_sizelimita el tamaño del cuerpo aceptado; por defecto Nginx corta en 1 MB y los uploads fallan con error 413 misterioso.
La trampa de herencia de add_header
Este comportamiento sorprende incluso a administradores veteranos: las cabeceras add_header NO se heredan si un bloque location define alguna propia. En este ejemplo:
server {
add_header X-Frame-Options "DENY" always;
location / {
# hereda X-Frame-Options
}
location ~* \.(css|js|png)$ {
expires 30d;
add_header Cache-Control "public, immutable";
# ¡pierde X-Frame-Options!
}
}
El bloque de assets define su propio add_header (el de caché), así que Nginx descarta las heredadas del nivel server. Para assets estáticos es irrelevante — nadie embebe un CSS en un iframe — pero es imprescindible conocerlo cuando añades cabeceras dentro de locations con contenido dinámico.
HTTPS: redirección y certificados
Con certificados de Let's Encrypt (vía Certbot), el patrón son dos bloques server: el viejo puerto 80 solo redirige, y el 443 hace el trabajo real:
server {
listen 80;
server_name midominio.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name midominio.com;
ssl_certificate /etc/letsencrypt/live/midominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/midominio.com/privkey.pem;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# ... resto igual ...
}
Dos detalles modernos: http2 on es la sintaxis actual (desde Nginx 1.25.1); la antigua era listen 443 ssl http2;. Y HSTS con max-age=63072000 le dice a los navegadores que durante dos años ni intenten entrar por HTTP — actívalo solo cuando estés seguro de que HTTPS es estable.
Gzip pertenece al bloque http
La compresión gzip se configura en el bloque http {} del archivo principal /etc/nginx/nginx.conf, no en tu server block:
gzip on;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_comp_level 6 es el punto dulce: niveles superiores ganan unos pocos bytes a cambio de bastante CPU.
Genera los tres patrones configurados
Elegir modo (estático, SPA o proxy), activar SSL, caché de assets, cabeceras de seguridad y gzip mediante toggles es lo que hace nuestro generador de configuración Nginx: rellena dominio, puertos y rutas, y copia la configuración completa lista para /etc/nginx/sites-available/.
Preguntas frecuentes
¿Dónde va mi archivo de configuración? En Debian/Ubuntu, /etc/nginx/sites-available/midominio con un symlink desde sites-enabled. Tras cambiar algo: nginx -t para validar y systemctl reload nginx para aplicar sin corte.
¿Nginx o Caddy? Caddy automatiza certificados TLS sin tocar nada y es excelente para proyectos personales. Nginx sigue siendo el estándar en producción por rendimiento crudo, ecosistema y documentación ante problemas raros.
¿Por qué mis WebSockets se caen a los 60 segundos? El proxy_read_timeout por defecto cierra conexiones sin tráfico. Con WebSockets inactivos, súbelo o implementa ping/pong a nivel de aplicación.
Genera tu configuración completa con el generador de Nginx, con SSL y cabeceras de seguridad incluidas.