Traefik Proxy 2.x and TLS 101
Originally published: September 2020
Updated: April 2022
There are hundreds of reasons why I love being a developer (besides memories of sleepless nights trying to fix a video game that nobody except myself would ever play).
Being a developer gives you superpowers — you can solve any problem. Yes, especially if they don’t involve real-life, practical situations.
But these superpowers are sometimes hindered by tedious configuration work that expects you to master yet another arcane language assembled with heaps of words you’ve never seen before. Such a barrier can be encountered when dealing with HTTPS and its certificates.
Luckily for us — and for you, of course — Traefik Proxy lowers this kind of hurdle and makes sure that there are easy ways to connect your projects to the outside world securely.
The goal for today
The challenge that I’ll explore today is that you have an HTTP service exposed through Traefik Proxy and you want Traefik Proxy to deal with the HTTPS burden (TLS termination), leaving your pristine service unspoiled by mundane technical details.
I’m assuming you have a basic understanding of Traefik Proxy on Docker and that you’re familiar with its configuration. If not, it’s time to read Traefik 2 & Docker 101.
For the purpose of this article, I’ll be using my pet demo docker-compose
file. It enables the Docker provider and launches a my-app
application that allows me to test any request.
version: "3.9"
services:
traefik:
image: traefik:v2.6
command:
- --entrypoints.web.address=:80
- --providers.docker=true
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
my-app:
image: traefik/whoami:v1.7.1
Getting things ready
First things first, let’s make sure my setup can handle HTTPS traffic on the default port (:443
). Let’s also be certain Traefik Proxy listens to this port thanks to an entrypoint I’ll name web-secure
.
version: "3.9"
services:
traefik:
image: traefik:v2.6
command:
- --entrypoints.web.address=:80
- --entrypoints.web-secure.address=:443 # Declares the web-secure entrypoint in Traefik
- --providers.docker=true
ports:
- "80:80"
- "443:443" # Docker sends requests on port 443 to Traefik on port 443
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
my-app:
image: traefik/whoami:v1.7.1
To avoid confusion, let’s state the obvious — I haven’t yet configured anything but enabled requests on 443 to be handled by Traefik Proxy. So, no certificate management yet!
General concepts
In Traefik Proxy, you configure HTTPS at the router level. While defining routes, you decide whether they are HTTP or HTTPS routes (by default, they are HTTP routes).
First, let’s expose the my-app
service on HTTP so that it handles requests on the domain example.com
.
version: "3.9"
services:
# ...
my-app:
image: traefik/whoami:v1.7.1
labels:
- "traefik.http.routers.my-app.rule=Host(`example.com`)"
And now, see what it takes to make this route HTTPS only.
version: "3.9"
services:
# ...
my-app:
image: traefik/whoami:v1.7.1
labels:
- "traefik.http.routers.my-app.rule=Host(`example.com`)"
- "traefik.http.routers.my-app.tls=true"
There you have it! By adding the tls
option to the route, you’ve made the route HTTPS. The TLS configuration could be done at the entrypoint level to make sure all routers tied to this entrypoint are using HTTPS by default. See the Traefik Proxy documentation to learn more.
The only unanswered question left is, where does Traefik Proxy get its certificates from? And the answer is, either from a collection of certificates you own and have configured or from a fully automatic mechanism that gets them for you.
Let’s see these solutions in action.
Option 1 — certificates you own
The least magical of the two options involves creating a configuration file.
Say you already own a certificate for a domain — or a collection of certificates for different domains — and that you are then the proud holder of files to claim your ownership of the said domain.
To have Traefik Proxy make a claim on your behalf, you’ll have to give it access to the certificate files. Let’s do this.
Add a configuration file for certificates
version: "3.9"
services:
traefik:
image: traefik:v2.6
command:
- --entrypoints.web.address=:80
- --entrypoints.web-secure.address=:443
- --providers.docker=true
- --providers.file.directory=/configuration/
- --providers.file.watch=true
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "/home/username/traefik/configuration/:/configuration/"
Traefik Proxy runs with many providers beyond Docker (i.e., Kubernetes, Rancher, Marathon). Here I chose to add plain old configuration files (--providers.file
) to the configuration/
directory and I automatically reload changes with --providers.file.watch=true
. I’m using a configuration file to declare our certificates.
Add the certificates to the configuration file
# Dynamic configuration
# in configuration/certificates.yaml
tls:
certificates:
# first certificate
- certFile: /path/to/example-com.cert
keyFile: /path/to/example-com.key
# second certificate
- certFile: /path/to/other.cert
keyFile: /path/to/other.key
# and so on
Now that I have my YAML configuration file available (thanks to the enabled file provider), I can fill in certificates in the tls.certificates
section.
This is all there is to do. When dealing with an HTTPS route, Traefik Proxy goes through your default certificate store to find a matching certificate.
Specifying a default certificate
If no valid certificate is found, Traefik Proxy serves a default auto-signed certificate. But if needed, you can customize the default certificate like so:
# Dynamic configuration
tls:
stores:
default:
defaultCertificate:
certFile: path/to/cert.cert
keyFile: path/to/cert.key
Even though the configuration is straightforward, it is your responsibility, as the administrator, to configure/renew your certificates when they expire. If you don’t like such constraints, keep reading!
Option 2 — dynamic / automatic certificates
Having to manage (buy/install/renew) your certificates is a process you might not enjoy — I know I don’t! If so, you’ll be interested in the automatic certificate generation embedded in Traefik Proxy, thanks to Let’s Encrypt.
Long story short, you can start Traefik Proxy with no other configuration than your Let’s Encrypt account, and Traefik Proxy automatically negotiates (get/renew/configure) certificates for you. No extra step is required.
Certificate resolvers
As I showed earlier, you can configure a router to use TLS with --traefik.http.routers.router-name.tls=true
. As a result, Traefik Proxy goes through your certificate list to find a suitable match for the domain at hand — if not, it uses a default certificate.
For the automatic generation of certificates, you can add a certificate resolver to your TLS options. A certificate resolver is responsible for retrieving certificates.
Here, let’s define a certificate resolver that works with your Let’s Encrypt account.
version: "3.9"
services:
traefik:
image: traefik:v2.6
command:
- --entrypoints.websecure.address=:443
# ...
- [email protected]
- --certificatesresolvers.le.acme.storage=/acme.json
- --certificatesresolvers.le.acme.tlschallenge=true
# ...
As you can see, I defined a certificate resolver named le
of type acme
. Then, I provided an email (your Let’s Encrypt account), the storage file (for certificates it retrieves), and the challenge for certificate negotiation (here tlschallenge
, just because it’s the most concise configuration option for the sake of the example).
From now on, Traefik Proxy is fully equipped to generate certificates for you.
Using the certificate resolver
Earlier, I enabled TLS on my router like so:
version: "3.9"
services:
# ...
my-app:
image: traefik/whoami:v1.7.1
labels:
- "traefik.http.routers.my-app.rule=Host(`example.com`)"
- "traefik.http.routers.my-app.tls=true"
Now, to enable the certificate resolver and have it automatically generate certificates when needed, I add it to the TLS configuration:
version: "3.9"
services:
# ...
my-app:
image: traefik/whoami:v1.7.1
labels:
- "traefik.http.routers.my-app.rule=Host(`example.com`)"
- "traefik.http.routers.my-app.tls.certresolver=le"
Now, if your certificate store doesn’t yet have a valid certificate for example.com
, the le
certificate resolver will transparently negotiate one for you. Yes, it’s that simple!
And as stated above, you can configure this certificate resolver right at the entrypoint level. Doing so applies the configuration to every router attached to the entrypoint (refer to the documentation to learn more).
Multiple certificate resolvers
With certificate resolvers, you can configure different challenges.
Below is an example that shows how to configure two certificate resolvers that leverage Let’s Encrypt, one using the dnsChallenge and the other using the tlsChallenge.
# Static configuration
certificatesResolvers:
resolver-digital-ocean:
acme:
# ...
dnsChallenge:
provider: "digitalocean"
delayBeforeCheck: 0
tls-challenge-resolver:
acme:
# ...
tlsChallenge: {}
Later on, you’ll be able to use one or the other on your routers.
# Dynamic configuration
http:
routers:
https-route:
rule: "Host(`my.domain`)"
tls:
certResolver: "resolver-digital-ocean"
https-route-2:
rule: "Host(`other.domain`)"
tls:
certResolver: "tls-challenge-resolver"
In the above example that uses the file provider, I asked Traefik Proxy to generate certificates for my.domain
using the dnsChallenge
with DigitalOcean and to generate certificates for other.domain
using the tlsChallenge
.
And you’ve guessed it already — Traefik Proxy supports DNS challenges for different DNS providers at the same time!
Wildcard and Let’s Encrypt
Instead of generating a certificate for each subdomain, you can choose to generate wildcard certificates.
# Dynamic configuration
http:
routers:
router-example:
rule: "Host(`something.my.domain`)"
tls:
certResolver: "my-resolver"
domains:
- main: "my.domain"
sans: "*.my.domain"
In the above example, I configured Traefik Proxy to generate a wildcard certificate for *.my.domain
.
If I had omitted the .tls.domains
section, Traefik Proxy would have used the host ( in this example, something.my.domain
) defined in the Host
rule to generate a certificate.
What about TCP and TLS?
If you want to configure TLS with TCP, then the good news is that nothing changes. You configure the same tls
option, but this time on your tcp
router.
version: "3.9"
services:
# ...
my-tcp-app:
image: traefik/whoamitcp:v0.2.1
labels:
- "traefik.tcp.routers.my-tcp-app.rule=HostSNI(`tcp-example.com`)"
- "traefik.tcp.routers.my-tcp-app.tls=true"
What about passthrough?
Sometimes your services handle TLS by themselves. In such cases, Traefik Proxy must not terminate the TLS connection but forward the request as is to these services. To configure this passthrough, you need to configure a TCP router, even if your service handles HTTPS.
version: "3.9"
services:
# ...
my-tcp-app:
image: traefik/whoamitcp:v0.2.1
labels:
- "traefik.tcp.routers.my-tcp-app.rule=HostSNI(`tcp-example.com`)"
- "traefik.tcp.routers.my-tcp-app.tls.passthrough=true"
What about mTLS?
Sometimes, especially when deploying following a Zero Trust security model, you want Traefik Proxy to verify that clients accessing the services are authorized beforehand, instead of having them authorized by default. This is when mutual TLS (mTLS) comes to the rescue.
To enforce mTLS in Traefik Proxy, the first thing you do is declare a TLS Option (in this example, require-mtls
) forcing verification and pointing to the root CA of your choice.
# Dynamic configuration
tls:
options:
require-mtls:
clientAuth:
clientAuthType: RequireAndVerifyClientCert
caFiles:
- /certs/rootCA.crt
Now that this option is available, you can protect your routers with tls.options=require-mtls@file
. Once done, every client trying to connect to your routers will have to present a certificate signed with the root certificate authorities configured in the caFiles list.
version: "3.9"
services:
# ...
my-app:
image: traefik/whoami:v1.7.1
labels:
- "traefik.http.routers.my-app.rule=Host(`example.com`)"
- "traefik.http.routers.my-app.tls.options=require-mtls@file"
TLS from Traefik Proxy to your service
Not only can you configure Traefik Proxy to enforce TLS between the client and itself, but you can configure in many ways how TLS is operated between Traefik Proxy and the proxied services.
Leveraging the serversTransport configuration, you can define the list of trusted certificate authorities, a custom server name, and, if mTLS is required, what certificate it should present to the service.
# Dynamic configuration
http:
serversTransports:
my-transport:
serverName: "traefik.example.com"
certificates:
- certFile: path/to/cert.crt
keyFile: path/to/cert.key
Later on, you can bind that serversTransport to your service:
version: "3.9"
services:
# ...
my-app:
image: traefik/whoami:v1.7.1
labels:
- "traefik.http.routers.my-app.rule=Host(`example.com`)"
- "traefik.http.services.my-app.loadbalancer.serverstransport=my-transport@file"
Mix and match
Traefik Proxy allows for many TLS options you can set on routers, entrypoints, and services (using server transport). Mixing and matching these options fits such a wide range of use cases that I’m sure it can tackle any advanced or straightforward setup you'll need.
- Do you want to serve TLS with a self-signed certificate?
- Are you're looking to get your certificates automatically based on the host matching rule?
- Would you rather terminate TLS on your services?
- Do you require mTLS from the client?
- Do you extend this mTLS requirement to the backend services?
Traefik Proxy covers that and more. And before you ask for different sets of certificates, let's be clear — the definitive answer is, absolutely!
Where to go next?
Hopefully, this article sheds light on how to configure Traefik Proxy 2.x with TLS. If there are missing use cases or still unanswered questions, let me know in the comments or on our community forum!
If you're interested in learning more about using Traefik Proxy as an ingress proxy and load balancer, watch our workshop “Advanced Load Balancing with Traefik Proxy”.
In the meantime — happy networking!