8

TLS y Let’s Encrypt en Traefik

Vistas: 1
TLS y Let's Encrypt en Traefik

Hasta ahora hemos estado trabajando con HTTP sin cifrar. En los ejemplos de los capítulos anteriores usábamos el entrypoint web (puerto 80) porque era más sencillo para pruebas. Pero en producción, no te digo que sea obligatorio, pero estarías loco si no usaras HTTPS.

En este capítulo vas a ver cómo configurar Traefik v3 para que obtenga y renueve automáticamente certificados SSL de Let’s Encrypt. Y lo mejor de todo, sin tener que tocar nada cuando los certificados caducan. Traefik lo hace solito. Configuras una vez y te olvidas. ¿Te imaginas tener que recordar renovar certificados cada 90 días manualmente? Pues eso, con Traefik no pasa.

¿Cómo funciona ACME?

Let’s Encrypt utiliza el protocolo ACME (Automatic Certificate Management Environment) para emitir certificados. El proceso es simple,

  1. Tu servidor demuestra a Let’s Encrypt que controlas el dominio.
  2. Let’s Encrypt emite un certificado válido por 90 días.
  3. Traefik renueva el certificado automáticamente antes de que caduque.

Existen dos métodos para demostrar que controlas el dominio,

  • HTTP challenge: Let’s Encrypt accede a un archivo temporal en tu servidor a través del puerto 80.
  • DNS challenge: Let’s Encrypt verifica un registro TXT en tu DNS.

El protocolo ACME ha evolucionado a lo largo de los años. Hoy día estamos en la versión 2 del protocolo, que es la que usa Traefik v3. Si alguna vez oyes hablar de ACME v1, olvídalo, está obsoleto desde 2021.

HTTP Challenge

Es el más sencillo de los dos. Solo necesitas tener el puerto 80 accesible desde internet. Let’s Encrypt, cuando recibe la solicitud de certificado, intenta acceder a una URL concreta en tu dominio: http://tudominio.com/.well-known/acme-challenge/<token>. Si Traefik responde correctamente con el token, la CA entiende que controlas el dominio y emite el certificado.

En tu traefik.yml, añades la configuración ACME,

certificatesResolvers:
  letsencrypt:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

Pero ojo, para que el HTTP challenge funcione, el entrypoint web debe estar en el puerto 80, y no tener redirección a HTTPS. Porque Let’s Encrypt tiene que acceder por HTTP a un archivo temporal.

Si tienes la redirección automática de HTTP a HTTPS (como vimos en el capítulo de EntryPoints), no hay problema siempre que el HTTP challenge se procese antes de la redirección. Traefik es lo suficientemente inteligente para hacer esto. Internamente, intercepta las peticiones a /.well-known/acme-challenge/ y las procesa antes de aplicar cualquier redirección. Vamos, que no te tienes que preocupar por el orden.

El archivo traefik.yml completo quedaría así,

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

api:
  dashboard: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

Fíjate en el parámetro storage. Es la ruta donde Traefik guarda los certificados. Debe estar en un volumen persistente. Si pierdes este archivo, Let’s Encrypt te limitará las peticiones porque tendrás que solicitar certificados nuevos. Esto te puede ahorrar más de un disgusto: asegúrate de tener backups del acme.json junto con el resto de tu configuración.

Usar el entorno de staging para pruebas

Antes de lanzarte a producción con Let’s Encrypt, te recomiendo que uses el entorno de staging. Es una CA de pruebas que emite certificados válidos, pero no son de confianza para los navegadores. Sirve para verificar que todo funciona sin golpearte contra los rate limits.

certificatesResolvers:
  letsencrypt:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory
      httpChallenge:
        entryPoint: web

