Skip to content

TLS and HTTPS

HTTPS is where a reverse proxy earns its keep most visibly: one place that holds certificates, terminates the encryption, and speaks plain HTTP to the backends behind it, which never have to know. This chapter does it the hand-made way — a local certificate authority, a certificate, a redirect — because doing it once by hand is what makes the automatic way (Let's Encrypt, at the end) readable. The hand-made way is also the right way for names that only exist on your own network.

Certificates in dynamic configuration

Certificates are dynamic configuration: they can be added and replaced without restarting Traefik. The lab's examples/dynamic-tls.yml:

tls:
  certificates:
    - certFile: ./certs/lab.crt
      keyFile: ./certs/lab.key
http:
  routers:
    fern-secure:
      rule: "Host(`fern.test`)"
      entryPoints:
        - websecure
      service: fern
      tls: {}

Three pieces: the certificate and its private key are named under tls.certificates; the router sits on the websecure entrypoint; and its tls: {} line is the switch that says "this router speaks HTTPS" — without it, the router would answer the TLS entrypoint with plain HTTP behaviour and confuse everyone.

Certificate paths are resolved from Traefik's working directory when the file provider reads them. Keep certificates in one known folder and say so plainly in the config; a certificate that cannot be found is a certificate Traefik silently replaces with its own built-in default — chapter 15 has the symptom.

A certificate authority of one's own

A certificate is trusted because a certificate authority (CA) signed it and the visitor's machine trusts that CA. For public sites the CA is a public one. For fern.test, the authority is you:

# 1. The CA: one key, one certificate
openssl req -x509 -newkey rsa:2048 \
  -keyout ca.key -out ca.crt -days 3650 \
  -nodes -subj "/CN=Mere Mortals Lab CA"

# 2. The site's key and a signing request
openssl req -newkey rsa:2048 \
  -keyout lab.key -out lab.csr -nodes \
  -subj "/CN=fern.test"

# 3. The names the certificate covers
printf 'subjectAltName=DNS:fern.test,DNS:otter.test\n' > san.ext

# 4. The CA signs it
openssl x509 -req -in lab.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out lab.crt -days 825 -extfile san.ext

The subjectAltName list in step 3 is the part modern browsers and tools actually check; the name on the certificate's subject is decoration. Transcript t06 verifies the finished certificate carries both lab names.

Then the proof, three ways (t06):

  • curl --cacert ca.crt https://fern.test:8443/hello works — trusting the lab CA makes the lab's certificates trustworthy.
  • The same request without --cacert fails, curl exit 60, certificate not trusted. That failure is the system working: a private CA convinces only machines you give it to. To make a home setup feel real, install ca.crt into the trust store of each of your devices — and understand you have made yourself an authority whose key (ca.key) can now mint trust for any name at all. Guard it like a password, because it is one.
  • A self-signed site certificate (no CA) gives the same exit 60. The CA's value is not the signature; it is that one trust decision covers every certificate it signs.

Sending HTTP visitors to HTTPS

A redirectScheme middleware on the plain-HTTP router:

    to-https:
      redirectScheme:
        scheme: https
        port: "8443"

Transcript t06: the answer to http://fern.test:8000/hello is 302 Found with Location: https://fern.test:8443/hello. Two details matter: the port: line exists because the lab's HTTPS is not on the standard port, and real deployments on 443 omit it; and every HTTP router needs the redirect (or the entrypoint needs a global redirection, which static configuration offers) — a router you forget stays quietly available in plain HTTP, passwords and all.

Let's Encrypt: the automatic way

This section is documented, not verified: issuing a real certificate needs a public domain and a server the internet can reach, and the lab sandbox is neither. For public names, Traefik can act as its own CA client — the ACME protocol, in practice Let's Encrypt. Static configuration names a certificatesResolvers entry with your email and a storage file for the certificates; a router's tls block then says certResolver: that-name instead of {}, and Traefik obtains, serves and renews the certificate by itself, proving you control the name either by answering an HTTP challenge on port 80 or a DNS challenge through your DNS provider's API. The storage file becomes a secret to back up (chapter 11) and the email is where expiry warnings go if renewal ever fails. Everything else in this chapter — entrypoints, tls:, redirects — is identical; only the certificate's origin changes.