Tutorial: configurar mTLS entre Traefik y servicios en Docker Compose usando smallstep CA y certificados automáticos — paso a paso

Docker & Containers · Por · Publicado el · 12 min de lectura

acercamiento de pantalla con código fuente coloreado en editor, enfoque selectivo y líneas de sintaxis resaltadas
Visual técnico adecuado para un tutorial de mTLS entre Traefik y servicios Docker. Foto: Godfrey_atima / Pixabay

Este tutorial explica cómo configurar mTLS entre Traefik y servicios dentro de Docker Compose utilizando una CA local smallstep (step-ca) y certificados con emisión/renovación automáticas. Verá la arquitectura, los archivos necesarios, comandos y la verificación punta a punta. Traefik autenticará ante el backend mediante un certificado de cliente, y los servicios exigirán mTLS para aceptar conexiones, con la emisión y renovación gestionadas por step-ca.

Arquitectura: cómo funciona el mTLS entre Traefik y los servicios

Traefik actuará como proxy inverso y se conectará a los servicios de backend por TLS, presentando un certificado de cliente y validando el certificado del servidor. El backend exigirá un certificado cliente válido para aceptar la conexión.

Usaremos una Autoridad Certificadora local (smallstep step-ca) para emitir: (1) un certificado de cliente para Traefik y (2) un certificado de servidor para cada servicio. La renovación se realiza automáticamente con el CLI de step.

Este enfoque protege la conexión interna, reduce el riesgo de ataques man‑in‑the‑middle y autentica ambos extremos de la conexión. Si está evaluando su PKI, compare alternativas en smallstep vs HashiCorp Vault para mTLS en Docker Compose.

  • Traefik valida el certificado del servidor (CA raíz de confianza).
  • El servicio valida el certificado cliente de Traefik (mTLS obligatorio).
  • Certificados emitidos y renovados por step-ca mediante el step CLI.
  • El enrutamiento HTTP externo sigue igual; mTLS se aplica solo en upstream Traefik → servicios.
Componente Certificados/Archivos Finalidad
step-ca root_ca.crt, intermediaria, ca.json Emitir y renovar certificados
Traefik traefik-client.crt, traefik-client.key, root_ca.crt mTLS como cliente; confiar en el backend
Servicio (p. ej., Nginx) server.crt, server.key, root_ca.crt TLS del servidor y exigir certificado de cliente

Prerequisitos y alcance del entorno

Necesita Docker y Docker Compose instalados, acceso a shell y las puertos 80 y 9000 libres. Crearemos una red Docker única con tres servicios: step-ca, Traefik y un backend Nginx como ejemplo.

Los dominios usados son locales (app.localhost) y nombres internos (app.internal). Adáptelos a su entorno. No exponga la CA a internet; manténgala accesible únicamente en la red interna del Compose.

  • Docker y Compose actualizados.
  • Puertos 80 (Traefik) y 9000 (step-ca) libres.
  • Editor de texto para crear archivos.
  • Conocimientos básicos de YAML y TLS (SAN, CA).

Cómo iniciar una smallstep CA en Docker y obtener el fingerprint

La smallstep CA (step-ca) será nuestra PKI interna. Inicializaremos el repositorio de la CA en un volumen Docker, generaremos root/intermedia y guardaremos la contraseña. El fingerprint de la raíz se usará para el bootstrap seguro por los clientes.

Los comandos siguientes crean el volumen, inicializan la CA e imprimen el fingerprint de la raíz.

# 1) Crie o volume de dados da CA
docker volume create step-ca

# 2) Gere uma senha e inicialize a estrutura de diretórios
# (a senha protege as chaves privadas da CA)
docker run --rm -v step-ca:/home/step alpine sh -c '
  set -e; mkdir -p /home/step/secrets;
  head -c 16 /dev/urandom | base64 > /home/step/secrets/password
'

# 3) Inicialize a CA (nome, DNS interno e provisionador de login)
docker run --rm -it -v step-ca:/home/step smallstep/step-ca \
  step ca init \
    --deployment-type standalone \
    --name "Local Dev CA" \
    --dns "step-ca" \
    --address ":9000" \
    --provisioner "[email protected]" \
    --password-file /home/step/secrets/password

