In my opinion, Traefik is a very simple to use & configure container first reverse proxy which makes it incredibly easy to manage the wiring around self-hosted services. What’s even cooler is that with docker these services are contained as a manifest in a single compose file. As a result, it is a cakewalk to replicate the self hosted environment or to use docker swarm. In this post let’s see how to utilize Traefik’s ACME framework to automatically obtain and wireup wildcard SSL TLS certificates.
ACME stands for Automated Certificate Management Environment. It’s a protocol designed to automate the process of certificate issuance and management, particularly for SSL/TLS certificates used in securing websites.
The primary goal of ACME is to simplify the process of obtaining, renewing, and revoking SSL/TLS certificates by automating the interactions between certificate authorities (CAs) and web servers. This automation helps reduce the administrative burden associated with certificate management and ensures that certificates are regularly renewed to maintain secure connections.
Simply put, managing certificates for multiple services in timely manner requires tedious manual efforts, and ACME protocol aims to solve. Traefik’s ACME framework proves useful to manage these certificates.
Before we begin here’s a difference between different challenges by which we can obtain these certificates.
- HTTP challenge: expose a token on an HTTP endpoint.
- TLS challenge: expose a temporary certificates on an HTTPS endpoint.
- DNS challenge: expose a TXT record on a DNS.
There are several ways to establish ACME with Traefik, today we are going to cover DNS verification using Cloudflare to obtain a wildcard certificate (ex: *.example.com) for all the subdomains services automatically, using Cloudflare
provider
.
Create Cloudflare API token for DNS management
Create an API token on Cloudflare using the Edit DNS Zone template. We will be using this API token below along with our cloudflare email address to issue wildcard certificates for our subdomain service endpoints.
Create CNAME entries for subdomains
In the DNS management section of Cloudflare create CNAME entries for the subdomains you want to expose.
Configure Traefik
Our target is to:
- Get access to Traefik’s dashboard: Gets easier to see routing and middleware rules
- Redirect all http requests to https
- Register http with port
80
and https443
- Avoid SSL errors, if any, between internal docker services
- Register cloudflare resolvers for TXT DNS challenge success propagation: To issue the certificate successfully, there is a need for TXT domain records to be available across DNS servers managed by cloudflare.
- Let’s accomplish all of the above using the following
traefik.yml
configuration:
api:
dashboard: true
debug: true
entryPoints:
http:
address: ":80"
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ":443"
serversTransport:
insecureSkipVerify: true
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
# use this config file for configuring additional Traefik providers if needed
filename: /config.yml
certificatesResolvers:
cloudflare:
acme:
email: [[Add your cloudflare email]]
storage: acme.json
dnsChallenge:
provider: cloudflare
#disablePropagationCheck: true # uncomment this if you have issues pulling certificates through cloudflare, By setting this flag to true disables the need to wait for the propagation of the TXT record to all authoritative name servers.
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
log:
level: "INFO"
filePath: "/var/log/traefik/traefik.log"
accessLog:
filePath: "/var/log/traefik/access.log"
Create acme.json
We need to create acme.json
file with 0600
permissions since it will store all the keys and secrets related to the certificates.
sudo touch acme.json
sudo chmod 0600 acme.json
Ceate external docker network
By default it’s a good practice to not expose all your docker containers to external network, that’s why for all the communication between different docker containers we are going to create a virtual docker network:
docker create network proxy
Generate encrypted password
To secure our traefik’s dashboard, in this post we are going to use Basic Auth
. We are going to generate an encrypted version of our password using apache-util
’s htpasswd
utility. It’s never a good practice to store passwords in plaintext, that’s why we are using tools like htpasswd
to create an encrypted version of our password. We will be using this encrypted form of the password in our compose file for securing Traefik
’s dashboard, specially with Basic Auth
.
htpasswd -bn [[username]] [[password]]
This yields output in the form of [[username]]:[[encrypted_password]]
. Copy it, we are going to use it below in our docker compose yaml file.
Note: I am not a big fan of Basic Auth because of how inferior it is to its alternatives. However, for the sake of brevity, I decided to not entertain other alternatives in this post. And, in subsequent posts about Traefik we’ll see how to replace it with Oauth2 or Open ID connect.
Create Traefik container
version: '3.5'
services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
proxy:
ports:
- 80:80
- 443:443
environment:
- CF_API_EMAIL=[[your cloudflare account email]]
- CF_DNS_API_TOKEN=[[cloudflare DNS API token]]
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
# modify these paths according to your system environment
- /home/ubuntu/traefik/traefik.yml:/traefik.yml:ro
- /home/ubuntu/traefik/acme.json:/acme.json
# in this post we are not utilizing any this config.yml, but to extend traefik's capabilities I usually keep my config in this file.
- /home/ubuntu/traefik/config.yml:/config.yml:ro
- /home/ubuntu/traefik/logs:/var/log/traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.in`)"
- "traefik.http.middlewares.traefik-auth.basicauth.users=[[user]]:[[encrypted_password]]"
- "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain.in`)"
- "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.traefik-secure.tls.domains[0].main=yourdomain.in"
- "traefik.http.routers.traefik-secure.tls.domains[0].sans=*.yourdomain.in"
- "traefik.http.routers.traefik-secure.service=api@internal"
Create docker spec for our service with traefik routings and middlewares
traefik:
...configuration we declared above
whoami:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.entrypoints=http"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.in`)"
- "traefik.http.middlewares.whoami-https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.whoami-secure.entrypoints=https"
- "traefik.http.routers.whoami-secure.rule=Host(`whoami.yourdomain.in`)"
- "traefik.http.routers.whoami-secure.tls=true"
- "traefik.http.routers.whoami-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.whoami-secure.tls.domains[0].main=yourdomain.in"
- "traefik.http.routers.whoami-secure.tls.domains[0].sans=*.yourdomain.in"
- 'traefik.http.services.whoami.loadbalancer.server.port=80'
networks:
- proxy
...other self hosted services
Conclusion
version: '3.5'
services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
proxy:
ports:
- 80:80
- 443:443
environment:
- CF_API_EMAIL=[[your cloudflare account email]]
- CF_DNS_API_TOKEN=[[cloudflare DNS API token]]
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
# modify these paths according to your system environment
- /home/ubuntu/traefik/traefik.yml:/traefik.yml:ro
- /home/ubuntu/traefik/acme.json:/acme.json
# in this post we are not utilizing any this config.yml, but to extend traefik's capabilities I usually keep my config in this file.
- /home/ubuntu/traefik/config.yml:/config.yml:ro
- /home/ubuntu/traefik/logs:/var/log/traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.in`)"
# Replace the [[user]]:[[encrypted_password]] we obtained above with htpasswd utility
- "traefik.http.middlewares.traefik-auth.basicauth.users=[[user]]:[[encrypted_password]]"
- "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain.in`)"
- "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.traefik-secure.tls.domains[0].main=yourdomain.in"
- "traefik.http.routers.traefik-secure.tls.domains[0].sans=*.yourdomain.in"
- "traefik.http.routers.traefik-secure.service=api@internal"
whoami:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.entrypoints=http"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.in`)"
- "traefik.http.middlewares.whoami-https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.whoami-secure.entrypoints=https"
- "traefik.http.routers.whoami-secure.rule=Host(`whoami.yourdomain.in`)"
- "traefik.http.routers.whoami-secure.tls=true"
- "traefik.http.routers.whoami-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.whoami-secure.tls.domains[0].main=yourdomain.in"
- "traefik.http.routers.whoami-secure.tls.domains[0].sans=*.yourdomain.in"
- 'traefik.http.services.whoami.loadbalancer.server.port=80'
networks:
- proxy
networks:
proxy:
name: proxy
external: true
Let’s conclude this post and wrap it up by executing our docker compose spec:
docker-compose -f traefik-compose.yml up -d
Now, if you allow couple of seconds or a minute for DNS TXT records to be updated on Cloudflare
’s end, you can verify that the certificate used for traefik.yourdomain.in
attributes the parent domain since we instructed to obtain wildcard certificates in our configurations above.