Blog

Traefik 2 & TLS 101

HTTPS (& TCP over TLS) for everyone!

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 kind of problems. 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, Traefik tends to lower this kind of hurdle and makes sure that there are easy ways of securely connecting your developments to the outside world.

The Goal for Today

The challenge we’ll accept is the following — You have an HTTP service exposed through Traefik, and you want Traefik to deal with the HTTPS burden (TLS termination), leaving your pristine service unspoiled by mundane technical details.

We’ll assume you have a basic understanding of Traefik on Docker and that you’re familiar with its configuration (if not, it’s time to read Traefik 2 & Docker 101).

During this article, we’ll use my pet demo docker-compose file: it enables the docker provider and launches a my-app application that allows us to test any request.

version: "3"
services:
  traefik:
    image: "traefik:v2.0"
    command:
      - --entrypoints.web.address=:80
      - --providers.docker=true
    ports:
      - "80:80"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
  
  my-app:
    image: containous/whoami:v1.3.0

Getting Things Ready

First things first, let’s make sure our setup can handle HTTPS traffic on the default port (:443), and that Traefik listens to this port thanks to an entrypoint we’ll name web-secure.

version: "3"
services:
  traefik:
    image: "traefik:v2.0"
    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: containous/whoami:v1.3.0

To avoid confusion, let’s state the obvious — We haven’t yet configured anything but enabled requests on 443 to be handled by Traffic. So, no certificate management yet!

General Concepts

Ultimately, in Traefik, you configure HTTPS on the router level. While defining routes, you decide whether they are HTTP routes or HTTPS routes (by default, they are HTTP routes).

First, let’s expose our my-app service on HTTP so that it handles requests on domain example.com.

version: "3"

services:
  # ...
  my-app:
    image: containous/whoami:v1.3.0
    labels:
      - "traefik.http.routers.my-app.rule=Host(`example.com`)"

And now, see what it takes to make this route HTTPS only!

version: "3"

services:
  # ...
  my-app:
    image: containous/whoami:v1.3.0
    labels:
      - "traefik.http.routers.my-app.rule=Host(`example.com`)"
      - "traefik.http.routers.my-app.tls=true"

There, by adding the tls option to the route, we’ve made it HTTPS.

The only unanswered question left is, “Where does Traefik 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 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"

services:
  traefik:
    image: "traefik:v2.0"
    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 runs with many providers beyond Docker (i.e., Kubernetes, Rancher, Marathon), and here we chose to add plain old configuration files (--providers.file) in the configuration/ directory (and we’ll automatically reload changes with --providers.file.watch=true). We’ll use a configuration file to declare our certificates.

Add the Certificates to the Configuration File

# in files/certificates.toml

[[tls.certificates]] #first certificate
   certFile = “/path/to/example-com.cert” 
   keyFile = “/path/to/example-com.key”

[[tls.certificates]] #second certificate
   certFile = “/path/to/other.cert” 
   keyFile = “/path/to/other.key”
   
# and so on

Now that we have our TOML configuration file available (thanks to the enabled file provider), we can fill in certificates in the [[tls.certificates]]section.

Enjoy!

This is all there is to do. When dealing with an HTTPS route, Traefik goes through your default certificate store to find a matching certificate.

Specifying a Default Certificate?

If no valid certificate is found, Traefik serves a default auto-signed certificate. But if needed, you can customize the default certificate like so:

[tls.stores]
  [tls.stores.default]
   [tls.stores.default.defaultCertificate] 
     certFile = “path/to/cert.crt” 
     keyFile = “path/to/cert.key”

Additional Thoughts

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 don’t). If so, you’ll be interested in the automatic certificate generation embedded in Traefik (thanks to Let’s Encrypt).

Long story short, you can start Traefik with no other configuration than your Let’s Encrypt account, and Traefik automatically negotiates (get/renew/configure) certificates for you — No extra step.

Certificate Resolvers.

We saw that you can configure a router to use TLS
(--traefik.http.routers.router-name.tls=true).
As a consequence, we saw that Traefik would go through your certificate list to find a suitable match for the domain at hand (and if not would use a default certificate).

For automatic certificate generation, 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!