# 4) Descubra o fingerprint da raiz (copie para usar no Compose)
docker run --rm -v step-ca:/home/step smallstep/step-ca \
  step certificate fingerprint /home/step/certs/root_ca.crt

# Guarde o fingerprint impresso (ex.: 7f:ab:...): vamos usar como STEP_FINGERPRINT.

Cómo emitir y renovar certificados automáticos en el servicio backend (Nginx)

El backend necesita dos elementos: (a) un certificado de servidor emitido por la CA y (b) la configuración de Nginx para exigir certificado cliente. Usaremos una imagen personalizada con el step CLI para emitir y renovar el certificado, recargando Nginx cuando el archivo cambie.

Cree los archivos en el directorio ./app. La renovación se ejecuta en segundo plano y provoca un reload de Nginx sin interrumpir conexiones activas.

# file: app/Dockerfile
FROM nginx:alpine
RUN apk add --no-cache curl tar ca-certificates bash \
 && update-ca-certificates
# Instala o step CLI (verifique a URL mais recente conforme sua política interna)
RUN curl -fsSL https://dl.smallstep.com/cli/releases/latest/step_linux_amd64.tar.gz \
  | tar -xz -C /usr/local/bin --strip-components=1 'step_*/bin/step'
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

# file: app/nginx.conf
server {
  listen 443 ssl http2;
  server_name app.internal;

  ssl_certificate     /etc/nginx/certs/server.crt;
  ssl_certificate_key /etc/nginx/certs/server.key;

  # Exigir certificado de cliente (mTLS)
  ssl_client_certificate /step/certs/root_ca.crt; # CA raiz confiável
  ssl_verify_client on;

  # End-point exemplo
  location / {
    add_header Content-Type text/plain;
    return 200 'ok from app (mTLS required)\n';
  }
}

# file: app/entrypoint.sh
#!/usr/bin/env bash
set -euo pipefail
: "${STEP_FINGERPRINT:?Defina STEP_FINGERPRINT no compose}"
: "${STEP_PROVISIONER:?Defina STEP_PROVISIONER no compose}"

# Bootstrap confiança na CA interna (via DNS step-ca na rede Docker)
step ca bootstrap --ca-url https://step-ca:9000 --fingerprint "$STEP_FINGERPRINT"

# Emite o certificado de servidor (SAN: app.internal)
mkdir -p /etc/nginx/certs
step ca certificate "app.internal" \
  /etc/nginx/certs/server.crt /etc/nginx/certs/server.key \
  --provisioner "$STEP_PROVISIONER" --no-password --san "app.internal"

# Renova automaticamente e recarrega o Nginx quando necessário
step ca renew /etc/nginx/certs/server.crt /etc/nginx/certs/server.key \
  --daemon --exec "nginx -s reload"

# Inicia o Nginx em primeiro plano
exec nginx -g 'daemon off;'

Cómo configurar Traefik para mTLS con los backends

Traefik necesita: (a) un certificado de cliente para presentar al backend y (b) confiar en la CA raíz para validar el certificado del servidor. Usaremos una sidecar con el step CLI para emitir/renovar el certificado de cliente y un archivo dinámico de Traefik para definir el serversTransport con mTLS.

Para estrategias de enrutamiento gradual, vea ejemplos de canary en Canary releases con Traefik en Docker Compose.

  • Traefik usa serversTransport para mTLS en el upstream.
  • El router expone app.localhost vía HTTP y reenvía por HTTPS+mTLS al backend.
  • La sidecar mantiene el certificado cliente renovado; reiniciar Traefik aplica el nuevo certificado.
# file: traefik/dynamic.yml
http:
  serversTransports:
    mtls-to-app:
      serverName: app.internal
      rootCAs:
        - /step/certs/root_ca.crt
      certificates:
        - certFile: /certs/traefik-client.crt
          keyFile: /certs/traefik-client.key

  routers:
    app:
      rule: Host(`app.localhost`)
      entryPoints: ["web"]
      service: app

  services:
    app:
      loadBalancer:
        servers:
          - url: https://app:443
        serversTransport: mtls-to-app

