Ir al contenido
  1. Posts/

Traefik en el homelab: HTTPS automático para todos tus servicios sin tocar nginx

Hay una fase en el homelab que todos conocemos: la fase de los puertos. Grafana en el 3000, n8n en el 5678, Gitea en el 3001, Portainer en el 9443, Immich en el 2283… Cada servicio nuevo es otro número que memorizar, otro marcador en el navegador con la IP y el puerto raro.

Durante un tiempo lo aguanté. Total, solo lo uso yo. Pero llegó un día en que intenté enseñarle algo de Phatt a un amigo y tuve que decirle “espera que busco el puerto de Grafana”. En ese momento decidí que tocaba arreglarlo.

La solución obvia para alguien del mundillo web es un reverse proxy. Nginx lo conoce todo el mundo. Caddy está ganando popularidad. Pero para un homelab con Docker, la opción que más sentido tiene es Traefik.

Por qué Traefik y no Nginx
#

Nginx es sólido. Lo uso en producción en otros contextos. Pero tiene un problema para el homelab dockerizado: cada vez que añades un servicio nuevo, tienes que editar su configuración, recargarla, y si algo falla en la sintaxis te carga la configuración de todos tus servicios.

Traefik tiene un enfoque completamente diferente: autodescubrimiento. En vez de tener un fichero central que describe todos tus servicios, Traefik lee las etiquetas de tus contenedores Docker y configura el routing automáticamente.

Añades un contenedor nuevo con dos etiquetas, y Traefik lo detecta solo, le asigna el subdominio, solicita el certificado SSL, y ya está accesible. Sin tocar un fichero de configuración central. Sin recargar nada.

En Phatt tengo ahora mismo 18 servicios detrás de Traefik. Cuando añado el número 19, tardo literalmente 30 segundos en configurar el routing.

Cómo funciona por dentro
#

La arquitectura de Traefik tiene tres conceptos clave que vale la pena entender antes de ponerse a configurarlo:

Entrypoints: los puertos donde Traefik escucha. Normalmente el 80 (HTTP) y el 443 (HTTPS). Nada más entra por aquí.

Routers: las reglas que deciden qué tráfico va a qué servicio. “Si el Host es grafana.midnightlab.build, manda el tráfico al servicio de Grafana.”

Services: los backends reales, es decir, tus contenedores corriendo en sus puertos internos.

El truco está en que con Docker, los Routers y Services se configuran como etiquetas en el propio contenedor. Traefik lee esas etiquetas via la API de Docker y construye su configuración en tiempo real.

1
2
3
Internet → Traefik (443) → Router: Host(`grafana.tu-dominio.com`) → Contenedor Grafana:3000
                        → Router: Host(`n8n.tu-dominio.com`) → Contenedor n8n:5678
                        → Router: Host(`gitea.tu-dominio.com`) → Contenedor Gitea:3001

Una sola IP pública, un solo puerto 443, múltiples subdominios, cada uno apuntando a su contenedor.

Instalación: el docker-compose de Traefik
#

Lo primero es preparar la red Docker que compartirán Traefik y todos tus servicios:

1
docker network create traefik-net

Luego el directorio y el fichero de certificados:

1
2
3
mkdir -p /mnt/user/appdata/traefik
touch /mnt/user/appdata/traefik/acme.json
chmod 600 /mnt/user/appdata/traefik/acme.json

El acme.json es donde Traefik guarda los certificados Let’s Encrypt. El chmod 600 es obligatorio, Traefik se niega a arrancar si los permisos son más permisivos.

Ahora el docker-compose.yml de Traefik:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
version: "3.8"

services:
  traefik:
    image: traefik:v3.2
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - traefik-net
    ports:
      - "80:80"
      - "443:443"
    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /mnt/user/appdata/traefik/traefik.yml:/traefik.yml:ro
      - /mnt/user/appdata/traefik/acme.json:/acme.json
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.entrypoints=https"
      - "traefik.http.routers.traefik.rule=Host(`traefik.midnightlab.build`)"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.tls.certresolver=cloudflare"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.middlewares=traefik-auth"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH}"

networks:
  traefik-net:
    external: true

Y el fichero de configuración estática traefik.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
api:
  dashboard: true

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

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik-net

