Configuring Traefik v3 with Cloudflare DNS Challenge: Automatic Wildcard SSL for Docker

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Background & Why Use Traefik with DNS Challenge?

Managing SSL the traditional way while running dozens of containers on a single VPS? It’s a genuine nightmare. Every new service means another round of tedious setup: configuring a server block in Nginx, opening port 80, adding an A record, then sitting around waiting for Let’s Encrypt to validate via the HTTP-01 challenge. One new subdomain takes 10–15 minutes. Ten of them? There goes your entire afternoon.

Traefik v3 solves this problem in a completely different way. It continuously watches the Docker socket — when you start a new container with the right labels attached, Traefik automatically detects it and provisions SSL without you lifting a finger. Even better, by combining it with Cloudflare DNS Challenge, you get:

  • Wildcard SSL certificates (*.yourdomain.com): a single certificate that covers every subdomain.
  • No need to expose port 80 publicly — ideal for internal services that shouldn’t be accessible from the internet.
  • Automatic renewal before expiration — zero manual intervention required.

I used Nginx Proxy Manager for a while, but switching to Traefik v3 felt like going from a manual transmission to an automatic — everything is fully automated thanks to Docker’s label system.

Installation & Prerequisites

Missing any one of these three things will leave you stuck halfway through, so get everything ready before you begin:

1. Cloudflare API Token

This token allows Traefik to create a temporary TXT record on DNS to prove domain ownership — Let’s Encrypt checks that record before issuing the certificate. Never use the Global API Key: if it leaks, your entire Cloudflare account could be compromised.

Go to My Profile > API Tokens > Create Token. Select the “Edit zone DNS” template, and scope the permissions to only the specific zone (domain) you need — don’t grant access to the entire account.

2. Directory Structure

I typically organize directories like this for easier management and backups:

mkdir -p ~/traefik/data
touch ~/traefik/data/acme.json
chmod 600 ~/traefik/data/acme.json
touch ~/traefik/docker-compose.yml
touch ~/traefik/data/traefik.yml

Note: The acme.json file stores your SSL certificates. It must have 600 permissions — otherwise, Traefik will refuse to start with the error “acme.json” should have permissions 600.

3. Point Your Domain to the VPS

Add an A record pointing *.yourdomain.com to your VPS IP in the Cloudflare dashboard. Important: disable the Proxy (the orange cloud icon) at this step. If you leave Proxy enabled, Traefik will receive Cloudflare’s IP instead of your VPS IP — and the cert request will fail immediately.

Traefik v3 Configuration in Detail

This section walks through each file one by one. Traefik v3 changed some syntax from v2 — particularly around entryPoints and certResolver — so don’t copy your old config wholesale without reviewing it first.

Step 1: Static Configuration File (traefik.yml)

This file is loaded once at startup and defines entrypoints, the Docker provider, and ACME configuration for automatic certificate provisioning.

api:
  dashboard: true
  debug: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https

  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

certificatesResolvers:
  cloudflare:
    acme:
      email: [email protected]
      storage: /acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

Step 2: Docker Compose File (docker-compose.yml)

This file starts Traefik and mounts the three things it needs: the static config, the Docker socket (to read container labels), and acme.json (to store certificates once issued).

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: always
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - 80:80
      - 443:443
    environment:
      - CF_DNS_API_TOKEN=YOUR_CLOUDFLARE_API_TOKEN
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/acme.json:/acme.json
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.tls.certresolver=cloudflare"
      - "traefik.http.routers.dashboard.tls.domains[0].main=yourdomain.com"
      - "traefik.http.routers.dashboard.tls.domains[0].sans=*.yourdomain.com"

networks:
  proxy:
    external: true

Create the network before running: docker network create proxy. This network is shared by every container you want to expose through Traefik — create it once, use it forever.

When debugging Docker API responses or inspecting Traefik’s dynamic config as JSON, I paste it into the formatter at toolcraft.app/en/tools/developer/json-formatter — much faster than installing a browser extension.

Step 3: Deploy a Test Application

To verify the system is working, deploy a lightweight whoami container:

services:
  whoami:
    image: traefik/whoami
    container_name: test-app
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
      - "traefik.http.routers.whoami.tls.certresolver=cloudflare"

Verification & Monitoring

After running docker compose up -d, check the logs right away:

docker logs -f traefik

When you see the line "Legitimate certificate retrieved for domain yourdomain.com", your certificate has been issued. Open https://whoami.yourdomain.com — a green HTTPS padlock with no warnings means you’re all set.

A few things to keep an eye on when monitoring:

  • Dashboard: Visit https://traefik.yourdomain.com to get a visual overview of running routers and services — extremely useful when debugging.
  • Rate Limit: Let’s Encrypt caps you at 50 certs per domain per week. Misconfiguring and restarting repeatedly can drain that quota in just a few hours. When testing, use the staging environment by adding caServer: "https://acme-staging-v02.api.letsencrypt.org/directory" to the acme section in traefik.yml.
  • Propagation: Cloudflare’s TXT records need anywhere from 30 seconds to a few minutes to propagate. Don’t restart the container right away if you don’t see a cert in the first minute or two — let Traefik handle it automatically.

After running this setup for several weeks, I haven’t had to touch SSL once. Adding a new service is just a matter of attaching the right labels — Traefik takes care of the rest. If you run into any issues along the way — especially around API Token permissions or DNS propagation — drop a comment below.

Share: