beginner Por Mathias Paulenko

Configurar Certificados SSL con Let's Encrypt

Cómo obtener, instalar y renovar automáticamente certificados SSL usando Certbot con Nginx, Apache y modos standalone para despliegues con HTTPS.

Temas: devops

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Descripción General

Let’s Encrypt es una autoridad certificadora gratuita y automatizada que provee certificados SSL/TLS confiables por todos los navegadores principales. Certbot es el cliente oficial que automatiza la emisión, instalación y renovación de certificados. Juntos eliminan el costo y trabajo manual de configurar HTTPS.

Antes de Let’s Encrypt, obtener un certificado SSL válido requería comprar a una CA comercial, generar un CSR, validar la propiedad del dominio vía email e instalar el certificado manualmente. El proceso tomaba horas o días y costaba cientos de dólares por año. Let’s Encrypt redujo esto a un solo comando y costo cero.

Cuándo Usar

Usa esta receta cuando:

  • Configuras HTTPS para una nueva aplicación web pública.
  • Reemplazas certificados expirados o autofirmados con otros confiables por navegadores.
  • Automatizas la renovación de certificados para que nunca expiren inadvertidamente.
  • Configuras SSL en un reverse proxy (Nginx, Apache, HAProxy) o load balancer.
  • Habilitas HTTPS en un contenedor Docker o ingress de Kubernetes.

Implementación Paso a Paso

Instalar Certbot

# Ubuntu / Debian
sudo apt update
sudo apt install -y certbot python3-certbot-nginx

# macOS (para testing)
brew install certbot

# Verificar instalación
certbot --version

Nginx (Plugin Automático)

# Emitir e instalar certificado en un solo paso
certbot --nginx -d example.com -d www.example.com

# Probar renovación automática
sudo certbot renew --dry-run

El plugin --nginx lee automáticamente el server block, instala el certificado y configura la redirección a HTTPS.

# /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;
    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;

    # Configuración TLS moderna
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Apache (Plugin Automático)

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

    # TLS moderno
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    SSLHonorCipherOrder on
</VirtualHost>

Standalone (Sin Servidor Web)

# Usar modo standalone cuando no hay servidor web corriendo
certbot certonly --standalone -d example.com

# Los certificados se guardan en /etc/letsencrypt/live/example.com/
# Configura tu aplicación para leer:
#   /etc/letsencrypt/live/example.com/fullchain.pem
#   /etc/letsencrypt/live/example.com/privkey.pem

Docker / Certbot

# docker-compose.yml
version: '3'
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;
    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;
    }
}

Auto-Renovación (Systemd Timer)

# Certbot instala un systemd timer automáticamente en la mayoría de sistemas
sudo systemctl status certbot.timer

# Fallback con cron manual
sudo crontab -e
# Agregar:
# 0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

Lo que funciona

  • Usa el plugin del servidor web (--nginx, --apache) cuando sea posible. Maneja la configuración automáticamente y reduce errores humanos.
  • Configura la auto-renovación desde el día uno. Los certificados de Let’s Encrypt expiran cada 90 días. Un cron job olvidado causa downtime en producción.
  • Incluye dominio y subdominio www en la solicitud del certificado para evitar warnings del navegador en cualquiera de las URLs.
  • Redirige HTTP a HTTPS a nivel de servidor web, no en código de aplicación. Es más rápido y confiable.
  • Usa fullchain.pem, no cert.pem. La cadena completa incluye certificados intermedios que los navegadores necesitan para validar la cadena de confianza.

Errores Comunes

  • Abrir el puerto 80 solo temporalmente durante la configuración inicial y luego cerrarlo. Let’s Encrypt requiere el puerto 80 abierto para validación HTTP-01 en cada renovación.
  • Solicitar un certificado para un dominio interno (ej., app.local). Let’s Encrypt solo emite certificados para dominios públicamente resolvibles.
  • Usar cert.pem en lugar de fullchain.pem causa errores “certificado no confiado” en algunos navegadores porque faltan intermedios.
  • No probar la renovación con --dry-run. Una emisión inicial exitosa no garantiza que la renovación funcionará. Pruébalo antes de los 90 días.
  • Ejecutar Certbot como root innecesariamente. Usa sudo para la instalación, pero considera ejecutar la renovación como un usuario dedicado con privilegios limitados.

Tips de Rendimiento

  1. Habilita OCSP stapling. Los clientes verifican el estado del cert sin contactar a la CA, reduciendo latencia del handshake:
ssl_stapling on;
ssl_stapling_verify on;
  1. Usa session resumption. Cachéa sesiones TLS para saltar el handshake completo en clientes recurrentes:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
  1. Prefiere ECDSA sobre RSA. Llaves más pequeñas significan handshakes más rápidos y menos uso de CPU:
certbot certonly --key-type ecdsa --elliptic-curve secp256r1 -d example.com

Preguntas frecuentes

¿Cuál es la diferencia entre un certificado autofirmado y uno firmado por CA?
Un certificado autofirmado lo emites tú y generará advertencias en el navegador. Un certificado firmado por CA es confiable por defecto porque los navegadores confían en la Autoridad Certificadora...
¿Cómo funciona Let's Encrypt?
Let's Encrypt es una CA gratuita que valida la propiedad del dominio mediante desafíos HTTP o DNS y emite certificados de corta duración, típicamente válidos por 90 días.
¿Por qué debería automatizar la renovación de certificados?
Los certificados TLS expiran. La renovación automatizada previene interrupciones del servicio por certificados vencidos y reduce el trabajo operativo manual.