certificatesResolvers:
  cloudflare:
    acme:
      email: [email protected]
      storage: /acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

Un par de decisiones importantes en esta configuración:

exposedByDefault: false es fundamental. Sin esto, Traefik intentaría crear rutas para todos tus contenedores, incluyendo bases de datos y servicios internos que no deben estar expuestos. Con esta opción, solo los contenedores que explícitamente digan traefik.enable=true serán visibles.

El dnsChallenge con Cloudflare es la forma de obtener certificados wildcard (*.tu-dominio.com) sin necesitar un servidor accesible desde Internet. Traefik crea un registro DNS temporal en tu cuenta de Cloudflare para validar el dominio.

Certificados wildcard: la configuración que te ahorra trabajo
#

Hay dos formas de gestionar los certificados con Traefik:

Certificado individual por subdominio: Traefik solicita un certificado diferente para grafana.tu-dominio.com, otro para n8n.tu-dominio.com, etc. Funciona, pero si tienes 18 servicios son 18 certificados que gestionar, y Let’s Encrypt tiene rate limits.

Certificado wildcard: Un único certificado *.tu-dominio.com que sirve para todos los subdominios. Un certificado, cero problemas. Es la opción que uso en Phatt.

Para el wildcard necesitas el DNS Challenge (lo que ya está configurado en el traefik.yml de arriba). Let’s Encrypt no puede validar un wildcard por HTTP, necesita acceso a tu DNS.

La variable CF_DNS_API_TOKEN es un token de API de Cloudflare con permisos para editar registros DNS en tu zona. Se crea desde el panel de Cloudflare en “My Profile > API Tokens > Create Token”, usando la plantilla “Edit zone DNS”.

Añadir servicios: las etiquetas que necesitas
#

Una vez Traefik está corriendo, añadir un servicio es cuestión de poner las etiquetas correctas en su docker-compose.yml. Por ejemplo, Grafana:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    networks:
      - traefik-net
    volumes:
      - grafana-data:/var/lib/grafana
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.entrypoints=https"
      - "traefik.http.routers.grafana.rule=Host(`grafana.midnightlab.build`)"
      - "traefik.http.routers.grafana.tls=true"
      - "traefik.http.routers.grafana.tls.certresolver=cloudflare"
      - "traefik.http.services.grafana.loadbalancer.server.port=3000"

networks:
  traefik-net:
    external: true

Eso es todo. Cuando haces docker compose up -d, Traefik detecta el nuevo contenedor, aplica el routing, y si es la primera vez que ve ese subdominio solicita el certificado. En 30 segundos tienes https://grafana.tu-dominio.com funcionando.

El proceso es idéntico para n8n, Gitea, Immich, o cualquier otro servicio. Cambia el nombre del router, el dominio en el rule, y el puerto en loadbalancer.server.port. Todo lo demás igual.

Middlewares: la parte poderosa
#

Los middlewares son capas de lógica que Traefik aplica al tráfico antes de enviarlo al servicio. Los que más uso en el homelab:

BasicAuth: para servicios que no tienen autenticación propia, puedo ponerle usuario y contraseña a nivel de Traefik. Útil para dashboards internos.

IPWhitelist: para servicios que solo deben ser accesibles desde mi red Tailscale. Bloquea cualquier IP que no esté en el rango permitido.

RateLimit: para servicios públicos, limitar el número de peticiones por IP y evitar abuso.

Headers de seguridad: HSTS, X-Frame-Options, X-Content-Type-Options. Traefik puede añadir estas cabeceras automáticamente sin tocar la configuración del servicio.

Un middleware se define en las etiquetas del contenedor de Traefik (o en ficheros de configuración dinámica) y luego se referencia desde cualquier router:

1
2
3
4
5
# Definir el middleware
- "traefik.http.middlewares.local-only.ipwhitelist.sourcerange=100.64.0.0/10,192.168.1.0/24"

# Usar el middleware en un router
- "traefik.http.routers.grafana.middlewares=local-only"

El rango 100.64.0.0/10 es el espacio de direcciones de Tailscale. Con esto, Grafana solo es accesible desde dispositivos en mi red Tailscale o desde la red local. Desde Internet, Traefik devuelve 403.

Dashboard de Traefik: ver qué está pasando
#

