🔎 Buscar

⚙️ Nginx de verdad

Virtual hosts, reverse proxy, TLS, caching y load balancing con configuraciones completas y explicadas línea a línea.

Wiki / Apuntes📖 Contenido

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 -t para 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: manda Cache-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

💡 http2 en la directiva listen habilita 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, .git y 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

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.