Automating SSL certificate issuance with Traefik, Let's Encrypt and Cloudflare

Where your laziness turns as positive and site reliability mindset

In today’s digital landscape, securing your web applications with HTTPS is not just a recommendation but a necessity. In this blog post, I’ll share how I setup automated SSL certificate issuance for my domains, including wildcard certificates, using Traefik, Let’s Encrypt, and Cloudflare.

The Setup

My goal was to automate the management of SSL certificates for various subdomains and wildcard domains like:

  • co.example.com
  • io.example.com
  • *.co.example.com
  • *.io.example.com

By combining Traefik (a modern HTTP reverse proxy), Let’s Encrypt (for free SSL certificates), and Cloudflare (for DNS management), I created a fully automated SSL certificate renewal system for my homelab setup.

Prerequisites

  • A domain registered with any registrar (my domains are with Namecheap and OVHCloud), but DNS aka nameservers managed through Cloudflare
  • Docker and Docker Compose installed on your server (Portainer if you are interested)
  • Basic understanding of Docker, DNS, and networking concepts
Step 1: Configure Traefik in Docker Compose or in Portainer

First, I setup Traefik using Portainer. Here’s a simplified version of my configuration: (I user Portainer, so my environment variables are handled in Portainer itself. You may have to handle them with .env file if you are using docker-compose.yml)

---
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_DNS_API_TOKEN: ${CF_DNS_API_TOKEN}
      TRAEFIK_DASHBOARD_CREDENTIALS: ${TRAEFIK_DASHBOARD_CREDENTIALS}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /home/docker/traefik/data/traefik.yml:/traefik.yml:ro
      - /home/docker/traefik/data/acme.json:/acme.json
      - /home/docker/traefik/data/config.yml:/config.yml:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-http.entrypoints=http"
      - "traefik.http.routers.traefik-http.rule=Host(`traefik.io.example.com`)"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_CREDENTIALS}"
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
      - "traefik.http.routers.traefik-http.middlewares=traefik-https-redirect"
      - "traefik.http.routers.traefik-https.entrypoints=https"
      - "traefik.http.routers.traefik-https.rule=Host(`traefik.io.example.com`)"
      - "traefik.http.routers.traefik-https.middlewares=traefik-auth"
      - "traefik.http.routers.traefik-https.tls=true"
      - "traefik.http.routers.traefik-https.tls.certresolver=cloudflare"
      - "traefik.http.routers.traefik-https.tls.domains[0].main=io.example.com"
      - "traefik.http.routers.traefik-https.tls.domains[0].sans=*.io.example.com"
	  - "traefik.http.routers.traefik-https.tls.domains[1].main=co.example.com"
      - "traefik.http.routers.traefik-https.tls.domains[1].sans=*.co.example.com"
      - "traefik.http.routers.traefik-https.service=api@internal"

networks:
  proxy:
    external: true
Step 2: Create the Traefik configuration file

Next, I created a traefik.yml file to configure the core Traefik settings:

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:
    filename: /config.yml
certificatesResolvers:
  cloudflare:
    acme:
      email: [email protected]
      storage: acme.json
      caServer: https://acme-v02.api.letsencrypt.org/directory # prod (default)
      # caServer: https://acme-staging-v02.api.letsencrypt.org/directory # staging
      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.
        #delayBeforeCheck: 60s # uncomment along with disablePropagationCheck if needed to ensure the TXT record is ready before verification is attempted
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

Step 3: Create an empty ACME JSON file

Traefik needs a file to store the certificates:

touch acme.json
chmod 600 acme.json

Step 4: Setup DNS records in Cloudflare

In the Cloudflare dashboard for my domain, I haven’t added any DNS records. Why? Because, I do not run anything in co.example.com or io.example.com. Because my sole purpose is to issue wildcard SSL certificates for the services under *.co.example.com and *.io.example.com. If you are running anything on your domains, please feel free to add your A record or CNAME

For the wildcard subdomains, I didn’t need to create separate DNS records since Cloudflare supports wildcard certificates through the DNS challenge.

Step 5: Generate Cloudflare API tokens

To allow Traefik to automatically verify domain ownership, I created a Cloudflare API token with the following permissions: You can access it from your Cloudflare Dashboard

  • Zone > Zone > Read
  • Zone > DNS > Edit
Step 6: Launch Traefik

With everything configured, I launched Traefik:

docker-compose up -d

How it works?

Here’s the magic behind this setup:

  1. When a service that needs HTTPS is deployed, Traefik automatically detects it through Docker labels
  2. If a certificate doesn’t exist, Traefik uses Let’s Encrypt to issue one
  3. For wildcard certificates, Traefik uses the DNS challenge method with Cloudflare
  4. Traefik handles certificate renewal automatically

The Result: Fully automated certificate management

After setting this up, I received confirmation emails from Cloudflare about new certificate issuance:

Hello,

Cloudflare has observed issuance of the following certificate for example.com or one of its subdomains:

Log date: 2025-02-10 17:13:32 UTC
Issuer: CN=R11,O=Let's Encrypt,C=US
Validity: 2025-02-10 16:15:02 UTC - 2025-05-06 16:15:01 UTC
DNS Names: *.io.example.com, io.example.com

Similarly, I received another email for the co.example.com and *.co.example.com wildcard certificate.

Adding new wildcard domains

When I needed to add another subdomain (something.example.com) and wildcard domain (*.something.example.com), I simply added two new label lines to my Traefik service in docker-compose.yml:

- "traefik.http.routers.traefik-https.tls.domains[2].main=something.example.com"
- "traefik.http.routers.traefik-https.tls.domains[2].sans=*.something.example.com"

After restarting Traefik, it automatically obtained the new wildcard certificate.

The Benefits

This setup provides several key advantages:

  1. Automation: No manual certificate renewal or installation
  2. Scalability: Easy to add new domains and subdomains
  3. Security: Always up-to-date SSL certificates
  4. Cost Efficiency: Free certificates from Let’s Encrypt
  5. Flexibility: Works with wildcard domains and multiple subdomains

Conclusion

By combining Traefik, Let’s Encrypt, and Cloudflare, I’ve created a robust and automated system for managing SSL certificates across all my domains and subdomains. This approach not only saves time but also ensures that my web applications are always securely served over HTTPS.

Whether you’re running a small personal project or managing multiple services, this setup provides a scalable and maintainable solution for SSL certificate management.


Note: Remember to replace placeholder values like example.com, [email protected] and API tokens with your actual information when implementing this solution.


See also