⚙️ Nginx de verdad
Virtual hosts, reverse proxy, TLS, caching y load balancing con configuraciones completas y explicadas línea a línea.
Nginx de verdad
Nginx es el web server más usado del mundo por una razón: es event-driven (miles de conexiones con pocos recursos) y su configuración, aunque peculiar, es muy expresiva. Aquí aprenderás a configurarlo de verdad — del archivo estático al reverse proxy con TLS.
Estructura de la configuración
Nginx organiza la config en contextos anidados:
events { } # cómo maneja conexiones
http { # contexto principal de web
server { # un "virtual host" (dominio/sitio)
location / { } # cómo responder a una ruta
}
}
Archivos clave:
/etc/nginx/nginx.conf— configuración principal./etc/nginx/sites-available/→sites-enabled/— un archivo por sitio (link simbólico)./etc/nginx/conf.d/— configs sueltas.
⚠️ Siempre:
nginx -tpara validar la config antes de recargar. Un error de sintaxis tira el server.
nginx -t # valida la config
systemctl reload nginx # recarga sin cortar conexiones
Nivel 1 · Servir archivos estáticos
server {
listen 80;
server_name midominio.com www.midominio.com;
root /var/www/midominio;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Cachear assets con hash: un año
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
try_files $uri $uri/ =404: intenta el archivo, luego el directorio, luego 404. Evita que Nginx haga directory listing.expires 1y: mandaCache-Control: max-age=31536000.
Nivel 2 · Reverse proxy hacia tu backend
El caso más común: Nginx escucha en 80/443 y reenvía a tu app (Go/Python/Node/PHP-FPM) en localhost.
server {
listen 80;
server_name api.midominio.com;
location / {
proxy_pass http://127.0.0.1:8000; # tu 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_connect_timeout 5s;
proxy_read_timeout 30s;
}
# Límite de peticiones por IP (protección básica)
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
}
}
💡 Cabeceras X-Forwarded-*: tu backend necesita saber la IP real y el protocolo original del cliente. Si tu app genera URLs HTTPS pero Nginx le dice
http, rompes la seguridad de cookies (Secure) y los redirects.
Nivel 3 · TLS/HTTPS con Let’s Encrypt
server {
listen 443 ssl http2;
server_name midominio.com;
ssl_certificate /etc/letsencrypt/live/midominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/midominio.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Seguridad de headers
add_header Strict-Transport-Security "max-age=63072000" always;
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;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header X-Forwarded-Proto https;
}
}
# Redirigir todo HTTP a HTTPS
server {
listen 80;
server_name midominio.com www.midominio.com;
return 301 https://$host$request_uri;
}
Instalación del certificado:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d midominio.com -d www.midominio.com # configura todo automáticamente
💡
http2en la directivalistenhabilita HTTP/2.Strict-Transport-Security(HSTS) fuerza HTTPS en el navegador; úsalo con cuidado (una vez emitido, no se puede «apagar» hasta que caduque).
Nivel 4 · Load balancing
Distribuir entre varios backends:
upstream app_backend {
least_conn; # al que menos conexiones tenga
server 10.0.0.1:8000;
server 10.0.0.2:8000;
server 10.0.0.3:8000 backup; # solo si los demás fallan
keepalive 32;
}
server {
listen 443 ssl http2;
server_name app.midominio.com;
# ...ssl...
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Estrategias de balanceo: round-robin (por defecto), least_conn, ip_hash (misma IP → mismo backend, para sesiones sin estado externo). En producción, tus backends deben ser stateless para que el balanceo funcione.
Nivel 5 · Servir PHP-FPM (el stack LEMP)
server {
listen 80;
server_name midominio.com;
root /var/www/midominio/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string; # front controller
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all; # bloquear archivos ocultos (.env, .git)
}
}
⚠️ Seguridad: bloquear
.env,.gity demás archivos ocultos es obligatorio en producción. Esta regla (deny all) lo hace.
Cheatsheet
| Necesito… | Config |
|---|---|
| Validar config | nginx -t |
| Recargar sin cortar | systemctl reload nginx |
| Sirve estático | root + try_files |
| Reenviar a mi app | proxy_pass http://127.0.0.1:PUERTO |
| HTTPS | listen 443 ssl http2 + ssl_certificate |
| Repartir carga | upstream + proxy_pass http://upstream |
| PHP | fastcgi_pass unix:/run/php/phpX-fpm.sock |
| Limitar peticiones | limit_req_zone + limit_req |
| Bloquear ocultos | location ~ /\. { deny all; } |
Para profundizar
- Nginx Beginner’s Guide: la introducción oficial.
- Mozilla SSL Config Generator: configs TLS A+ listas.
- Let’s Encrypt: certificados gratis y automáticos.
- Ruta completa: Servidores y Nginx.