Traefik incluye un dashboard web donde puedes ver todos los routers, servicios y middlewares activos. En la configuración de arriba, está accesible en https://traefik.tu-dominio.com protegido con BasicAuth.

El dashboard muestra en tiempo real:

  • Qué routers están activos y con qué reglas
  • Qué servicios están healthy (si Traefik puede llegar al contenedor)
  • Qué certificados tiene y cuándo expiran
  • El estado de cada entrypoint

Es útil para depurar cuando un servicio no responde como esperas. Normalmente el problema es o una etiqueta mal escrita, o que el contenedor no está en la red traefik-net.

Lo que aprendí con 18 servicios en producción
#

Todos los contenedores necesitan estar en la misma red. El error más común al empezar es olvidar añadir traefik-net a la sección networks del servicio nuevo. Traefik puede ver las etiquetas del contenedor pero no puede llegar a él si no comparten red.

El socket de Docker es un privilegio. Traefik necesita acceso al socket de Docker para el autodescubrimiento, y eso es un vector de ataque si alguien comprometiera Traefik. Para mitigarlo, uso no-new-privileges:true y considero usar un proxy de socket como Tecnativa Docker Socket Proxy en setups más críticos.

Los logs son tus amigos. Si algo no funciona, docker logs traefik suele tener la respuesta. Traefik es bastante verboso con los errores de configuración.

Combine con Cloudflare Tunnels cuando necesite exposición pública. En mi setup, Traefik gestiona el routing interno y la terminación SSL. Para los servicios que necesitan ser accesibles desde Internet, Cloudflare Tunnels actúa como capa anterior, enviando el tráfico a Traefik que luego lo distribuye. Para acceso privado desde fuera de casa, Tailscale va directo a los servicios sin pasar por Traefik.

Los wildcards son más cómodos pero tardan más en emitirse. La primera vez que Let’s Encrypt emite un certificado wildcard tarda entre 30 segundos y 2 minutos. Si ves que el certificado no llega, revisa los logs: casi siempre es un problema con el token de Cloudflare o un typo en el nombre del proveedor DNS.

Traefik v3 vs v2: lo que cambió
#

En 2024 salió Traefik v3 con cambios importantes. Si encuentras tutoriales de v2 por ahí (muchos), hay algunas diferencias clave:

  • El formato de configuración cambió ligeramente, especialmente en TLS
  • Algunas opciones obsoletas fueron eliminadas
  • El soporte para HTTP/3 mejoró significativamente
  • La gestión de middlewares tiene pequeñas diferencias de sintaxis

Los ejemplos de este post son todos v3. Si estás empezando, ve directamente a v3. Si tienes una instalación v2 funcionando y no tienes problemas, no hay prisa en migrar, pero vale la pena hacerlo cuando tengas un momento tranquilo.

Alternativas que no uso
#

Caddy es la alternativa más mencionada. También tiene HTTPS automático y es más sencillo de configurar que Traefik, pero el autodescubrimiento Docker no es tan nativo. Para un homelab con pocos servicios puede ser mejor opción.

Nginx Proxy Manager es Nginx con una interfaz web bonita. Buena opción para quien prefiere configurar con clicks en vez de etiquetas. Menos potente para automatización pero más accesible si vienes de un background no técnico.

HAProxy lo descarto directamente para homelab. Potentísimo, pero la complejidad de configuración no justifica la potencia para este caso de uso.

Me quedo con Traefik porque el workflow de “añado etiquetas, el servicio aparece” encaja perfectamente con cómo trabajo. Cuando despliego algo con Dokploy, o cuando levanto un servicio de n8n para una automatización nueva, Traefik simplemente lo recoge y lo publica.

El resultado real
#

Antes de Traefik tenía un fichero en Notion con todos los puertos de mis servicios. Ahora tengo subdominios que recuerdo de memoria. grafana.midnightlab.build, n8n.midnightlab.build, gitea.midnightlab.build. Intuitivos, con HTTPS, sin puertos raros.

El tiempo de configuración inicial fue unas dos horas, contando los errores que cometí con los permisos del acme.json y con la red Docker. Desde entonces, cada nuevo servicio me cuesta 2 minutos de configuración.

Si tienes más de tres servicios corriendo en Docker, Traefik es la mejora de calidad de vida más directa que puedes hacer en tu homelab.