Setup a Let's Encrypt Certificate With Traefik
In this post, i will explain you how to setup your first Let’s Encrypt certificate with Traefik. You need to know a little about Traefik. You can read my first post about it.
Basic setup
For this test, you need to have a machine with port 80 and 443 reachable from the internet.
For the curious, you can find more informations about Let’s Encrypt here.
Now launch traefik:
docker run --rm --name traefik --network test --publish 80:80 --publish 443:443 --publish 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
traefik:1.6-alpine \
--entryPoints="Name:http Address::80" \
--entryPoints="Name:https Address::443 TLS" \
--api --docker --docker.endpoint="unix:///var/run/docker.sock" --loglevel=debug \
--acme=true --acme.entrypoint=https --acme.httpchallenge --acme.httpchallenge.entrypoint=http \
--acme.domains="xxx.raveland.org" --acme.email="xxx@raveland.org" --acme.storage=/tmp/acme.json
I will now explain the new options used here :
- entryPoints=“Name:https Address::443 TLS” : define a new entrypoint listening on port 443 and with TLS activated
- acme=true : enable Let’s Encrypt
- acme.entrypoint=https : Entrypoint to proxy acme challenge to
- acme.httpchallenge : enable the HTTP-01 challenge for Let’s Encrypt
- acme.httpchallenge.entrypoint=http : the HTTP-01 challenge entrypoint
- acme.domains=“xxx.raveland.org” : the FQDN you want to protect with SSL
- acme.email=“xxx@raveland.org” : email address used for registration
- acme.storage=/tmp/acme.json : file used for certificates storage
WARNING : i will explain why you will need to change acme.storage later.
Wait a few seconds and you should see something like this in the logs:
time="2018-05-23T08:59:29Z" level=debug msg="Building ACME client..."
time="2018-05-23T08:59:29Z" level=debug msg="https://acme-v02.api.letsencrypt.org/directory"
time="2018-05-23T08:59:29Z" level=info msg=Register...
time="2018-05-23T08:59:32Z" level=debug msg="Using HTTP Challenge provider."
time="2018-05-23T08:59:33Z" level=debug msg="Unable to split host and port: address xxx.raveland.org: missing port in address. Fallback to request host."
time="2018-05-23T08:59:33Z" level=debug msg="Looking for an existing ACME challenge for token e-d4CY0MHvtZkZT0VSR7DSEF-Kz2bgrXmBGEneTFjJY0..."
time="2018-05-23T08:59:38Z" level=debug msg="Challenge CleanUp for domain xxx.raveland.org"
time="2018-05-23T09:00:18Z" level=debug msg="Certificates obtained for domains [xxx.raveland.org]"
time="2018-05-23T09:00:18Z" level=debug msg="Configuration received from provider ACME: ....
Now your certifcate is generated and you can access the URL with https.
Tune your traefik setup
The main disadvantage of the first setup is the acme.storage. If the certificate is stored on the container itself, you will have to regenerate it each time the container will restart.
It’s best to store it (them) on a docker volume and to mount this volume on the container.
Here are the small optimizations we can do :
- entryPoints=“Name:http Address::80 Redirect.EntryPoint:https” : It’s not necessary but with this change all the http trafic will be redirected to https
- -v ${PWD}/certs:/etc/traefik-certs : we will mount ${PWD}/certs to the traefik container in ‘/etc/traefik-certs’
- acme.storage=/etc/traefik-certs/acme.json : now we tell traefik to store the certificate in the file ‘/etc/traefik-certs/acme.json’ (on the container). But this directory is a docker volume
Hardening the configuration (by frontend)
By default, we will obtain a grade A with Traefik on SSL Labs. We can obtain a grade A+ by adding more options.
These options are setup by frontend :
docker run --rm --network test --label traefik.backend=nginx1 --label traefik.port=80 \
--label traefik.frontend.rule="Host:xxx.raveland.org" \
--label traefik.frontend.entryPoints=https \
--label traefik.frontend.headers.forceSTSHeader=true \
--label traefik.frontend.headers.STSSeconds=315360000 \
--label traefik.frontend.headers.STSIncludeSubdomains=true \
--label traefik.frontend.headers.STSPreload=true \
nginx:latest
The explanations :
- traefik.frontend.headers.forceSTSHeader=true : adds the STS header to non-SSL requests
- traefik.frontend.headers.STSSeconds : sets the max-age of the STS header
- traefik.frontend.headers.STSIncludeSubdomains=true : adds the IncludeSubdomains section of the STS header.
- traefik.frontend.headers.STSPreload=true : adds the preload flag to the STS header
You can find more informations about HTTP Strict Transport Security here
Enjoy 😏