HTTPS en Nginx con TLS: qué resuelve y qué necesitas
TLS (lo que normalmente llamamos “HTTPS”) cifra el tráfico entre el navegador y tu servidor, evitando que terceros lean o modifiquen datos en tránsito. En Nginx, habilitar TLS consiste en: (1) disponer de un certificado y su clave privada, (2) configurar un server que escuche en 443 con ssl, (3) redirigir HTTP (80) a HTTPS (443), y (4) añadir cabeceras y límites defensivos.
Estructura de certificado y clave (cert/key)
- Certificado (
.crt,.pem): contiene la identidad del sitio (dominio) y la firma de una CA. Suele incluir la cadena de certificados (intermedios) según el proveedor. - Clave privada (
.key): es secreta; Nginx la usa para negociar TLS. Si se filtra, un atacante puede suplantar tu sitio. - Permisos recomendados: la clave debe ser legible solo por root (o por el usuario que ejecute Nginx si aplica). Ejemplo:
chmod 600 /etc/ssl/private/tu_sitio.key.
Rutas típicas en Linux: /etc/ssl/certs/ para certificados y /etc/ssl/private/ para claves. En entornos con Let’s Encrypt, suelen estar en /etc/letsencrypt/live/tu-dominio/.
Práctica 1: habilitar TLS en un server block
El siguiente ejemplo asume un dominio example.com y archivos: /etc/ssl/certs/example.com.fullchain.pem (certificado + cadena) y /etc/ssl/private/example.com.key (clave). Ajusta rutas y dominio a tu caso.
Paso 1: crear (o editar) el bloque HTTPS
server { listen 443 ssl http2; server_name example.com www.example.com; ssl_certificate /etc/ssl/certs/example.com.fullchain.pem; ssl_certificate_key /etc/ssl/private/example.com.key; # Selección introductoria de protocolos/cifrados (compatibilidad razonable) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers off; # (Opcional) mejora de rendimiento en reanudación de sesión ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # Raíz del sitio (ejemplo) root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; }}Notas rápidas: TLSv1.2 y TLSv1.3 cubren la mayoría de clientes modernos. La directiva ssl_ciphers aquí es intencionalmente simple (nivel introductorio). En muchos sistemas, los valores por defecto ya son aceptables; lo importante es evitar protocolos antiguos.
Paso 2: redirigir HTTP → HTTPS
Crea un bloque separado en el puerto 80 que responda al mismo server_name y redirija todo a HTTPS.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri;}Esto asegura que cualquier visita por HTTP termine en HTTPS, conservando ruta y parámetros.
Paso 3: validar sintaxis y recargar Nginx
sudo nginx -tsudo systemctl reload nginxSi nginx -t muestra errores, corrígelos antes de recargar.
Probar HTTPS y la redirección con curl y navegador
Prueba 1: comprobar redirección HTTP → HTTPS
curl -I http://example.comDeberías ver algo como:
HTTP/1.1 301 Moved PermanentlyLocation: https://example.com/...Prueba 2: comprobar negociación TLS y respuesta HTTPS
curl -I https://example.comSi el certificado es válido, verás HTTP/2 200 o HTTP/1.1 200 según el caso. Para inspeccionar detalles del certificado (útil para depurar cadenas incompletas), puedes usar:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -datesPrueba 3: validar en navegador
- Abre
https://example.comy verifica el candado. - Revisa que
http://example.comredirige automáticamente a HTTPS. - En herramientas de desarrollador (Network), selecciona la petición y revisa “Response Headers” (lo usaremos en la sección de cabeceras).
Cabeceras de seguridad comunes (y cómo aplicarlas)
Las cabeceras de seguridad ayudan a reducir riesgos del lado del navegador. Se suelen configurar en el bloque HTTPS (y en algunos casos también en HTTP si no rediriges todo). En Nginx se añaden con add_header. Para que se apliquen también en respuestas de error (por ejemplo 404/500), usa el modificador always.
HSTS (Strict-Transport-Security)
Indica al navegador que, durante un tiempo, solo debe acceder al sitio por HTTPS. Esto refuerza la redirección y ayuda contra ataques de “downgrade”.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;Precaución: activa HSTS cuando estés seguro de que HTTPS funciona bien en todo el dominio (y subdominios si usas includeSubDomains). Si lo habilitas y luego rompes HTTPS, los navegadores insistirán en HTTPS hasta que expire.
X-Content-Type-Options
Evita que el navegador “adivine” tipos MIME (mitiga ciertos vectores de ejecución inesperada).
add_header X-Content-Type-Options "nosniff" always;X-Frame-Options
Reduce el riesgo de clickjacking al controlar si tu sitio puede ser embebido en iframes.
add_header X-Frame-Options "SAMEORIGIN" always;Si tu sitio no debe ser embebido nunca, puedes usar DENY.
Referrer-Policy
Controla cuánta información del “referer” se envía al navegar a otros sitios.
add_header Referrer-Policy "strict-origin-when-cross-origin" always;Aplicación práctica: añadir cabeceras al server HTTPS
Inserta estas directivas dentro del server que escucha en 443 (idealmente cerca de la configuración TLS):
server { listen 443 ssl http2; server_name example.com www.example.com; ssl_certificate /etc/ssl/certs/example.com.fullchain.pem; ssl_certificate_key /etc/ssl/private/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Cabeceras de seguridad add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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; root /var/www/example.com; index index.html; location / { try_files $uri $uri/ =404; }}Validar cabeceras con curl
curl -I https://example.comBusca en la salida:
Strict-Transport-Security:X-Content-Type-Options:X-Frame-Options:Referrer-Policy:
Si no aparecen, revisa que estén en el bloque correcto (el de 443) y que recargaste Nginx.
Límites defensivos: limit_req y limit_conn
Además de TLS y cabeceras, es útil aplicar límites para reducir abuso (picos de tráfico, bots agresivos, intentos de fuerza bruta). Nginx ofrece dos herramientas sencillas:
- limit_req: limita la tasa de solicitudes (requests por segundo) por clave (por ejemplo, por IP).
- limit_conn: limita conexiones simultáneas por clave (por ejemplo, por IP).
limit_req (rate limiting) con ejemplo simple
Se define una “zona” con limit_req_zone (normalmente en el contexto http) y luego se aplica con limit_req en un server o location.
1) Definir la zona (en nginx.conf, dentro de http { ... }):
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=5r/s;Esto crea una zona llamada req_per_ip y permite ~5 solicitudes por segundo por IP. 10m es memoria para almacenar estados (aprox. miles de IPs, depende del tráfico).
2) Aplicar el límite (en tu server 443 o en una ruta sensible):
location /login { limit_req zone=req_per_ip burst=10 nodelay; proxy_pass http://backend_login;}Interpretación: burst=10 permite ráfagas cortas (hasta 10) antes de empezar a rechazar. Con nodelay, en vez de “ralentizar” las solicitudes extra, Nginx las procesa inmediatamente hasta el burst y luego rechaza el exceso (útil para cortar picos agresivos). Para un sitio estático, podrías aplicarlo a todo / si lo necesitas, pero suele ser mejor limitar endpoints sensibles (login, API, etc.).
limit_conn (conexiones simultáneas) con ejemplo simple
Igual que antes, defines una zona y luego aplicas el límite.
1) Definir la zona (en http { ... }):
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;2) Aplicar el límite (en el server 443):
server { listen 443 ssl http2; server_name example.com; # ... TLS y resto ... limit_conn conn_per_ip 20; location / { try_files $uri $uri/ =404; }}Esto limita a 20 conexiones simultáneas por IP. Es una defensa básica contra clientes que abren demasiadas conexiones.
Prueba rápida de límites
Para observar efectos, puedes lanzar varias solicitudes rápidamente. Por ejemplo, para limit_req en /login:
for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code}\n" https://example.com/login; doneSi el límite se activa, es común ver respuestas 429 (Too Many Requests) o 503 dependiendo de la configuración y versión/módulos. Para afinar, ajusta rate y burst según el patrón real de tu aplicación.
Checklist de verificación (TLS, redirección, cabeceras)
| Objetivo | Comprobación | Resultado esperado |
|---|---|---|
| HTTP redirige a HTTPS | curl -I http://example.com | 301 con Location: https://... |
| HTTPS responde correctamente | curl -I https://example.com | 200 (o el código esperado) |
| Certificado válido | Navegador (candado) / openssl s_client | Sin advertencias; dominio coincide |
| HSTS activo | curl -I https://example.com | Cabecera Strict-Transport-Security |
| Cabeceras básicas | curl -I https://example.com | X-Content-Type-Options, X-Frame-Options, Referrer-Policy |
| Límites defensivos | Ráfaga de requests | Rechazos/limitación al exceder umbrales |