services:
  traefik:
    image: "traefik:v2.0"
    command:
      - --entrypoints.websecure.address=:443
      # ...
      - --certificatesresolvers.le.acme.email=my@email.com
      - --certificatesresolvers.le.acme.storage=/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
      # ...

As you can read, we defined a certificate resolver named le of type acme. Then, we 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 is fully equipped to generate certificates for you!

Using the Certificate Resolver.

If you remember correctly (I’m sure you do!), we enabled TLS on our router like so:

version: "3"

services:
  # ...
  my-app:
    image: containous/whoami:v1.3.0
    labels:
      - "traefik.http.routers.my-app.rule=Host(`example.com`)"
      - "traefik.http.routers.my-app.tls=true"

Now, to enable our certificate resolver and have it automatically generate certificates (when needed), we’ll add it to the TLS configuration, like so:

version: "3"

services:
  # ...
  my-app:
    image: containous/whoami:v1.3.0
    labels:
      - "traefik.http.routers.my-app.rule=Host(`example.com`)"
      - "traefik.http.routers.my-app.tls=true"
      - "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 — it’s that simple.

Multiple Certificate Resolvers?

With certificate resolvers, you can configure different challenges.

Below is an example that shows how to configure two CertResolvers that leverage Let’s Encrypt, one using the dnsChallenge, the other using the tlsChallenge.

[certificatesResolvers.resolver-digital-ocean.acme]
  # ... 
  [certificatesResolvers.resolver-digital-ocean.acme.dnsChallenge]
    provider = "digitalocean"
    delayBeforeCheck = 0

[certificatesResolvers.tls-challenge-resolver.acme]
  # ...
  [certificatesResolvers.tls-challenge-resolver.acme.tlsChallenge]

Later on, you’ll be able to use one or the other on your routers.

# in routers.toml

[http.routers]
  [http.routers.https-route]
    rule = "Host(`my.domain`)"
    [http.routers.https-route.tls]
      certResolver = "resolver-digital-ocean"

[http.routers.https-route-2]
    rule = "Host(`other.domain`)"
    [http.routers.https-route-2.tls]
      certResolver = "tls-challenge-resolver"

In the above example (that uses the file provider), we’ve asked Traefik to generate certificates for my.domain using the dnsChallenge (with digital ocean) and to generate certificates for other.domain using the TLSChallenge.

And you’ve guessed it already — Traefik supports DNS challenge 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!

[http.routers]
  [http.routers.router-example]
    rule = "Host(`something.my.domain`)"
    [http.routers.router-example.tls]
      certResolver = "my-resolver"
      [[http.routers.router-example.tls.domains]]
        main = "my.domain"
        sans = "*.my.domain"

In the above example, we’ve configured Traefik to generate a wildcard certificate for *.my.domain.

If we had omitted the .tls.domains section, Traefik would have used the host (here something.my.domain) defined in the Host rule to generate a certificate.


What About TCP & TLS?

If you want to configure TLS with TCP, then good news: nothing changes, you’ll configure the same tls option, but this time on your tcp router.

version: "3"

services:
  # ...
  my-tcp-app:
    image: containous/whoamitcp:v1.0.0
    labels:
      - "traefik.tcp.routers.my-tcp-app.rule=HostSNI(`tcp-example.com`)"
      - "traefik.tcp.routers.my-tcp-app.tls=true"

What About Pass-Through?

Sometimes your services handle TLS by themselves. In such cases, Traefik mustn’t terminate the TLS connection but forward the request “as is” to these services. To configure this passthrough, you’ll need to configure a TCP router (even if your service handles HTTPS).

version: "3"

services:
  # ...
  my-tcp-app:
    image: containous/whoamitcp:v1.0.0
    labels:
      - "traefik.tcp.routers.my-tcp-app.rule=HostSNI(`tcp-example.com`)"
      - "traefik.tcp.routers.my-tcp-app.tls.passthrough=true"

Questions? Where to Go Next?

Hopefully, this article sheds light on how to configure Traefik 2 with TLS.

If there are missing use cases or still unanswered questions, let me know in the comments or on the community forum!

In the meantime — Happy Traefik!