Struggling with Traefik, Wildcard LE certs and Portainer

Hi,

I'm using Traefik 3.3 on Docker Swarm (currently just one manager node). I am trying to set up Let's encrypt wildcard cert for a domain to use in my homelab. I'm also redirecting all HTTP requests to HTTPS. This works for Traefik and the Traefik dashboard. But I can't get Portainer to use the wildcard cert. Traefik uses its default cert for Portainer. I've been fiddling around with traefik.yaml and the docker-compose files, googling and reading other posts here, but I am stuck. I'd be glad for any help.

This is my traefik.yml file:

global:
  checkNewVersion: true
  sendAnonymousUsage: false
log:
  level: DEBUG
api:
  dashboard: true
providers:
  swarm:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
entrypoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
        certResolver: myresolver
        domains:
          - main: home.example.com
            sans:
              - "*.home.example.com"
certificatesResolvers:
  myresolver:
    acme:
      email: myname@example.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: loopia
        delayBeforeCheck: 0
        resolvers: 1.1.1.1:53,8.8.8.8:53

And this is the docker-compose.yml file for Traefik:

services:
  traefik:
    image: traefik:v3.3
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./usersfile:/etc/traefik/usersfile:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik-public
    environment:
      - LOOPIA_API_USER=myuser@loopiaapi #/run/secrets/loopia_api_user
      - LOOPIA_API_PASSWORD=mysecret #/run/secrets/loopia_api_password
    secrets:
      - loopia_api_user
      - loopia_api_password
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        - traefik.http.routers.traefik-dashboard.service=api@internal
        - traefik.http.routers.traefik-dashboard.rule=Host(`traefik.home.example.com`)
        - traefik.http.routers.traefik-dashboard.entrypoints=websecure
        - traefik.http.routers.traefik-dashboard.tls=true
        - traefik.http.routers.traefik-dashboard.tls.certresolver=myresolver
          #        - traefik.http.routers.traefik-dashboard.tls.domains[0].main=home.example.com
          #        - traefik.http.routers.traefik-dashboard.tls.domains[0].sans=*.home.example.com
        - traefik.http.routers.traefik-dashboard.middlewares=auth
        - traefik.http.middlewares.auth.basicauth.usersfile=/etc/traefik/usersfile
        - traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080
secrets:
  loopia_api_user:
    file: loopia_api_user.txt
  loopia_api_password:
    file: loopia_api_password.txt
networks:
  traefik-public:
    external: true

The above works for Traefik. It gets a wildcard cert for *.home.example.com, and the Traefik dashboard is served at https://traefik.home.example.com with that wildcard cert.

Here is the docker-compose.yml file for Portainer, which results in Portainer being served at https://portainer.home.example.com but not with the wildcard cert. Instead it is served with Traefik's default cert:

services:
  portainer:
    image: portainer/portainer-ce:2.27.0
    command: -H tcp://tasks.portainer_agent:9001 --tlsskipverify
    volumes:
      - portainer_data:/data
    networks:
      - traefik-public
      - portainer-agent-network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        - traefik.http.routers.portainer.rule=Host(`portainer.home.example.com`)
        - traefik.http.routers.portainer.entrypoints=websecure
        - traefik.http.routers.portainer.tls=true
        - traefik.http.routers.portainer.tls.certresolver=myresolver
          #- traefik.http.routers.portainer.tls.domains[0].main=home.example.com
          #- traefik.http.routers.portainer.tls.domains[0].sans=*.home.example.com
        - traefik.http.services.portainer.loadbalancer.server.port=9000

  portainer_agent:
    image: portainer/agent:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes
    networks:
      - portainer-agent-network
    deploy:
      mode: global
      restart_policy:
        condition: on-failure

networks:
  traefik-public:
    external: true
  portainer-agent-network:
    driver: overlay

volumes:
  portainer_data:

Running docker service logs traefik_traefik shows:

traefik_traefik.1.h13n6yr2p5u2@nuc    | 2025-02-15T10:11:53Z DBG github.com/traefik/traefik/v3/pkg/tls/tlsmanager.go:228 > Serving default certificate for request: "portainer.home.example.com"
traefik_traefik.1.h13n6yr2p5u2@nuc    | 2025-02-15T10:11:53Z DBG log/log.go:245 > http: TLS handshake error from 10.0.0.2:51198: remote error: tls: bad certificate

Those are not necessary, as already set on entrypoint

You can also remove the entrypoints assignment on router, set a default on entrypoint, check simple Traefik example.

When using multiple Docker networks on the target service, which are not all shared with Traefik, make sure to set docker.network globally on providers.swarm or individually on router.

Note there is no :ro for sockets. To improve security here, you would need a Docker socket proxy.

Not sure about your issue, Portainer should be like any other target service, check Traefik debug log for err and acme.

1 Like

Thanks @bluepuma77 for the clarification. I cleaned up my config files.

I feel like a moron. The version tag 2.27.0 for Portainer doesn't exist. Yet. Only a release candidate 2.27.0-rc1. So instead I tagged the latest stable release 2.26.1 in the docker-compose file for Portainer. Now I'm up and running.

Well, well. We learn by doing ...