đ Traefik 2, generate a wildcard SSL certificate with Let's Encrypt
How to generate a wildcard SSL certificate with Traefik ? This post is about it, with Gandi and Let's Encrypt to do so.
This post will explain how to generate a wildcard SSL certificate with Let's Encrypt. Have a certificate one-by-one per service is a good practice, you can also exploit the wildcard
functionality of Traefik to generate a wildcard SSL certificate for all your services.
Important update of this post by ldez on 15/07/2020. Again, a big THANK YOU !
Version | Date | Comments |
---|---|---|
1 | 07/2020 | Initial creation |
1.1 | 05/2020 | Reformating post |
1.2 | 07/2022 | Update informations |
2.0 | 08/2023 | Update versions, adding blocs "docker labels" and "without docker label", delete old informations |
Goal : Configure Traefik to gain a wildcard certificate from Let's Encrypt.
Environment : Debian 12
, Docker 24.x
, docker compose (plugin) 2.20.x
, Traefik 2.10
.
Execution context :
jho@vmi866042:/opt/docker/dc$ tree
.
âââ conf
â  âââ acme.json
â  âââ traefik.yml
â  âââ traefikdynamic
â  â  âââ general.yml
â  â  âââ routersservices.yml
âââ docker-compose.yml
âââ logs
âââ traefikAccess.log
âââ traefik.log
- path where are every folder and files :
/opt/docker/dc
- path of the principal configuration file for Traefik :
/opt/docker/dc/conf/traefik.yml
- folder where are every dynamic configuration files :
/opt/docker/dc/conf/traefikdynamic
- path of the file which is used to store SSL certificates for let's encrypt (or other provider) :
/opt/docker/dc/conf/acme.json
- folder to store logs :
/opt/docker/dc/logs/
This post involves a modification to your DNS zone. Be sure to have necessary access and rights to your DNS service or server, to add DNS records and generate API keys. Here, I will use "Gandi" with the "LiveDNS" function. The procedure is similar to other DNS services and providers, but will not be seen in this article.
DNS preparation
Many verification methods are available to generate a wildcard certificate : TLS, HTTP or DNS. Some APIs are provided by DNS providers, which are used by Traefik with its own tool lego
.
You have to create "A" records of your services. Next, generate an API key from your DNS provider. If you are using Gandi like me, login to "account.gandi.net", go to the tab "Security" and click on "Production API key". Keep this key, if you lose it you will have to generate a new one.
Docker and Traefik configuration
Here is the docker-compose.yml
base file for this post :
---
services:
traefik:
image: traefik:saintmarcelin
container_name: traefik
restart: unless-stopped
ports:
- target : 80
published : 80
protocol: tcp
mode : host
### BEGIN dashboad
- target : 8080
published : 8080
protocol: tcp
mode : host
### END dashboard
- target : 443
published : 443
protocol: tcp
mode : host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./conf/traefikdynamic:/dynamic
- ./conf/traefik.yml:/etc/traefik/traefik.yml:ro
- ./conf/acme.json:/etc/traefik/acme.json
- ./logs/traefik.log:/etc/traefik/applog.log
environment:
TZ: Europe/Paris
GANDIV5_API_KEY: apikey
portainer:
container_name: portainer
image: portainer/portainer-ce:alpine
restart: unless-stopped
depends_on:
- traefik
command: -H unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- dataportainer:/data
labels:
traefik.enable: true
traefik.http.routers.portainer.entrypoints: websecure
traefik.http.routers.portainer.rule: Host(`portainer.domain.local`)
traefik.http.routers.portainer.middlewares: security@file
traefik.http.routers.portainer.tls: true
volumes:
dataportainer:
File conf/traefik.yml
. Remember, I'm using Gandi as DNS provider. Don't forget to modify to your needs and your provider:
---
global:
sendAnonymousUsage: false
checkNewVersion: false
api:
dashboard: true
log:
filePath: "/etc/traefik/applog.log"
format: json
level: "ERROR"
providers:
docker:
endpoint: unix:///var/run/docker.sock
exposedByDefault: false
watch: true
swarmMode: false
file:
directory: "/dynamic"
watch: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
# caServer: https://acme-staging-v02.api.letsencrypt.org/directory
caServer: https://acme-v02.api.letsencrypt.org/directory
storage: acme.json
keyType: EC256
dnsChallenge:
provider: gandiv5
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
File conf/traefikdynamic/dynamic.yml
:
---
tls:
options:
default:
minVersion: VersionTLS12
sniStrict: true
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
curvePreferences:
- CurveP521
- CurveP384
alpnProtocols:
- h2
- http/1.1
mintls13:
minVersion: VersionTLS13
http:
middlewares:
security:
headers:
accessControlAllowMethods:
- GET
- OPTIONS
- PUT
accessControlMaxAge: 100
addVaryHeader: true
browserXssFilter: true
contentTypeNosniff: true
customFrameOptionsValue: SAMEORIGIN
customResponseHeaders:
Access-Control-Allow-Origin: "*"
Sec-Fetch-Site: cross-site
X-Forwarded-Proto: https
forceSTSHeader: true
frameDeny: true
hostsProxyHeaders:
- "X-Forwarded-Host"
referrerPolicy: "strict-origin-when-cross-origin"
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
Be aware of the traefik.yml
⣠file : there are two lines with caServer
, pointing to Let's Encrypt. During your build, to avoid a ban from Let's Encrypt (rate limiter on demands per week), use the line "caServer ... acme-staging". When you have finished your build, comment the line "caServer ... acme-staging" and uncomment "caServer ... acme-v02". Don't forget to restart Traefik to take into account the changes and regenerate the production certificate.
Use on the method above: docker labels or dynamic configuration.
Usage of docker labels
Every service in your docker-compose.yml needs these labels:
labels:
traefik.enable: true
traefik.http.routers.SERVICENAME.entrypoints: websecure
traefik.http.routers.SERVICENAME.rule: Host(`SERVICENAME.domain.local`)
traefik.http.routers.SERVICENAME.middlewares: security@file
traefik.http.routers.SERVICENAME.tls: true
traefik.http.routers.SERVICENAME.tls.certresolver: "letsencrypt"
If we take our traefik and portainer services (for example), here's the docker-compose.yml :
---
services:
traefik:
image: traefik:saintmarcelin
container_name: traefik
restart: unless-stopped
ports:
- target : 80
published : 80
protocol: tcp
mode : host
### BEGIN dashboad
- target : 8080
published : 8080
protocol: tcp
mode : host
### END dashboard
- target : 443
published : 443
protocol: tcp
mode : host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./conf/traefikdynamic:/dynamic
- ./conf/traefik.yml:/etc/traefik/traefik.yml:ro
- ./conf/acme.json:/etc/traefik/acme.json
- ./logs/traefik.log:/etc/traefik/applog.log
environment:
TZ: Europe/Paris
GANDIV5_API_KEY: apikey
labels:
traefik.enable: true
traefik.http.routers.traefik-secure.entrypoints: websecure
traefik.http.routers.traefik-secure.rule: Host(`traefik.domain.local`)
traefik.http.routers.traefik-secure.tls: true
traefik.http.routers.traefik-secure.tls.certresolver: letsencrypt
traefik.http.routers.traefik-secure.tls.domains[0].main: domain.local
traefik.http.routers.traefik-secure.tls.domains[0].sans: *.domain.local
traefik.http.routers.traefik-secure.service: api@internal
portainer:
container_name: portainer
image: portainer/portainer-ce:alpine
restart: unless-stopped
depends_on:
- traefik
command: -H unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- dataportainer:/data
labels:
traefik.enable: true
traefik.http.routers.portainer.entrypoints: websecure
traefik.http.routers.portainer.rule: Host(`portainer.domain.local`)
traefik.http.routers.portainer.middlewares: security@file
traefik.http.routers.portainer.tls: true
volumes:
dataportainer:
Usage of a dynamic configuration file instead of docker labels
If you do not want docker labels, you have to create a file in the dynamic configuration folder of traefik, and type this:
http:
...
services:
sc-SERVICENAME:
loadBalancer:
servers:
- url: "http://SERVICENAME:PORT"
routers:
rt-SERVICENAME:
entryPoints:
- websecure
middlewares:
- security
service: sc-SERVICENAME
rule: Host(`SERVICENAME.domain.local`)
tls:
certResolver: letsencrypt
The docker-compose file is the same as before in this article, and have no labels.
acme.json
 file now has more lines with the wildcard certificate. With this configuration, your services are behind Traefik, can be accessed with SSL (and will use the wildcard SSL certificate).
Sources : Traefik et Let's Encrypt