I am using the following docker compose file to deploy traefik on a swarm cluster.
version: "3.7"
services:
traefik:
image: traefik:v2.1
command:
- "--api.dashboard=true"
- "--accesslog=true"
- "--log.level=INFO"
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
- "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik-public"
- "--providers.file.watch=true"
- "--providers.file.filename=/file_provider.yml"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare"
- "--certificatesresolvers.letsencrypt.acme.dnsChallenge.delayBeforeCheck=15"
- "--certificatesresolvers.letsencrypt.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53"
- "--certificatesresolvers.letsencrypt.acme.email=user@domain.tld"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- 80:80
- 443:443
volumes:
- traefik-certificates:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock
networks:
- traefik-public
environment:
- "CF_API_EMAIL=user@domain.tld"
- "CF_API_KEY=api-key"
deploy:
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.docker.lbswarm=true"
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https@docker"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.routers.api.tls.domains[0].main=*.domain.tld"
- "traefik.http.routers.api.tls.domains[0].sans=domain.tld"
- "traefik.http.routers.api.rule=Host(`management.domain.tld`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
- "traefik.http.routers.api.service=api@internal"
- "traefik.http.services.api.loadbalancer.server.port=8080"
configs:
- file_provider.yml
volumes:
traefik-certificates:
configs:
file_provider.yml:
file: /home/access/docker/traefik-provider.yml
networks:
traefik-public:
external: true
I am trying to switch to wildcard certificates over individual certificates for each subdomain. The wildcard certificate is issued successfully for *.domain.tld
and domain.tld
, but a certificate is also issued for management.domain.tld
from the host rule definition. When I navigate to the site, the TLS certificate issued is for management.domain.tld
, not *.domain.tld
. How do I get Traefik to use the wildcard certificate instead of the one from the host rule?
I am also having this issue. It not only generates a wildcard cert, but also a separate certificate for each service that I have as well.
You have a similar issue?
Maybe share your full Traefik static and dynamic config, and docker-compose.yml
if used.
docker compose file
traefik:
container_name: traefik
image: traefik:latest
restart: always
logging: *loki-logging
ports:
- 80:80
- 443:443
- 5900:5900
- 8081:8080
environment:
- CF_ZONE_API_TOKEN=${TRAEFIK__CF_ZONE_API_TOKEN}
- CF_DNS_API_TOKEN=${TRAEFIK__CF_DNS_API_TOKEN}
security_opt:
- no-new-privileges:true
command:
# - --log.level=DEBUG
- --api.insecure=true # FIXME
- --api.dashboard=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.file.filename=/etc/traefik/config.yml
- --providers.file.watch=true
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.web.http.redirections.entryPoint.permanent=true
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls.certResolver=le
- --entrypoints.websecure.http.middlewares=default-headers@file
- --entrypoints.idrac.address=:5900
- --certificatesresolvers.le.acme.storage=acme.json
- --certificatesresolvers.le.acme.email=${EMAIL}
- --certificatesresolvers.le.acme.dnschallenge=true
- --certificatesresolvers.le.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.le.acme.dnschallenge.resolvers=carl.ns.cloudflare.com:53,lara.ns.cloudflare.com:53,1.1.1.1:53,1.0.0.1:53
- --certificatesresolvers.le.acme.dnschallenge.delaybeforecheck=20
- --certificatesResolvers.le.acme.httpChallenge=true # needed for duckdns domains, wildcard does not work with httpChallenge
- --certificatesResolvers.le.acme.httpChallenge.entryPoint=websecure
- --entrypoints.websecure.http.tls.domains[0].main=${DOMAIN}
- --entrypoints.websecure.http.tls.domains[0].sans=*.${DOMAIN}
- --entrypoints.websecure.http.tls.domains[1].main=${DOMAIN_2}
- --serversTransport.insecureSkipVerify=true
- --experimental.plugins.plugin-log4shell.modulename=github.com/traefik/plugin-log4shell
- --experimental.plugins.plugin-log4shell.version=v0.1.2
labels:
- traefik.enable=true
- traefik.http.routers.traefik.entrypoints=websecure
- traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`, `proxy.${DOMAIN}`)
- traefik.http.routers.traefik.service=traefik
- traefik.http.services.traefik.loadbalancer.server.port=8080
- traefik.http.routers.traefik.tls=true
- traefik.http.routers.traefik.tls.certResolver=le
# FIXME remove?
# - traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/config/traefik/acme.json:/acme.json
- ${PWD}/config/traefik/:/etc/traefik/
docker compose label for another service with traefik enabled
labels:
- traefik.enable=true
- traefik.http.routers.whoogle.entrypoints=websecure
- traefik.http.routers.whoogle.rule=Host(`search.${DOMAIN}`)
- traefik.http.routers.whoogle.tls=true
- traefik.http.routers.whoogle.tls.certResolver=le
I think I have figured it out though it doesn't make a lot of sense as to why it needs to be defined like this- I have added these labels to the traefik container on another machine and it seems to have done the trick, though it still generates some regular certs before creating the wildcard cert. I manually removed the site-specific certs from acme.json
and everything seems to work with the wildcard, though I'm not sure what will happen when it tries to renew in a few months.
- traefik.enable=true
# https
# - traefik.http.routers.websecure.tls=true
# - traefik.http.routers.websecure.entrypoints=https
# - traefik.http.routers.websecure.rule=Host(`traefik.local.mydomain.com`)
# wildcard certs
- traefik.http.routers.websecure.tls.certresolver=le
- traefik.http.routers.websecure.tls.domains[0].main=${ROOT_DOMAIN}
- traefik.http.routers.websecure.tls.domains[0].sans=*.${ROOT_DOMAIN}
I only use config.yml to define additional services that cannot be created directly inside the docker compose file.
This can not work, only one challenge, remove both http lines:
weskezm99:
- --certificatesresolvers.le.acme.dnschallenge=true
- --certificatesresolvers.le.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.le.acme.dnschallenge.resolvers=carl.ns.cloudflare.com:53,lara.ns.cloudflare.com:53,1.1.1.1:53,1.0.0.1:53
- --certificatesresolvers.le.acme.dnschallenge.delaybeforecheck=20
- --certificatesResolvers.le.acme.httpChallenge=true # needed for duckdns domains, wildcard does not work with httpChallenge
- --certificatesResolvers.le.acme.httpChallenge.entryPoint=websecure
As far as I can tell, it does- I added that back since getting the wildcard going:
opened 08:07AM - 14 May 19 UTC
closed 09:52AM - 19 Jul 19 UTC
kind/proposal
area/acme
area/tls
status/5-frozen-due-to-age
<!--
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
The issue tracker is … for reporting bugs and feature requests only.
For end-user related support questions, please refer to one of the following:
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
- the Traefik community Slack channel: https://slack.traefik.io
-->
### Do you want to request a *feature* or report a *bug*?
Feature
### What did you expect to see?
- Dynamic wildcard
- Challenge by domain(s) / multiple challenges
- DNS provider by domain(s) / multiple DNS providers
- Multiple certificates providers (acme, vault, ...)
---
## Case: Host
### Static
```toml
[certificatesResolvers.default.acme]
tlsStore = "test"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = "test@test.com"
keyType = "RSA4096"
acmeLogging = true
# resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
[certificatesResolvers.default.acme.TLSchallenge]
```
### Dynamic
```toml
[http.routers.router1]
service = "service2"
rule = "Host(`goo.snitest.com`)"
[http.routers.router1.tls]
# store = "test"
certResolver = "default"
```
## Case: wildcard
Note: If a section `domain` is defined => it doesn't use the Host rules.
### Static
```toml
[certificatesResolvers.default.acme]
tlsStore = "test"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = "test@test.com"
keyType = "RSA4096"
acmeLogging = true
[certificatesResolvers.default.acme.DNSchallenge]
```
### Dynamic
```toml
[http.routers.router1]
service = "service2"
rule = "Host(`goo.snitest.com`)"
[http.routers.router1.tls]
# store = "test"
certResolver = "default"
[[http.routers.router1.tls.domain]]
main = "*.snitest.com"
# SANs = ["foo.com", "fii.com", "fuu.com"]
# certResolver = "default"
```
### Related use cases
- Host + wildcard
- HostRegex + wildcard
- no Host + wildcard
- Host + domain
- no Host + domain
- HostRegex + domain
## Case: wildcards
- Hosts + wildcards
### Static
```toml
[certificatesResolvers.default.acme]
tlsStore = "test"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = "test@test.com"
keyType = "RSA4096"
acmeLogging = true
[certificatesResolvers.default.acme.DNSChallenge]
```
### Dynamic
```toml
[http.routers.router1]
service = "service2"
rule = "Host(`goo.snitest.com`) || Host(`goo.foobar.org`)"
[http.routers.router1.tls]
store = "default"
certResolver = "default"
[[http.routers.router1.tls.domain]]
main = "*.snitest.com"
# certResolver = "default"
[[http.routers.router1.tls.domain]]
main = "*.foobar.org"
# certResolver = "default"
```
## Case: Multiple challenges LE
- multiple challenges => type choice made by Let's encrypt (ex: no wildcard => TLS ALPN, wildcard => DNS)
No fallback.
### Static
```toml
[certificatesResolvers.default.acme]
tlsStore = "test"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = "test@test.com"
keyType = "RSA4096"
acmeLogging = true
[certificatesResolvers.default.acme.TLSchallenge]
[certificatesResolvers.default.acme.DNSChallenge]
```
### Dynamic
```toml
[http.routers.router1]
service = "service2"
rule = "Host(`goo.snitest.com`)"
[http.routers.router1.tls]
# store = "test"
certResolver = "default"
```
## Case: Multiple challenges User
- multiple challenges => choice of type made by the user (domain1 => TLS ALPN, domain2 => HTTP)
- multiple challenges => choice of type and DNS made by the user (domain1 => DNS1, domain2 => DNS2)
No multi-account on the same DNS provider.
No fallback.
### Static
```toml
[certificatesResolvers.default.acme]
tlsStore = "test"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = "test@test.com"
keyType = "RSA4096"
acmeLogging = true
[certificatesResolvers.default.acme.TLSchallenge]
[certificatesResolvers.cloudflare.acme]
tlsStore = "test"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = "test@test.com"
keyType = "RSA4096"
acmeLogging = true
[certificatesResolvers.cloudflare.acme.DNSChallenge]
provider = "cloudflare"
```
### Dynamic
```toml
[http.routers.router1]
service = "service2"
rule = "Host(`goo.snitest.com`)"
[http.routers.router1.tls]
# store = "test"
certResolver = "default"
[http.routers.router2]
service = "service2"
rule = "Host(`goo.snitest.org`)"
[http.routers.router2.tls]
# If "store" different from the store configured on the challenge => log.Warn and we use that of the challenge.
# store = "test"
certResolver = "cloudflare"
[http.routers.router3]
service = "service2"
rule = "Host(`goo.snitest.com`) || Host(`goo.foobar.org`) || Host(`goo.foobar.net`)"
[http.routers.router3.tls]
# store = "test"
certResolver = "default"
[[http.routers.router3.tls.domain]]
main = "*.snitest.com"
# certResolver = "default"
[[http.routers.router3.tls.domain]]
main = "*.foobar.org"
certResolver = "cloudflare"
```
## Case: Multiple certificates providers
- Multiple certificates providers (acme, vault, ...)
No fallback.
### Static
```toml
[certificatesResolvers.lego.acme]
tlsStore = "test"
storage = "acme.json"
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = "test@test.com"
keyType = "RSA4096"
acmeLogging = true
[certificatesResolvers.lego.acme.TLSchallenge]
[certificatesResolvers.hashi.vault]
vaultServer = ""
storage = ""
pkiPath = ""
role = ""
```
### Dynamic
```toml
[http.routers.router1]
service = "service2"
rule = "Host(`goo.snitest.com`)"
[http.routers.router1.tls]
# If "store" different from the store configured on the challenge => log.Warn and we use that of the challenge.
# store = "test"
certResolver = "lego"
[http.routers.router2]
service = "service2"
rule = "Host(`goo.snitest.org`)"
[http.routers.router2.tls]
# If "store" different from the store configured on the challenge => log.Warn and we use that of the challenge.
# store = "test"
certResolver = "hashi"
```
## Case: Disable Host rule detection
- Host + `onHostRule`: false
- no explicit option `onHostRule`
If no named resolver == Host rule detection disabled
```toml
[http.routers.router1]
service = "service2"
rule = "Host(`goo.snitest.com`) || Host(`goo.snitest.org`)"
[http.routers.router1.tls]
# store = "test"
```
I have not seen the same certresolver le
use multiple challenges. I think one will overwrite the other. Check acme.json
which certs exist.
I can confirm that not only does it renew properly without adding additional single-domain certs, but I was able to add other completely different domains that use the http challenge (along side the dns challenge) and was able to generate valid certs. Hopefully this can help someone