Configurar SSL/TLS de Let's Encrypt para Nginx en Debian 12 y Ubuntu 24.04
Obtener y renovar automáticamente certificados TLS gratuitos con Certbot para Nginx en Debian 12 o Ubuntu 24.04. Cubre configuración DNS, instalación de Certbot, redirección HTTP a HTTPS, endurecimiento TLS, HTTP/2, HSTS y la discontinuación de OCSP.
Este tutorial te guía a través de la obtención de un certificado TLS gratuito de Let's Encrypt usando Certbot, la configuración de Nginx para HTTPS y la activación de la renovación automática.
Si todavía no has instalado Nginx, comienza con Instalar Nginx en Debian 12 y Ubuntu 24.04 desde el repositorio oficial. Para una visión general de la administración de Nginx en un VPS, consulta Administración de Nginx en un VPS.
¿Qué necesitas antes de solicitar un certificado?
Antes de que Certbot pueda emitir un certificado, tu dominio debe apuntar a la dirección IP de tu servidor y Nginx debe estar ejecutándose con un server block para ese dominio. Let's Encrypt valida la propiedad del dominio enviando una solicitud HTTP a tu servidor. Si el DNS no resuelve a tu VPS o Nginx no está escuchando, la verificación falla.
Necesitas:
- Un VPS con Debian 12 o Ubuntu 24.04 con Nginx instalado desde el repositorio oficial (Instalar Nginx en Debian 12 y Ubuntu 24.04 desde el repositorio oficial)
- Un nombre de dominio registrado (usaremos
example.coma lo largo del tutorial) - Un registro A apuntando
example.coma la dirección IPv4 de tu servidor - Un registro AAAA apuntando a tu dirección IPv6 (si tu servidor tiene una)
- El puerto 80 abierto en tu firewall (Certbot usa desafíos HTTP-01)
- Un server block de Nginx funcionando para tu dominio (Server Blocks de Nginx: Aloja Varios Dominios en un VPS)
Configurar los registros DNS
Crea un registro A en tu proveedor de DNS:
| Tipo | Nombre | Valor | TTL |
|---|---|---|---|
| A | example.com | 203.0.113.10 | 300 |
| AAAA | example.com | 2001:db8::1 | 300 |
Reemplaza las direcciones IP con las direcciones reales de tu servidor. Configura el TTL bajo (300 segundos) durante la configuración para que los cambios se propaguen rápido. Puedes aumentarlo después.
Verificar la resolución DNS
Espera unos minutos después de crear los registros, luego verifica desde tu máquina local (no desde el servidor):
dig +short example.com A
dig +short example.com AAAA
Deberías ver las direcciones IP de tu servidor en la salida. Si no ves nada o ves una IP diferente, el registro aún no se ha propagado. Espera e inténtalo de nuevo.
Verifica que Nginx responde en el puerto 80 desde tu máquina local:
curl -I http://example.com
Deberías obtener una respuesta HTTP/1.1 200 OK con Server: nginx. Si la conexión expira, revisa las reglas de tu firewall.
¿Cómo instalar Certbot en Debian 12 y Ubuntu 24.04?
Instala Certbot y su plugin para Nginx desde el repositorio de paquetes de tu distribución usando apt. El plugin de Nginx permite que Certbot modifique automáticamente tus server blocks para habilitar TLS.
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
Verifica la instalación:
certbot --version
En Debian 12, esto instala Certbot 2.1.0. En Ubuntu 24.04, obtienes Certbot 2.9.0. Ambas versiones funcionan para todo lo que cubre este tutorial.
Dato importante: si instalaste Nginx desde el repositorio oficial de nginx.org (como se recomienda en Instalar Nginx en Debian 12 y Ubuntu 24.04 desde el repositorio oficial), el plugin de Certbot para Nginx funciona sin configuración adicional. Detecta los server blocks en /etc/nginx/conf.d/ y /etc/nginx/sites-enabled/.
¿Cómo obtener un certificado de Let's Encrypt para Nginx?
Ejecuta certbot --nginx con tu nombre de dominio. Certbot contacta a Let's Encrypt, demuestra que controlas el dominio mediante un desafío HTTP-01, obtiene el certificado y edita tu server block de Nginx para usarlo. Todo el proceso toma unos 30 segundos.
sudo certbot --nginx -d example.com -d www.example.com
Certbot te pedirá tu dirección de correo (para recordatorios de renovación) y la aceptación de los Términos de Servicio. Después:
- Coloca un archivo de desafío HTTP-01 en tu web root
- Pide a Let's Encrypt que lo verifique
- Descarga el certificado firmado
- Modifica tu server block de Nginx para agregar las directivas TLS
- Recarga Nginx
Verifica que el certificado fue emitido:
sudo ls -la /etc/letsencrypt/live/example.com/
Deberías ver:
lrwxrwxrwx 1 root root ... cert.pem -> ../../archive/example.com/cert1.pem
lrwxrwxrwx 1 root root ... chain.pem -> ../../archive/example.com/chain1.pem
lrwxrwxrwx 1 root root ... fullchain.pem -> ../../archive/example.com/fullchain1.pem
lrwxrwxrwx 1 root root ... privkey.pem -> ../../archive/example.com/privkey1.pem
Estos son enlaces simbólicos. fullchain.pem es tu certificado más la cadena de CA intermedia. privkey.pem es tu clave privada.
Comprueba que Nginx está ejecutándose con la nueva configuración:
sudo nginx -t && sudo systemctl status nginx
nginx -t prueba la sintaxis de la configuración. Si muestra test is successful, la configuración es válida.
¿Qué cambia Certbot en tu configuración de Nginx?
Certbot agrega varias líneas a tu server block. Esto es lo que inserta (las líneas marcadas con # managed by Certbot):
server {
server_name example.com www.example.com;
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
# ... tus location blocks existentes ...
}
El archivo options-ssl-nginx.conf contiene la configuración TLS predeterminada de Certbot. La reemplazaremos con configuraciones más estrictas en la sección de endurecimiento a continuación.
Certbot también crea un segundo server block para redirigir HTTP a HTTPS. Mejoraremos esa redirección en la siguiente sección.
Puedes ver exactamente qué cambió comparando tu configuración:
sudo diff /etc/nginx/conf.d/example.com.conf /etc/nginx/conf.d/example.com.conf.bak 2>/dev/null || echo "No backup found. Certbot modifies in place."
¿Cómo redirigir HTTP a HTTPS en Nginx?
Todo el tráfico HTTP debe redirigirse a HTTPS con una redirección 301 (permanente). Certbot puede agregar esto automáticamente, pero su valor predeterminado usa una sentencia if dentro del server block existente. Eso es un anti-patrón en Nginx. Un server block dedicado es más limpio y confiable.
Reemplaza la redirección de Certbot con un server block separado. Edita tu archivo de configuración (la ruta depende de tu configuración; normalmente /etc/nginx/conf.d/example.com.conf):
# HTTP -> HTTPS redirect (separate server block, not an if-statement)
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Esto va en el mismo archivo que tu server block HTTPS, o en un archivo separado. Asegúrate de eliminar cualquier bloque de redirección generado por Certbot para evitar duplicados.
Prueba y recarga:
sudo nginx -t
sudo systemctl reload nginx
Verifica la redirección desde tu máquina local:
curl -I http://example.com
Salida esperada:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
¿Cómo endurecer la configuración TLS para un servidor de producción?
La configuración TLS predeterminada de Certbot (options-ssl-nginx.conf) es intencionalmente conservadora. Para un servidor de producción, necesitas configuraciones más estrictas. Seguiremos el perfil Intermediate de Mozilla del SSL Configuration Generator, que equilibra seguridad con compatibilidad de clientes hasta Firefox 27, Chrome 31 y Android 4.4.2.
Crea un archivo de snippet (fragmento de configuración) que puedas incluir con include desde cada server block:
sudo nano /etc/nginx/snippets/tls-params.conf
Agrega lo siguiente:
# TLS protocol versions, TLS 1.2 and 1.3 only
# TLS 1.0 and 1.1 are deprecated (RFC 8996)
ssl_protocols TLSv1.2 TLSv1.3;
# Ciphers, Mozilla Intermediate profile (January 2026)
# Source: https://ssl-config.mozilla.org/
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# DH parameters, 2048-bit, RFC 7919 ffdhe2048
ssl_dhparam /etc/nginx/dhparam.pem;
# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:TLS:10m;
ssl_session_tickets off;
# HSTS, tell browsers to always use HTTPS (2 years)
# Only add includeSubDomains if ALL subdomains use HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# Hide Nginx version in error pages and headers
server_tokens off;
Genera el archivo de parámetros DH (esto toma unos segundos):
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
Verifica que el archivo fue creado:
sudo ls -la /etc/nginx/dhparam.pem
Ahora actualiza tu server block HTTPS para usar estas configuraciones en lugar de los valores predeterminados de Certbot. Elimina la línea include /etc/letsencrypt/options-ssl-nginx.conf; y la línea ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;. Reemplázalas con:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS hardening (replaces Certbot defaults)
include snippets/tls-params.conf;
# ... your location blocks ...
}
Prueba y recarga:
sudo nginx -t
sudo systemctl reload nginx
¿Qué versiones de TLS y cifrados deberías usar?
Mozilla publica tres perfiles TLS. Esta es la comparación:
| Perfil | Protocolos | Cliente compatible más antiguo | Caso de uso |
|---|---|---|---|
| Modern | Solo TLS 1.3 | Firefox 63, Chrome 70, Android 10 | Servicios donde todos los clientes son recientes |
| Intermediate | TLS 1.2 + 1.3 | Firefox 27, Chrome 31, Android 4.4 | Servidores web de propósito general |
| Old | TLS 1.0 + 1.1 + 1.2 + 1.3 | Firefox 1, Chrome 1, IE 8 | Solo sistemas heredados |
Usa Intermediate a menos que tengas una razón específica para no hacerlo. Cubre más del 99.9% de los navegadores actuales mientras excluye protocolos débiles. TLS 1.0 y 1.1 fueron formalmente deprecados por RFC 8996 en marzo de 2021.
La lista de cifrados en nuestro snippet usa solo cifrados AEAD (GCM y ChaCha20-Poly1305). ssl_prefer_server_ciphers off permite que el cliente elija su cifrado preferido. Esta es la recomendación de Mozilla porque los clientes modernos toman mejores decisiones que una preferencia estática del servidor.
¿Let's Encrypt todavía soporta OCSP stapling?
No. Let's Encrypt cerró su servicio OCSP el 6 de agosto de 2025. Los certificados emitidos después de mayo de 2025 no contienen URL de OCSP responder. El estado de revocación ahora se publica exclusivamente a través de Certificate Revocation Lists (CRL, listas de revocación de certificados). Si solo usas certificados de Let's Encrypt, elimina cualquier directiva ssl_stapling de tu configuración de Nginx.
Esta es la línea de tiempo:
- Diciembre 2024. Let's Encrypt anunció el plan de finalizar OCSP.
- 30 de enero de 2025. Los certificados que solicitaban la extensión OCSP Must-Staple comenzaron a fallar.
- 7 de mayo de 2025. Los nuevos certificados dejaron de incluir URLs de OCSP responder. Se agregaron URLs de CRL en su lugar.
- 6 de agosto de 2025. El servicio OCSP se cerró por completo. Todos los certificados emitidos anteriormente con URLs de OCSP ya han expirado.
Si ves ssl_stapling on; o ssl_stapling_verify on; en alguna guía o fragmento de configuración, es un consejo desactualizado para usuarios de Let's Encrypt. Estas directivas son inofensivas (Nginx las ignora silenciosamente cuando no hay URL de OCSP responder), pero agregan desorden innecesario. Elimínalas.
Si usas certificados de otra CA que todavía proporciona OCSP, esas directivas siguen siendo válidas para esos certificados.
¿Por qué Let's Encrypt dejó de usar OCSP? Dos razones. OCSP es un riesgo de privacidad: cada vez que un navegador verificaba la revocación del certificado vía OCSP, la CA sabía qué sitio se visitaba y desde qué IP. En su punto máximo, el servicio OCSP de Let's Encrypt manejaba 340 mil millones de solicitudes por mes. Cambiar a CRL elimina esa filtración de privacidad. Los CRL son descargados en bloque por los navegadores, por lo que no se envía ninguna solicitud por visita a la CA.
¿Cómo habilitar HTTP/2 con Nginx?
HTTP/2 multiplexa solicitudes sobre una sola conexión, reduciendo la latencia. Si instalaste Nginx desde el repositorio oficial de nginx.org (versión 1.25.1 o posterior), habilita HTTP/2 con la directiva http2 dentro de tu server block.
Agrega http2 on; dentro de tu server block HTTPS:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include snippets/tls-params.conf;
# ... your location blocks ...
}
La directiva http2 on; reemplazó la sintaxis deprecada listen 443 ssl http2; en Nginx 1.25.1. La sintaxis antigua todavía funciona pero produce advertencias de deprecación en el log de errores. Si estás ejecutando el Nginx empaquetado por la distribución (1.22 en Debian 12, 1.24 en Ubuntu 24.04), usa la sintaxis antigua listen 443 ssl http2; en su lugar.
Prueba y recarga:
sudo nginx -t
sudo systemctl reload nginx
Verifica que HTTP/2 está activo:
curl -I --http2 -s https://example.com | head -1
Salida esperada:
HTTP/2 200
Si ves HTTP/1.1 en su lugar, verifica que http2 on; está dentro del server block correcto y que tu versión de Nginx lo soporta.
¿Cómo funciona la renovación automática de certificados?
Los certificados de Let's Encrypt expiran después de 90 días. Certbot instala un temporizador de systemd (certbot.timer) que verifica dos veces al día si algún certificado está a menos de 30 días de expirar. Si es así, lo renueva automáticamente. No necesitas configurar un cron job.
Verifica que el temporizador está activo:
systemctl status certbot.timer
Deberías ver Active: active (waiting) y una línea mostrando la próxima hora de activación.
Consulta cuándo ocurrirá la próxima renovación:
systemctl list-timers certbot.timer
Esto muestra los tiempos NEXT y LAST de ejecución.
Probar la renovación sin renovar realmente
Ejecuta un dry-run (simulación) para verificar que el proceso de renovación funciona de principio a fin:
sudo certbot renew --dry-run
Esto contacta al servidor de staging de Let's Encrypt y simula una renovación completa. Si muestra Congratulations, all simulated renewals succeeded, tu configuración es correcta.
Configurar un deploy hook para recargar Nginx
Cuando Certbot renueva un certificado, Nginx necesita recargarse para usar los nuevos archivos. Configura un deploy hook que se ejecute solo tras una renovación exitosa:
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Agrega:
#!/bin/bash
/usr/bin/systemctl reload nginx
Hazlo ejecutable:
sudo chmod 700 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Verifica los permisos:
ls -la /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
La salida esperada muestra -rwx------ (solo root puede leer y ejecutar).
El directorio deploy ejecuta scripts solo después de una renovación exitosa, no en cada activación del temporizador. Esto evita recargas innecesarias.
¿Cómo verificar que tu configuración TLS es correcta?
Después de completar todos los pasos anteriores, ejecuta estos comandos de verificación. Cada uno comprueba un aspecto diferente de tu configuración.
Comprobar la cadena de certificados con OpenSSL
Desde tu máquina local:
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates -subject -issuer
Salida esperada:
notBefore=Mar 19 00:00:00 2026 GMT
notAfter=Jun 17 00:00:00 2026 GMT
subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R12
Dato importante: la fecha notAfter debería ser aproximadamente 90 días después de la emisión. El emisor debería ser Let's Encrypt. Los intermediarios RSA actuales son R12 y R13. Para certificados ECDSA, busca E7 o E8.
Comprobar la versión TLS y el cifrado en uso
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | grep -E "Protocol|Cipher"
Esperado:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Si ves TLSv1.2, también está bien. Depende de qué versión prefiera tu OpenSSL local.
Comprobar las cabeceras HTTPS
curl -I https://example.com
Busca:
HTTP/2 200
strict-transport-security: max-age=63072000; includeSubDomains
Si falta strict-transport-security, verifica la línea add_header en tu snippet TLS. El parámetro always asegura que la cabecera se envíe incluso en respuestas de error.
Resumen de comandos de verificación
| Comando | Qué comprueba | Qué buscar |
|---|---|---|
dig +short example.com |
Resolución DNS | La IP de tu servidor |
curl -I http://example.com |
Redirección HTTP | 301 con Location: https:// |
curl -I https://example.com |
HTTPS + cabeceras | HTTP/2 200, cabecera HSTS |
openssl s_client -connect ... |
Cadena de certificados, versión TLS | Emisor Let's Encrypt, TLSv1.2 o 1.3 |
certbot renew --dry-run |
Proceso de renovación | all simulated renewals succeeded |
systemctl status certbot.timer |
Temporizador de renovación automática | active (waiting) |
Probar con SSL Labs
Para una auditoría externa detallada, envía tu dominio a SSL Labs Server Test. Con la configuración de esta guía, deberías obtener una puntuación A o A+. La calificación A+ requiere HSTS, que habilitamos en el snippet TLS.
Referencia completa de configuración de Nginx
Aquí está el server block completo con todas las configuraciones de este tutorial combinadas:
# HTTP -> HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# HTTPS server block
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
# Let's Encrypt certificate
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# TLS hardening
include snippets/tls-params.conf;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
}
Y el snippet TLS en /etc/nginx/snippets/tls-params.conf:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:TLS:10m;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
server_tokens off;
Para cabeceras de seguridad adicionales como Content-Security-Policy, X-Frame-Options y Permissions-Policy, consulta Hardening de seguridad de Nginx en Ubuntu y Debian.
¿Algo salió mal?
Certbot dice "Could not automatically find a matching server block"
Certbot busca una directiva server_name que coincida con tu dominio -d. Asegúrate de que tu archivo de server block esté en /etc/nginx/conf.d/ o /etc/nginx/sites-enabled/ y contenga server_name example.com;.
Certbot dice "Connection refused" o "Challenge failed" El puerto 80 debe estar abierto. Revisa tu firewall:
sudo ufw status # if using UFW
sudo nft list ruleset # if using nftables
También verifica que Nginx está escuchando en el puerto 80:
sudo ss -tlnp | grep ':80'
"SSL: error" en el log de errores de Nginx después de editar la configuración TLS Probablemente tienes un error de sintaxis en la cadena de cifrados o una ruta de archivo faltante. Comprueba:
sudo nginx -t
sudo journalctl -u nginx -n 20 --no-pager
El navegador muestra "NET::ERR_CERT_DATE_INVALID" El certificado puede haber expirado. Comprueba la fecha de expiración:
sudo certbot certificates
Si ha expirado, fuerza una renovación:
sudo certbot renew --force-renewal
certbot renew --dry-run falla
Causa habitual: el DNS del dominio ya no apunta a este servidor, o el puerto 80 está bloqueado. Certbot necesita acceso HTTP-01 para la renovación.
HTTP/2 no funciona
Comprueba tu versión de Nginx: nginx -v. Si es anterior a 1.25.1, usa listen 443 ssl http2; en lugar de la directiva separada http2 on;. Si es 1.25.1 o posterior, asegúrate de que http2 on; esté dentro del bloque server correcto (no en http ni en location).
¿Listo para probarlo?
Aloja tus aplicaciones web en un VPS fiable. →