Traefik Tls Enabled Subdomains with Cloudflare
Traefik TLS enabled subdomains with cloudflare
Suyash Singh
Posted by Suyash Singh
on May 4, 2024
Photo by Marc on Unsplash

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.

  1. HTTP challenge: expose a token on an HTTP endpoint.
  2. TLS challenge: expose a temporary certificates on an HTTPS endpoint.
  3. 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 https 443
  • 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:
traefik.yaml
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

traefk-compose.yaml
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

traefik-compose.yaml
 
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.