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 post |
1.1 | 05/2022 | Reformating post |
1.2 | 07/2022 | Informations update |
Goal : Configure Traefik to gain a wildcard certificate from Let's Encrypt.
Environment : Debian 11.2
, Docker 20.10.x
, docker-compose 2.4.x
, Traefik 2.8
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
I am using the path /opt/docker/dc
to store YAML files (docker-compose and Traefik configuration files) :
mkdir -p /opt/docker/dc/{conf,logs}
mkdir -p /opt/docker/dc/conf/traefikdynamic
touch /opt/docker/dc/conf/acme.json
chmod 0600 /opt/docker/dc/conf/acme.json
touch /opt/docker/dc/logs/traefik.log /opt/docker/dc/conf/traefik.yml /opt/docker/dc/conf/traefikdynamic/dynamic.yml
This example is a Traefik "static configuration" ; it will use docker labels in the docker-compose file. Here is the docker-compose.yml
file :
---
services:
traefik:
image: traefik:2.8
restart: unless-stopped
environment:
- "GANDIV5_API_KEY=<INSERT-WITHOUT-HOOK>"
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./conf/traefik.yml:/etc/traefik/traefik.yml:ro
- ./conf/traefikdynamic:/etc/traefik/dynamic:ro
- ./conf/acme.json:/acme.json
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:2.14.0
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
:
---
global:
sendAnonymousUsage: false
checkNewVersion: false
api:
dashboard: true
pilot:
dashboard: false
log:
level: ERROR
providers:
docker:
endpoint: unix:///var/run/docker.sock
exposedByDefault: false
watch: true
swarmMode: false
file:
directory: /etc/traefik/dynamic
watch: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: contact@domain.local
# 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/traefik_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:
compression:
compress:
excludedContentTypes:
- text/event-stream
security:
headers:
accessControlAllowMethods:
- GET
- OPTIONS
- PUT
accessControlMaxAge: 100
addVaryHeader: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
frameDeny: true
stsPreload: true
customFrameOptionsValue: SAMEORIGIN
referrerPolicy: "origin-when-cross-origin"
permissionsPolicy: "camera 'none'; microphone 'none'; geolocation 'none'; payment 'none';"
stsSeconds: 315360000
hostsProxyHeaders:
- "X-Forwarded-Host"
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.
In short :
- docker-compose file has the API key, in an environment variable for Traefik ;
acme.json
file is ready with needed rights ;- TLS labels for services are shorter and simpler! Everything needed are in Traefik configuration.
For every service in the docker-compose.yml file, you have to add 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
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