Levantar todo con Docker Compose (Traefik, step-ca y backend)

El docker-compose.yml integra la CA, Traefik, la sidecar que gestiona el certificado cliente y el backend Nginx. Antes de levantar, sustituya el valor de STEP_FINGERPRINT por el fingerprint obtenido al inicializar la CA.

En el primer arranque, la sidecar y el backend emitirán certificados; la renovación se ejecuta en segundo plano. Para aplicar certificados renovados en Traefik, reinicie el contenedor cuando lo desee.

# file: docker-compose.yml
version: "3.9"

services:
  step-ca:
    image: smallstep/step-ca:latest
    command: ["step-ca", "/home/step/config/ca.json"]
    volumes:
      - step-ca:/home/step
    ports:
      - "9000:9000"  # somente para laboratório; evite expor em produção
    networks: [internal]

  traefik-client:
    image: smallstep/step-ca:latest  # contém o step CLI
    depends_on:
      - step-ca
    environment:
      STEP_FINGERPRINT: "COLE_AQUI_O_FINGERPRINT_DA_RAIZ"
      STEP_PROVISIONER: "[email protected]"
    volumes:
      - step-ca:/step:ro
      - traefik-certs:/certs
    entrypoint: ["/bin/sh","-c"]
    command: >-
      step ca bootstrap --ca-url https://step-ca:9000 --fingerprint $STEP_FINGERPRINT &&
      step ca certificate traefik-client /certs/traefik-client.crt /certs/traefik-client.key \
        --provisioner $STEP_PROVISIONER --no-password --kty RSA &&
      step ca renew /certs/traefik-client.crt /certs/traefik-client.key --daemon
    networks: [internal]

  traefik:
    image: traefik:v2.10
    depends_on:
      - traefik-client
    command:
      - "--providers.docker=true"
      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.file.watch=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/dynamic.yml:/etc/traefik/dynamic/dynamic.yml:ro
      - traefik-certs:/certs:ro
      - step-ca:/step:ro
    networks: [internal]

  app:
    build: ./app
    depends_on:
      - step-ca
    environment:
      STEP_FINGERPRINT: "COLE_AQUI_O_FINGERPRINT_DA_RAIZ"
      STEP_PROVISIONER: "[email protected]"
    volumes:
      - step-ca:/step:ro  # para confiar na raiz da CA
    networks: [internal]

volumes:
  step-ca:
  traefik-certs:
  
networks:
  internal:
    driver: bridge

Cómo confirmar que funcionó: pruebas rápidas

Compruebe que Traefik responde por HTTP y enruta al backend por HTTPS+mTLS. Luego, intente acceder al backend directamente sin cliente mTLS para comprobar que falla, como se espera.

Si algo falla, revise los logs de los contenedores y valide nombres (serverName, SAN app.internal) y el fingerprint.

  • La prueba vía Traefik debe devolver 200 con el mensaje del backend.
  • Acceder directamente al backend sin cliente mTLS debe fallar (handshake).
  • Los logs del Nginx muestran 'ssl client verify successful' al pasar por Traefik.
# 1) Suba o ambiente
docker compose up -d --build

# 2) Acesse via Traefik (host header para roteamento)
curl -sS -H 'Host: app.localhost' http://127.0.0.1/
# Esperado: ok from app (mTLS required)

# 3) Teste o backend sem cliente mTLS (deve falhar)
docker compose run --rm alpine sh -lc "apk add --no-cache curl >/dev/null && curl -ksS https://app:443 || echo 'falha esperada (mTLS)'"

# 4) Opcional: veja logs
docker compose logs -f app traefik

Consejos de producción y mantenimiento de certificados

En producción, mantenga la step-ca accesible solo en red privada, haga backups de las claves y planifique rotaciones. Ajuste lifetimes cortos y renovaciones frecuentes para reducir riesgos.

