How to get Let's Encrypt SAN certificate for domain and subdomains in one place?

So I have traefik on traefik.example.com and my-service on example.com in docker-swarm mode and I want to get and define Let's Encrypt certificate for example.com and SAN for *.example.com (tls/http challenge only). Other words any other services on www.example.com, smth.example.com etc. should work by https with this settings. What should I write in config?

Current configuration (doesn't work correct):

# docker-compose.traefik.yaml
version: '3.9'

services:
  traefik:
    image: traefik:v2.10
    
    ports:
      - 80:80
      - 443:443
      
    deploy:
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.labels.certificate-storage == true
          - node.role == manager
      labels:
        - traefik.enable=true

        - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
        - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true

        - traefik.http.routers.traefik-public-http.rule=Host(`traefik.example.com`)
        - traefik.http.routers.traefik-public-http.entrypoints=http
        - traefik.http.routers.traefik-public-http.middlewares=https-redirect

        - traefik.http.routers.traefik-public-https.rule=Host(`traefik.example.com`)
        - traefik.http.routers.traefik-public-https.entrypoints=https
        - traefik.http.routers.traefik-public-https.tls=true
        - traefik.http.routers.traefik-public-https.tls.certresolver=le
        - traefik.http.routers.traefik-public-https.tls.domains[0].main=example.com
        - traefik.http.routers.traefik-public-https.tls.domains[0].sans=*.example.com
        - traefik.http.routers.traefik-public-https.service=api@internal
        
        - traefik.http.services.traefik.loadbalancer.server.port=8080

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-public-certificates:/certificates

    command:
      - --providers.docker
      - --providers.docker.exposedbydefault=false
      - --providers.docker.swarmmode
      
      - --entrypoints.http.address=:80
      - --entrypoints.https.address=:443
      
      - --certificatesresolvers.le.acme.email=test@gmail.com
      - --certificatesresolvers.le.acme.storage=/certificates/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true

    networks:
      - traefik-public

volumes:
  traefik-public-certificates:

networks:
  traefik-public:
    driver: overlay
    attachable: true
# docker-compose.yaml
version: "3.9"

services:
  my-service:
    image: my-service:latest
    
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        
        - traefik.http.services.my-service.loadbalancer.server.port=8080

        - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
        - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true

        - traefik.http.routers.my-service-http.rule=Host(`example.com`)
        - traefik.http.routers.my-service-http.entrypoints=http
        - traefik.http.routers.my-service-http.middlewares=https-redirect
        
        - traefik.http.routers.my-service-https.rule=Host(`example.com`)
        - traefik.http.routers.my-service-https.entrypoints=https
        - traefik.http.routers.my-service-https.tls=true
        - traefik.http.routers.my-service-https.tls.certresolver=le

    networks:
      - traefik-public

networks:
  traefik-public:

How many Docker Swarm managers do you have?

In general Traefik open source and LetsEncrypt only works with a single instance.

One manager. And all services in one node for now

For wildcards you need to use dnsChallenge, define via main/sans, see Traefik LE doc.

For a couple of sub-domains you can just get separate certs, AFAIK current LE limit is 50 different per week.

Yeah, separate certificates is my way for now. Anyway it's strange that Let's Encrypt provides wildcard SAN certificates, Traefik seems to provide them (by tls/http challenge) but I can't configure them

And one more question.

I don't understand why we describe SAN certificates for particular route?

- "traefik.http.routers.traefik-public-https.tls.domains[0].main=example.com"
- "traefik.http.routers.traefik-public-https.tls.domains[0].sans=traefik.example.com, www.example.com"

Looks like it should be in a global config because of we use certificate for domain, not for route (generally).

I also found config for SAN certificates looks like more correct

# traefik.yaml
certificatesResolvers:
  le:
    acme:
      email: $EMAIL
      storage: /acme.json
      httpChallenge:
        entryPoint: http
      tlsChallenge:
        keyType: "EC256"
      domains:
        - main: "example.com"
          sans:
            - "traefik.example.com"
            - "www.example.com"

But why I can't describe this resolver inside command section of docker compose? Why I can set both httpChallenge and tlsChallenge? Is this gonna work? Why I have to set keyType value instead of true if we have default value (according doc)? Why this way of using SAN certificates doesn't work for me? Bad config or this is beta-feature?

You can declare certResolver via command, see doc.

AFAIK you can only use one type of challenge.

But I can't declare main/sans via CLI certResolver

Can I use defaultGeneratedCert settings for my case?

Yes, you can:

--entrypoints..http.tls.domains[n].main:
Default subject name.

--entrypoints..http.tls.domains[n].sans:
Subject alternative names.

command traefik error: failed to decode configuration from flags: field not found, node: tls

How about you search this forum, as this has been discussed and solved here many times before.

Note the [n] is an index, should be:

[0].main=example.com
[0].sans=*.example.com

My experiments show that this is the only way to get a SAN certificate:

#docker-compose.traefik (services.traefik.deploy.labels)
- traefik.http.routers.traefik.tls.domains[0].main=example.com
- traefik.http.routers.traefik.tls.domains[0].sans=sub1.example.com, sub2.example.com, etc.example.com

Asterisk template unfortunatelly doesn't work

- traefik.http.routers.traefik.tls.domains[0].sans=*.example.com

Unable to obtain ACME certificate for domains "example.com,.example.com"" rule="Host(traefik.example.com)" error="unable to generate a certificate for the domains [example.com .example.com]: error: one or more domains had a problem:\n[.example.com] [.example.com] acme: could not determine solvers\n" routerName=traefik@docker providerName=le.acme ACME CA="https://acme-v02.api.letsencrypt.org/directory"

Did you try with quotes?

- "--entrypoints.websecure.http.tls.domains[0].main=${DOMAIN}"
- "--entrypoints.websecure.http.tls.domains[0].sans=*.${DOMAIN}"

Hello!

This is an old topic, but it is very relevant to me right now.

Why can’t I use global settings in traefik.yml?
It simply doesn’t work.
For example:

  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letencrypt
        domains:
          - main: example.com
            sans:
              - "*.example.com"
certificatesResolvers:
  letencrypt:
    acme:
      email: e-mail@gmail.com
      storage: /certs/acme.json
#      caServer: https://acme-v02.api.letsencrypt.org/directory # production (default)
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory # staging
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - 1.1.1.1:53
          - 1.0.0.1:53

If I add labels, for example, to the Traefik service, then everything works perfectly:

- "traefik.http.routers.wildcard_cert.tls.certresolver=letencrypt"
- "traefik.http.routers.wildcard_cert.tls.domains[0].main=example.com"
- "traefik.http.routers.wildcard_cert.tls.domains[0].sans=*.example.com"

Here is the full configuration, but unfortunately, the wildcard certificate is not being issued.(

traefik.yml

global:
  checkNewVersion: false
  sendAnonymousUsage: false

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

  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letencrypt
        domains:
          - main: example.com
            sans:
              - "*.example.com"

log:
  level: DEBUG

api:
  insecure: true
  dashboard: true

providers:
  docker:
    exposedByDefault: false

certificatesResolvers:
  letencrypt:
    acme:
      email: e-mail@gmail.com
      storage: /certs/acme.json
#      caServer: https://acme-v02.api.letsencrypt.org/directory # production (default)
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory # staging
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - 1.1.1.1:53
          - 1.0.0.1:53
        delayBeforeCheck: 20

I’m also interested in the answer to this question:

(Please don’t judge me for any mistakes; English is not my native language, and I’m using a translator.)

Best regards,
Alexander