El parámetro caServer apunta al servidor de staging de Let’s Encrypt. Los certificados que obtengas aquí no serán válidos en navegadores, pero te permiten probar todo el flujo sin miedo a que te bloqueen por exceder peticiones. Cuando todo funcione, quitas esa línea y Traefik usará el servidor de producción por defecto (https://acme-v02.api.letsencrypt.org/directory).

Activar TLS en los servicios

Ahora, para que un servicio use HTTPS, tienes que indicarlo en las labels del router,

services:
  miapp:
    image: nginx:alpine
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.miapp.rule=Host(`midominio.com`)"
      - "traefik.http.routers.miapp.entrypoints=websecure"
      - "traefik.http.routers.miapp.tls=true"
      - "traefik.http.routers.miapp.tls.certresolver=letsencrypt"
    networks:
      - traefik-net

Dos cosas importantes aquí,

  • entrypoints=websecure: el router escucha en el entrypoint HTTPS.
  • tls=true: habilita TLS para este router. Sin esto, el router usaría HTTP aunque esté en el entrypoint websecure.
  • tls.certresolver=letsencrypt: indica qué resolver de certificados usar.

Si no indicas el certresolver, Traefik usará el certificado por defecto (si lo has configurado) o generará uno autofirmado. Pero ojo, sin esta label no pedirá un certificado de Let’s Encrypt para ese dominio.

DNS Challenge

El HTTP challenge tiene una limitación importante: necesitas tener el puerto 80 abierto y accesible desde internet. ¿Y si no puedes o no quieres abrirlo? ¿O si tu servidor está en una red interna sin IP pública? Ahí entra el DNS challenge.

Con el DNS challenge, Let’s Encrypt te pide que añadas un registro TXT a tu DNS. Traefik puede hacerlo automáticamente si configuras las credenciales de tu proveedor de DNS. Internamente usa la librería lego, que soporta decenas de proveedores.

Las ventajas del DNS challenge son enormes:

  • No necesitas tener el puerto 80 abierto. Esto es genial si solo quieres usar el 443 directamente.
  • Puedes obtener certificados wildcard (*.dominio.com), algo que el HTTP challenge no permite por cómo funciona el protocolo.
  • Funciona aunque tu servidor no sea accesible desde internet (redes internas, desarrollo local, laboratorios caseros).
  • Ideal para entornos cloud donde el balanceador de carga maneja el tráfico y tu servidor está en una red privada.

La configuración varía según tu proveedor de DNS. Vamos a ver los más comunes.

Cloudflare

Cloudflare es probablemente el proveedor más usado con Traefik. Soporta wildcards, es rápido y la API es fiable.

certificatesResolvers:
  letsencrypt-dns:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

Necesitas pasar las credenciales de Cloudflare como variables de entorno en el docker-compose.yml,

services:
  traefik:
    image: traefik:v3.7
    environment:
      - CF_DNS_API_TOKEN=tu_token_api_cloudflare
      # ...

Puedes usar tanto el API Token (recomendado) como el API Key global. El token necesita permisos de DNS:Edit para la zona correspondiente. Si usas la key global, la variable se llama CF_API_EMAIL y CF_API_KEY. Pero te recomiendo el token, es más seguro porque puedes limitar los permisos.

Importante con Cloudflare: si tienes el proxy (naranjita) activado en tu DNS, Cloudflare actúa como proxy inverso. Para el DNS challenge esto no afecta porque la validación se hace a nivel de DNS, no HTTP. Pero una vez tengas el certificado, asegúrate de que la comunicación entre Cloudflare y tu servidor sea HTTPS también. En el panel de Cloudflare, en SSL/TLS, pon el modo en Full (strict). Si lo dejas en Flexible, Cloudflare se comunicará con tu servidor por HTTP plano, y estarías perdiendo el sentido de tener HTTPS.

DuckDNS

DuckDNS es genial para dominios dinámicos y laboratorios caseros.

certificatesResolvers:
  letsencrypt-dns:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: duckdns

Y las variables de entorno,

environment:
  - DUCKDNS_TOKEN=tu_token_duckdns

El token lo obtienes de la web de DuckDNS cuando te registras. Es un token fijo, no caduca. Con esto y un dominio de DuckDNS tipo miserver.duckdns.org puedes tener HTTPS funcionando en cuestión de minutos.

OVH

OVH es uno de los proveedores de dominio más usados en España y Europa. La configuración con Traefik requiere tres variables de entorno,

certificatesResolvers:
  letsencrypt-ovh:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: ovh
        resolvers:
          - "1.1.1.1:53"

Las variables de entorno necesarias son,

environment:
  - OVH_APPLICATION_KEY=tu_application_key
  - OVH_APPLICATION_SECRET=tu_application_secret
  - OVH_CONSUMER_KEY=tu_consumer_key

Para obtener estas credenciales tienes que registrar una aplicación en la web de OVH:

  1. Ve a https://api.ovh.com/createApp/
  2. Crea una nueva aplicación. Te darán el Application Key y el Application Secret.
  3. Con esos datos, genera un Consumer Key con permisos para gestionar DNS en /domain/zone/*.

Si es la primera vez que configuras la API de OVH, el proceso puede parecerte un poco lioso. Básicamente, la Application Key identifica tu aplicación, el Application Secret es como una contraseña de esa aplicación, y el Consumer Key autoriza a esa aplicación a actuar en tu cuenta de OVH.

AWS Route53

Si usas AWS para tus dominios, Route53 está soportado,

certificatesResolvers:
  letsencrypt-route53:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: route53

Las variables de entorno,

environment:
  - AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
  - AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
  - AWS_REGION=eu-west-1
  - AWS_HOSTED_ZONE_ID=ZONEID

El usuario de AWS IAM necesita permisos para route53:ChangeResourceRecordSets y route53:GetChange. La política mínima sería algo así,

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets",
        "route53:GetChange"
      ],
      "Resource": "*"
    }
  ]
}

Si gestionas múltiples zonas alojadas en Route53, te recomiendo especificar AWS_HOSTED_ZONE_ID para evitar ambigüedades. Si no lo pones, Traefik intentará auto-descubrir la zona, y si tienes varias, puede fallar.

DigitalOcean

Otro proveedor muy popular, sobre todo para VPS,

certificatesResolvers:
  letsencrypt-do:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: digitalocean

Y las variables de entorno,

environment:
  - DO_AUTH_TOKEN=tu_token_digitalocean

El token lo generas desde el panel de DigitalOcean, en API > Tokens/Keys. Necesita permisos de escritura para poder añadir y eliminar registros TXT.

Gandi

Gandi también está soportado, aunque la API ha cambiado con los años,

certificatesResolvers:
  letsencrypt-gandi:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: gandi

Variables de entorno,

environment:
  - GANDI_API_KEY=tu_api_key_gandi

Si usas la nueva API v5 de Gandi, asegúrate de que tu API key tenga los permisos adecuados. La API key la generas desde la sección de seguridad de tu cuenta en Gandi.

Namecheap

Namecheap también funciona, aunque tiene algunas particularidades,

certificatesResolvers:
  letsencrypt-namecheap:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: namecheap

Variables de entorno,

environment:
  - NAMECHEAP_API_USER=tu_usuario_namecheap
  - NAMECHEAP_API_KEY=tu_api_key_namecheap

Namecheap requiere que tu IP esté whitelisteada en el panel de API. Si no haces esto, la API te devolverá error. Además, el API user no es tu nombre de usuario de login, sino el mismo nombre de usuario (sin el @). Puede ser confuso al principio.

Configuración avanzada del DNS challenge

Además del provider, tienes algunas opciones extra que pueden salvarte en según qué situaciones,

dnsChallenge:
  provider: cloudflare
  resolvers:
    - "1.1.1.1:53"
    - "8.8.8.8:53"
  propagation:
    delayBeforeChecks: 0
  • resolvers: servidores DNS que usa Traefik para verificar que el registro TXT se ha propagado. Por defecto usa los del sistema, pero puedes especificar los que quieras. Esto te puede ahorrar más de un disgusto si tu DNS tarda en propagarse.
  • propagation.delayBeforeChecks: tiempo en segundos que Traefik espera antes de comprobar si el registro TXT se ha propagado. Si tu proveedor de DNS es lento propagando (como OVH o Namecheap), poner un valor de 30 o 60 segundos puede evitar fallos. El valor por defecto es 0, pero te recomiendo subirlo si ves errores de propagación en los logs.

Certificados wildcard

Con el DNS challenge puedes obtener certificados wildcard. Esto es realmente útil si tienes muchos subdominios y no quieres un certificado para cada uno. Aunque la realidad es que con la capacidad de Traefik de renovar automáticamente, no es tan necesario como antes. Pero sigue teniendo sus ventajas.

Un certificado wildcard cubre *.dominio.com y todos sus subdominios. Es decir, con un solo certificado tienes cubiertos app.dominio.com, api.dominio.com, blog.dominio.com, etc. La limitación es que no cubre el dominio desnudo (dominio.com) a menos que lo incluyas explícitamente como SAN.

Configurar wildcard a nivel de router

Para wildcard, la regla en el router debe usar HostRegex en lugar de Host,

labels:
  - "traefik.http.routers.miapp.rule=HostRegex(`{subdomain:.+}.dominio.com`)"
  - "traefik.http.routers.miapp.tls.domains[0].main=dominio.com"
  - "traefik.http.routers.miapp.tls.domains[0].sans=*.dominio.com"

El parámetro tls.domains es clave aquí. main es el dominio principal y sans son los Subject Alternative Names. Traefik usará esta información para solicitar el certificado a Let’s Encrypt.

Configurar wildcard a nivel de entrypoint

O puedes configurarlo a nivel de TLS en el entrypoint, y así cualquier servicio que use ese entrypoint tendrá el certificado wildcard disponible sin tener que configurarlo en cada router,

entryPoints:
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt-dns
        domains:
          - main: "dominio.com"
            sans:
              - "*.dominio.com"

Esta configuración global es muy práctica si tienes decenas de servicios. Configuras el wildcard una vez y todos los routers que usen websecure se beneficiarán de él.

¿Cuándo usar wildcard y cuándo certificados individuales?

Los wildcard son cómodos, pero no siempre son la mejor opción,

  • Wildcard: ideal si tienes muchos subdominios, si tu DNS es lento (menos renovaciones), o si quieres simplificar la configuración.
  • Individuales: mejor si tienes pocos subdominios, si quieres aislar dominios por seguridad, o si usas HTTP challenge (que no permite wildcard).

En mi experiencia, para un homelab o un servidor con 5-10 servicios, el wildcard es una gozada. Para un despliegue empresarial con dominios separados, igual prefieres ir uno a uno.

Renovación automática

Una de las mejores características de Traefik es que la renovación de certificados es completamente automática. No necesitas cron jobs, no necesitas scripts, no necesitas acordarte de nada.

¿Cómo funciona la renovación?

Traefik revisa los certificados almacenados en acme.json periódicamente. Cuando un certificado está a menos de 30 días de caducar, inicia el proceso de renovación automática. El proceso es idéntico al de obtención inicial: Traefik realiza el challenge (HTTP o DNS), obtiene el nuevo certificado, y lo almacena en acme.json.

En los logs lo verás así,

time="2025-06-10T10:00:00Z" level=info msg="Testing certificate renew..." providerName=letsencrypt.acme
time="2025-06-10T10:00:05Z" level=info msg="The certificate for [midominio.com] is renewed..." providerName=letsencrypt.acme

Si la renovación falla, Traefik lo reintenta. Y si falla repetidamente, el certificado antiguo sigue siendo válido hasta que caduque. Así que no te vas a quedar sin servicio de repente.

¿Qué ocurre si Traefik se reinicia?

Si reinicias Traefik, este vuelve a leer acme.json y reutiliza los certificados existentes. No los vuelve a pedir a Let’s Encrypt. Eso significa que puedes reiniciar el contenedor cuantas veces quieras sin preocuparte por los rate limits.

Otra cosa importante: si eliminas el volumen donde está acme.json y reinicias Traefik, este tendrá que solicitar todos los certificados de nuevo. Y ahí es donde puedes toparte con los rate limits de Let’s Encrypt. Por eso te decía antes que hagas backup de ese archivo.

Forzar la renovación manual

Si necesitas forzar la renovación de un certificado (por ejemplo, porque cambiaste el dominio o porque sospechas que el certificado actual tiene algún problema), puedes hacerlo eliminando la entrada del certificado en acme.json y reiniciando Traefik. Pero ojo con los rate limits.

Otra forma más limpia es cambiar el email de ACME en la configuración, reiniciar Traefik y luego volver a poner el email original. Pero vamos, que eliminar la entrada de acme.json es más directo. El acme.json tiene una estructura JSON con los certificados almacenados. Busca el bloque de tu dominio y elimínalo.

docker exec traefik cat /letsencrypt/acme.json | jq '.'

Luego, si editas el archivo (con cuidado), puedes borrar solo el certificado que te interesa renovar.

Certificados personalizados

A veces no quieres usar Let’s Encrypt. Puede ser porque tienes un certificado de pago, porque tienes una CA interna en tu organización, o simplemente porque quieres usar un certificado autofirmado para desarrollo.

Para usar certificados personalizados, puedes montarlos como archivos en el contenedor de Traefik,

volumes:
  - ./certs/micertificado.crt:/etc/traefik/certs/micertificado.crt:ro
  - ./certs/micertificado.key:/etc/traefik/certs/micertificado.key:ro

Y configurarlos en el traefik.yml,

tls:
  certificates:
    - certFile: /etc/traefik/certs/micertificado.crt
      keyFile: /etc/traefik/certs/micertificado.key

También puedes usar un certificado por defecto para todos los servicios que no tengan un certificado específico,

tls:
  stores:
    default:
      defaultCertificate:
        certFile: /etc/traefik/certs/default.crt
        keyFile: /etc/traefik/certs/default.key

Esto es útil cuando tienes servicios internos que no necesitan un certificado de una CA pública. Por ejemplo, si tienes un servicio de monitorización interna al que solo accedes desde tu VPN, un certificado autofirmado o de tu CA interna es más que suficiente.

Configuración TLS avanzada

Traefik v3 te permite configurar aspectos avanzados de TLS, como las versiones de TLS permitidas, las curvas elípticas, o las suites de cifrado. No voy a mentirte: esto es para casos muy concretos, pero está bien saber que existe.

tls:
  options:
    default:
      minVersion: VersionTLS12
      maxVersion: VersionTLS13
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
      sniStrict: false
  • minVersion: la versión mínima de TLS. Te recomiendo VersionTLS12. VersionTLS10 y VersionTLS11 están obsoletas y deberías evitarlas.
  • maxVersion: la versión máxima. VersionTLS13 es la más segura y rápida.
  • cipherSuites: las suites de cifrado permitidas. Si no sabes qué poner, déjalo vacío y Traefik usará las seguras por defecto. Meterme aquí a recomendarte suites concretas sería alargarme, pero si tienes que pasar audits de seguridad, este es tu sitio.
  • sniStrict: si es true, Traefik rechaza conexiones SNI que no coincidan con ningún certificado configurado. Por defecto está a false, lo que permite que Traefik sirva el certificado por defecto cuando no hay match.

Si necesitas cumplir con estándares como PCI DSS o simplemente pasar un audit de seguridad, configura minVersion: VersionTLS12 y elimina suites inseguras como las que usan RC4, 3DES o CBC. Si no, con dejarlo por defecto vas sobrado.

Ejemplos prácticos completos

Voy a dejarte dos ejemplos completos. Uno con HTTP challenge para el caso más simple, y otro con DNS challenge y Cloudflare para un escenario más realista con wildcard.

Ejemplo 1: HTTP challenge con Nextcloud y WordPress

Este es el montaje típico de un servidor casero con varios servicios.

docker-compose.yml,

services:
  traefik:
    image: traefik:v3.7
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik-net

  nextcloud:
    image: nextcloud:stable
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nextcloud.rule=Host(`cloud.tudominio.com`)"
      - "traefik.http.routers.nextcloud.entrypoints=websecure"
      - "traefik.http.routers.nextcloud.tls=true"
      - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
    volumes:
      - nextcloud-data:/var/www/html
    networks:
      - traefik-net

  wordpress:
    image: wordpress:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.wp.rule=Host(`blog.tudominio.com`)"
      - "traefik.http.routers.wp.entrypoints=websecure"
      - "traefik.http.routers.wp.tls=true"
      - "traefik.http.routers.wp.tls.certresolver=letsencrypt"
    volumes:
      - wp-data:/var/www/html
    networks:
      - traefik-net

volumes:
  nextcloud-data:
  wp-data:

networks:
  traefik-net:
    name: traefik-net

Y el traefik.yml correspondiente,

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

api:
  dashboard: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

Con este ejemplo tienes dos servicios (Nextcloud y WordPress) cada uno con su propio certificado de Let’s Encrypt. Traefik los solicita y renueva automáticamente.

Ejemplo 2: DNS challenge con Cloudflare y wildcard

Este es para los que quieren un certificado wildcard y no quieren abrir el puerto 80.

docker-compose.yml,

services:
  traefik:
    image: traefik:v3.7
    container_name: traefik
    restart: unless-stopped
    ports:
      - "443:443"
    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik-net

  whoami:
    image: traefik/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`app.tudominio.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls=true"
    networks:
      - traefik-net

  portainer:
    image: portainer/portainer-ce:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`admin.tudominio.com`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls=true"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - portainer-data:/data
    networks:
      - traefik-net

volumes:
  portainer-data:

networks:
  traefik-net:
    name: traefik-net

Y el traefik.yml con el wildcard configurado a nivel de entrypoint,

entryPoints:
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt-dns
        domains:
          - main: "tudominio.com"
            sans:
              - "*.tudominio.com"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

api:
  dashboard: true

certificatesResolvers:
  letsencrypt-dns:
    acme:
      email: tu@email.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"

Fíjate que aquí no necesitas abrir el puerto 80. El entrypoint web ni siquiera está definido. Todo el tráfico va por HTTPS directamente. Además, al tener el wildcard configurado a nivel de entrypoint, cualquier servicio nuevo que añadas al docker-compose con entrypoints=websecure y tls=true usará automáticamente el certificado wildcard. Sin tener que especificar certresolver en cada uno.

Resolución de problemas

Si los certificados no se generan, estos son los pasos que te recomiendo para diagnosticarlo,

  1. Comprueba los logs de Traefik:
   docker logs traefik | grep -i acme

Aquí verás si Let’s Encrypt está intentando validar el dominio y qué error da. Si no ves nada, prueba con docker logs traefik | grep -i "certificate|lego|acme|challenge" para capturar más contexto.

  1. Verifica que el puerto 80 es accesible (para HTTP challenge):
   curl -I http://tudominio.com

Si no responde, el HTTP challenge fallará. Comprueba también que no hay un firewall bloqueando el puerto, o que tu router no tiene el puerto cerrado.

  1. Comprueba el archivo acme.json:
   docker exec traefik cat /letsencrypt/acme.json | jq '.'

Si el archivo está vacío o no existe, significa que aún no se ha obtenido ningún certificado. Si existe pero no ves tu dominio, algo falló en el proceso.

  1. Rate limits: si haces muchas pruebas, Let’s Encrypt te puede limitar. Tienen un rate limit de 50 certificados por dominio por semana. Así que ve con cuidado si estás probando. Si te pasas, recibirás un error como acme: error: 429 :: POST :: https://acme-v02.api.letsencrypt.org/acme/new-order :: 429 - urn:ietf:params:acme:error:rateLimited. En ese caso, no te queda más que esperar. Por eso te recomiendo usar el entorno de staging para las pruebas.
  2. Problemas de propagación DNS: si usas DNS challenge y ves errores como time limit exceeded: last error: NS ... returned REFUSED, puede que tu DNS esté tardando en propagarse. Prueba a aumentar delayBeforeCheck a 60 segundos. Puedes verificar la propagación manualmente con,
   dig _acme-challenge.tudominio.com TXT +short

Si no ves el registro, el DNS aún no se ha propagado.

  1. Usa el entorno de staging de Let’s Encrypt: antes de lanzarte a producción, configura el caServer de staging. Así evitas quemar los rate limits de producción mientras haces pruebas,
   certificatesResolvers:
     letsencrypt:
       acme:
         caServer: https://acme-staging-v02.api.letsencrypt.org/directory
         ...

Los certificados de staging no son válidos en navegadores, pero te permiten probar el flujo completo sin riesgo.

  1. Problemas con Cloudflare: si usas Cloudflare como proxy (naranjita), recuerda que el DNS challenge funciona a nivel de DNS, no HTTP, así que no debería afectar. Pero si tienes problemas, prueba a poner el DNS en modo «DNS only» (gris) para descartar. Además, si Cloudflare está en modo Flexible, la comunicación entre Cloudflare y tu servidor va por HTTP. Eso no afecta al certificado de Let’s Encrypt en Traefik, pero el usuario final vería el certificado de Cloudflare, no el tuyo.
  2. Error de conexión timeout: si ves errores de timeout en los logs, puede que tu servidor no tenga salida a internet, o que un firewall esté bloqueando las conexiones salientes. Let’s Encrypt necesita que tu servidor pueda conectarse a sus servidores.
  3. Certificado no se renueva: si ves en los logs que Traefik intenta renovar pero falla, revisa que las credenciales del DNS provider siguen siendo válidas. Los tokens de API pueden caducar. También comprueba que el archivo acme.json no está corrupto. Si lo está, elimínalo (haciendo backup primero) y reinicia Traefik para que solicite los certificados de nuevo.
  4. Modo debug: si nada de lo anterior funciona, activa el log en modo DEBUG para obtener información detallada,
    yaml log: level: DEBUG
    Esto genera muchos logs, así que úsalo solo para diagnosticar y luego vuelve a INFO.

Conclusión

El TLS con Let’s Encrypt es una de las grandes ventajas de Traefik. Olvídate de gestionar certificados manualmente, de renovaciones, de tener que acordarte de cuándo caducan. Traefik hace todo el trabajo pesado por ti.

La clave está en elegir el challenge adecuado: HTTP challenge si tienes el puerto 80 abierto y no necesitas wildcard; DNS challenge si quieres wildcard, no tienes puerto 80, o tu servidor está en una red interna. Y si usas DNS challenge, te recomiendo Cloudflare por su rapidez y fiabilidad, aunque cualquiera de los proveedores que hemos visto funciona perfectamente.

En el próximo capítulo veremos cómo configurar el Dashboard de Traefik de forma segura. Porque ahora que ya tienes HTTPS, no vas a dejar el dashboard accesible sin protección, ¿verdad?


Más información

Deja una respuesta