Para aplicar certificados renovados en Traefik, programe un reload o reinicio controlado. Nginx ya recarga automáticamente en el hook de renovación. Pruebe las renovaciones fuera de las ventanas críticas. Para prácticas de gestión de logs, vea Retención y compresión de logs Docker con journald + logrotate.

  • Aislar la CA (red privada, restricciones de firewall).
  • SAN coherentes con DNS/Service Discovery.
  • Reloj sincronizado (NTP) para evitar invalidez por tiempo.
  • Automatizar reload de Traefik en ventana segura.
  • Monitorear expiración de certificados (alertas).

Qué errores de mTLS suelen aparecer y cómo resolverlos

Errores comunes incluyen SAN incorrecto, fingerprint erróneo en el bootstrap, cadena de certificados incompleta y problemas de DNS. Use los logs y pruebas directas para aislar la causa.

Si el handshake falla, verifique también que Traefik esté usando el serversTransport correcto y que el SNI (serverName) coincida con el SAN del certificado del backend.

  • SAN/SNI no coinciden: ajuste serverName y reemita el certificado del backend.
  • Fingerprint incorrecto: rehaga el bootstrap con el fingerprint correcto.
  • Cadena incompleta: asegure el uso de la raíz correcta en rootCAs/ssl_client_certificate.
  • DNS/nombre de servicio erróneo: confirme app.internal en la red Docker.
  • Tiempo (NTP) desincronizado: sincronice relojes para evitar problemas NotBefore/NotAfter.

Dónde profundizar: alternativas y variaciones

Si necesita comparar motores de emisión de certificados o ver una variante con Nginx en el borde, consulte los materiales relacionados. Para estrategias avanzadas de enrutamiento en Traefik, vea canary y reglas de peso.

La gestión de logs y la retención en VPS pequeños también es útil para la operación diaria.

Preguntas frecuentes

¿Necesito exponer la puerta de la CA (9000) a internet?

No. Mantenga la step-ca accesible solo en la red interna de Docker o por VPN. Exponer la CA aumenta la superficie de ataque. Los servicios emiten y renuevan certificados vía DNS interno (step-ca) sin acceso externo.

¿Cómo presenta Traefik el certificado de cliente al backend?

Traefik usa un serversTransport con certificados de cliente (certFile/keyFile) y la raíz de confianza (rootCAs). Se conecta por HTTPS al backend, presenta el certificado y valida el certificado del servidor antes de reenviar el tráfico.

¿Puedo usar varios servicios de backend con mTLS?

Sí. Emita un certificado de servidor para cada servicio (p. ej., api.internal, auth.internal) y cree serversTransports específicos cuando necesite SNI distinto. Puede reutilizar el mismo certificado cliente de Traefik o emitir clientes distintos por servicio.

¿Cómo renovar certificados sin downtime?

El step CLI renueva en background y puede ejecutar un reload suave del servicio (Nginx). Para Traefik, programe un reload/restart controlado del contenedor cuando los archivos de certificado se actualicen; mantenga instancias redundantes cuando sea posible.

¿Se pueden usar dominios reales (externos) con esta CA?

Sí, siempre que el DNS apunte a los servicios correctos y gestione la confianza de la CA en los clientes. Normalmente se usa la CA interna para tráfico interno (Traefik→servicios) y otra solución para TLS público, manteniendo los ámbitos separados.

¿Cómo diagnosticar fallos de handshake TLS entre contenedores?

Revise los logs de Traefik y del backend, confirme SAN/SNI y la confianza de la CA. Pruebe conexiones desde un contenedor temporal y valide las fechas de validez. Errores comunes: SAN incorrecto y fingerprint erróneo en el bootstrap de los clientes.

¿Quieres llevar mTLS a tu entorno Docker con seguridad?

Si este paso a paso te ayudó, ya tienes la base para ejecutar mTLS entre Traefik y servicios con certificados automáticos. ¿Necesitas adaptar esto a múltiples clusters, rotación o observabilidad? Hablemos.

Hablar sobre mTLS en Docker

Ver también