Descripción General
HTTPS necesita un certificado de una CA de confianza, y Let’s Encrypt los emite gratis mediante el protocolo ACME. Certbot es el cliente oficial: demuestra que el dominio es tuyo, descarga el certificado, lo instala y —la parte que la gente olvida— lo renueva antes de que expire.
Esta receta cubre las tres formas en que Certbot valida la propiedad (plugins del servidor web, standalone y DNS-01 para wildcards), además de la automatización de la renovación, despliegues en Docker y Kubernetes, y los detalles operativos —rate limits, staging, monitorización— que evitan que un certificado se convierta en una caída a las 3 de la madrugada.
Cuándo Usar
Usa esta receta cuando:
- Configuras HTTPS para una nueva aplicación web pública.
- Reemplazas certificados expirados o autofirmados por unos de confianza para los navegadores.
- Automatizas la renovación de certificados para que nunca expiren sin aviso.
- Configuras TLS en un reverse proxy, balanceador de carga, contenedor Docker o ingress de Kubernetes.
- Necesitas una alternativa gratuita y automatizada a las CAs de pago.
Prerequisitos
Antes de ejecutar Certbot, asegúrate de que:
- El registro A de tu dominio (y AAAA si usas IPv6) apunta a la IP pública de este servidor — Let’s Encrypt valida desde internet público.
- Los puertos 80 y 443 están abiertos en el firewall; la validación HTTP-01 usa el puerto 80, también en cada renovación.
- Para el modo
--nginx/--apache, ya hay un sitio HTTP respondiendo en el puerto 80 — Certbot lee el server block existente. - Para todo lo que no sea una prueba puntual, usa
--stagingprimero. El límite de producción de Let’s Encrypt es de 50 certificados por dominio registrado por semana (5 duplicados por semana) — staging te deja iterar sin gastarlo.
Cómo Funciona
Certbot pide un certificado a Let’s Encrypt y la CA responde con un desafío: coloca un token en http://tudominio.com/.well-known/acme-challenge/<token>. Let’s Encrypt descarga esa URL desde internet público — si el token coincide, has demostrado que controlas el dominio y el certificado se emite. El mismo desafío se repite en cada renovación, por eso el puerto 80 debe seguir abierto.
Cuando el puerto 80 no puede exponerse —redes internas, certificados wildcard— el desafío DNS-01 demuestra la propiedad con un registro TXT.
Implementación Paso a Paso
Instalar Certbot
# Ubuntu / Debian — snap es el método oficialmente recomendado
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# O por apt (funciona bien, puede ir una versión por detrás)
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
# Los setups con Apache necesitan su propio plugin
sudo apt install -y python3-certbot-apache
# Verificar instalación
certbot --version
Nginx (Plugin Automático)
# Prueba primero contra staging — no gastes los rate limits
sudo certbot --nginx --staging -d example.com -d www.example.com
# Y luego en serio
sudo certbot --nginx -d example.com -d www.example.com
# Probar la renovación automática
sudo certbot renew --dry-run
El plugin --nginx lee tu server block, instala el certificado y configura la redirección a HTTPS. El resultado queda así:
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
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;
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';
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
http2 on; es la sintaxis actual — la forma antigua listen 443 ssl http2; está deprecated desde nginx 1.25 y emite un warning.
Apache (Plugin Automático)
sudo certbot --apache -d example.com -d www.example.com
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder on
</VirtualHost>
Standalone (Sin Servidor Web)
# Standalone levanta un servidor temporal en el puerto 80 — nada más puede ocuparlo
sudo certbot certonly --standalone -d example.com
# Los certificados quedan en /etc/letsencrypt/live/example.com/
# Apunta tu aplicación a:
# /etc/letsencrypt/live/example.com/fullchain.pem
# /etc/letsencrypt/live/example.com/privkey.pem
Desafío DNS para Certificados Wildcard
Los certificados wildcard (*.example.com) solo se pueden validar por DNS-01:
# Validación DNS manual — añades el registro TXT tú mismo
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d example.com \
--agree-tos --email admin@example.com
# Automatizada con Cloudflare
pip install certbot-dns-cloudflare
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
-d "*.example.com" -d example.com
# ~/.secrets/cloudflare.ini — restríngelo: sudo chmod 600 ~/.secrets/cloudflare.ini
dns_cloudflare_api_token = your-api-token-here
El archivo de credenciales guarda un API token en texto plano — déjalo en 600 y trátalo como una contraseña, la misma regla que aplicarías a cualquier secreto.
Docker / Certbot
# docker-compose.yml
services:
certbot:
image: certbot/certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
- ./certbot/log:/var/log/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
# nginx.conf
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://app:3000;
}
}
Ingress de Kubernetes con cert-manager
# cert-manager.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- example.com
- www.example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app
port:
number: 80
Mantén un segundo ClusterIssuer apuntando al endpoint de staging (https://acme-staging-v02.api.letsencrypt.org/directory) y cámbiate a él mientras depuras — la misma lógica de rate limits que con --staging.
Archivos complementarios: el repo companion de setup-ssl-certificates tiene el stack de docker-compose, el nginx.conf, los manifiestos de cert-manager y una plantilla de credenciales listos para ejecutar.
Renovación Automática
# Certbot instala un systemd timer en la mayoría de sistemas — verifica que está activo
sudo systemctl status certbot.timer
# Fallback con cron, con un hook que recarga el servidor web
sudo crontab -e
# Agregar:
# 0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"
El --deploy-hook importa más de lo que parece: certbot renew sustituye los archivos del certificado en disco, pero nginx sigue sirviendo el viejo hasta que recargas. Para un runbook operativo estructurado sobre esto, mira la plantilla de renovación de certificados.
Variantes
| Opción | Costo | Validación | Ideal para |
|---|---|---|---|
| Let’s Encrypt + Certbot | Gratis | HTTP-01 / DNS-01 | Sitios públicos, la mayoría de casos |
| ZeroSSL (ACME) | Tier gratis | HTTP / DNS | Mismo protocolo, CA alternativa |
| Cloudflare Origin CA | Gratis | Gestionado por Cloudflare | Sitios totalmente detrás de Cloudflare |
| HTTPS gestionado por la plataforma | Incluido | Automático | Vercel/Netlify/LB cloud — sin Certbot |
| CA de pago (DigiCert, etc.) | $$$ | Validación extendida | Requisitos de compliance, plazos largos |
| Autofirmado | Gratis | Ninguna | Solo interno/dev — los navegadores avisan |
Headers de Seguridad
# Agregar al server block HTTPS
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Headers de seguridad
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Reanudación de sesión — evita el handshake completo en clientes recurrentes
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
}
Una nota sobre OCSP stapling: verás ssl_stapling on; recomendado en tutoriales antiguos, pero Let’s Encrypt retiró su servicio OCSP en 2025. Con certificados de LE la directiva ya no hace nada — sáltala. Sigue aplicando a CAs que mantienen sus propios respondedores OCSP.
Lo que funciona
- Usa el plugin del servidor web (
--nginx,--apache) siempre que puedas — edita la configuración por ti y elimina toda una clase de errores. - Configura la renovación automática desde el día uno. Los certificados duran 90 días; un cron olvidado se convierte en una caída en producción.
- Incluye el dominio y el subdominio www en la solicitud para evitar advertencias en cualquiera de las URLs.
- Redirige HTTP a HTTPS en el servidor web, no en el código de aplicación — es más rápido y una cosa menos que mantener.
- Usa
fullchain.pem, nocert.pem. La cadena completa incluye los intermedios que los navegadores necesitan para validar la confianza.
Errores Comunes
- Cerrar el puerto 80 tras la configuración. La validación HTTP-01 se repite en cada renovación — no solo en la primera emisión.
- Pedir certificados para dominios internos (
app.local). Let’s Encrypt solo firma nombres públicamente resolvibles. - Probar contra producción. Unas pocas pruebas con
--stagingprimero te mantienen lejos del límite de 50 certs por semana. - Servir
cert.pemen vez defullchain.pem— los intermedios que faltan causan errores de “certificado no confiable”. - Renovar sin deploy hook. Los archivos cambian en disco; el servidor web sigue sirviendo el certificado viejo hasta recargar.
- No probar con
--dry-runantes de la ventana de renovación — una primera emisión exitosa no demuestra nada sobre la renovación.
Monitorización de la Expiración de Certificados
# Comprobar la expiración de forma remota
echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -dates
Para flotas, alerta con el blackbox exporter de Prometheus en lugar de comprobarlo a mano:
# Alertar si el certificado expira en menos de 7 días
- alert: SslCertificateExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 7 * 24 * 3600
for: 10m
labels:
severity: warning
Tips de Rendimiento
- Usa reanudación de sesión — una caché de sesiones compartida evita el handshake TLS completo en clientes que vuelven:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
- Prefiere ECDSA sobre RSA — claves más pequeñas implican handshakes más rápidos y menos CPU:
sudo certbot certonly --key-type ecdsa --elliptic-curve secp256r1 -d example.com
- Mantén HTTP/2 activado —
http2 on;multiplexa peticiones sobre la misma conexión TLS, que es donde está gran parte de la mejora real de velocidad.
Solución de Problemas
- “Connection refused” durante la emisión: el puerto 80 no es alcanzable desde internet — revisa firewall, security groups y que el DNS resuelve a este servidor.
- “Too many failed authorizations”: tocaste un rate limit de Let’s Encrypt — reintenta con
--stagingmientras depuras y espera a que pase la ventana de producción (normalmente una hora para validaciones fallidas, una semana para certificados). - La renovación funciona pero sigue sirviendo el certificado viejo: falta el
--deploy-hook— los archivos se renovaron pero nginx nunca recargó. certbot renewfalla en Docker: la ruta del desafío/.well-known/acme-challenge/no se está sirviendo — verifica que ellocationde nginx mapea al volumen compartidocertbot/www.
Lectura Recomendada
Preguntas frecuentes
¿Cuál es la diferencia entre un certificado autofirmado y uno firmado por CA?
Un certificado autofirmado es uno que generaste tú mismo — nada lo avala, así que los navegadores avisan. Uno firmado por CA lleva la acreditación de una autoridad confiable de que controlas el dominio, así que los navegadores lo aceptan por defecto.
¿Cómo valida Let's Encrypt la propiedad del dominio?
Mediante desafíos ACME — HTTP-01 coloca un token en /.well-known/acme-challenge/ que la CA descarga públicamente, mientras que DNS-01 exige un registro TXT. Los wildcard necesitan DNS-01.
¿Qué pasa si falla la renovación de un certificado?
El certificado actual sigue funcionando hasta su expiración a los 90 días — Certbot registra el error y reintenta en la próxima ejecución del timer. La monitorización (openssl o un probe blackbox) es lo que convierte un fallo silencioso en una alerta.
¿Por qué usar --staging al probar comandos de Certbot?
Staging emite certificados no confiables pero con rate limits mucho más laxos — te permite depurar tu setup sin consumir la cuota de producción de 50 certificados por dominio por semana.
¿Cómo revoco o borro un certificado?
certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem lo revoca públicamente; certbot delete --cert-name example.com elimina los archivos locales y la configuración de renovación.
Recursos Relacionados
Playbook de Ansible para Configuración de Servidores
Cómo escribir y ejecutar playbooks de Ansible para aprovisionar, configurar y administrar servidores con tareas idempotentes, roles e inventarios.
RecipeFundamentos de Docker: Dockerfile, Compose y Comandos
Cómo empaquetar una aplicación en un contenedor, escribir un Dockerfile y ejecutar contenedores con Docker Compose.
RecipeGestionar Secretos de Aplicaciones de Forma Segura
Cómo almacenar, rotar e inyectar API keys, contraseñas de base de datos y certificados sin hardcodearlos en código fuente o archivos de entorno.
RecipeImágenes de Producción Slim con Multi-Stage Docker
Cómo construir imágenes Docker de producción minimales usando multi-stage builds con distroless base images, cubriendo Go, Node.js, Python y Java con reducción de tamaño.
RecipeMontar Configs y Secrets en Pods de Kubernetes
Cómo montar ConfigMaps y Secrets en pods de Kubernetes usando env vars, volumes, projected volumes y secret management con external secrets.
DocPlantilla de Renovación de Certificados SSL
Plantilla para rastrear la caducidad de certificados SSL y los flujos de trabajo de renovación.