> ## Content Index
> Fetch the complete content index at: https://notes.d4rkcyber.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Traefik v3 with Cloudflare DNS Challenge
- URL: https://notes.d4rkcyber.com/traefik-v3-with-cloudflare-dns-challenge/
- Published: 2026-06-02T00:00:00.000Z
- Updated: 2026-10-28T16:57:44.000Z
- Description: How I run Traefik v3 as a reverse proxy with automatic wildcard SSL using Cloudflare DNS challenge — the backbone of my public-facing homelab setup.
- Author: David Dami
- Tags: Infrastructure, traefik, docker, cloudflare, ssl

## Overview

Traefik v3 handles all reverse proxying for my Docker-based services. The main reason I use it over Nginx or Caddy for this: Docker label routing. Stick a few labels on any container and Traefik picks it up automatically — no config file edit, no reload.

Paired with Cloudflare's DNS API for the ACME challenge, I get wildcard SSL across all subdomains from a single cert. No manual cert work, no per-service cert requests.

## How the Traffic Flows

```
Internet → Cloudflare Edge → Traefik :443 → Container internal port

```

Cloudflare handles DDoS filtering at the edge. Traefik handles routing and SSL termination on my end. Containers only expose internal ports.

## Prerequisites

- Docker and Docker Compose running
- Domain managed by Cloudflare
- Cloudflare API token with `Zone:DNS:Edit` permission scoped to your domain

## Directory Layout

```
traefik/
├── compose.yml
├── traefik.yml
├── .env
└── letsencrypt/
    └── acme.json

```

Create the acme file before starting:

```bash
mkdir -p letsencrypt && touch letsencrypt/acme.json && chmod 600 letsencrypt/acme.json

```

Traefik will refuse to start if that file has wrong permissions.

## traefik.yml

```yaml
api:
  dashboard: true
  insecure: false

log:
  level: INFO

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

certificatesResolvers:
  cloudflare:
    acme:
      email: you@yourdomain.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

providers:
  docker:
    exposedByDefault: false
    network: proxy

```

## compose.yml

```yaml
networks:
  proxy:
    external: true

services:
  traefik:
    image: traefik:v3
    container_name: traefik
    restart: unless-stopped
    networks:
      - proxy
    ports:
      - "80:80"
      - "443:443"
    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    labels:
      - traefik.enable=true
      - traefik.http.routers.traefik-dashboard.rule=Host(`traefik.yourdomain.com`)
      - traefik.http.routers.traefik-dashboard.tls.certresolver=cloudflare
      - "traefik.http.routers.traefik-dashboard.tls.domains[0].main=yourdomain.com"
      - "traefik.http.routers.traefik-dashboard.tls.domains[0].sans=*.yourdomain.com"
      - traefik.http.routers.traefik-dashboard.service=api@internal

```

## .env

```env
CF_DNS_API_TOKEN=your_cloudflare_api_token

```

## Shared Proxy Network

Create the network once — all Traefik-routed services join it:

```bash
docker network create proxy

```

## Labelling a Service

Any service on the `proxy` network gets a route via labels:

```yaml
networks:
  - proxy

labels:
  - traefik.enable=true
  - traefik.http.routers.myapp.rule=Host(`myapp.yourdomain.com`)
  - traefik.http.routers.myapp.tls.certresolver=cloudflare
  - traefik.http.services.myapp.loadbalancer.server.port=8080

```

Traefik detects the new container and starts routing immediately. No restarts.

## Wildcard Certs

The `tls.domains` block in the Traefik labels requests a wildcard cert covering `*.yourdomain.com`. Once issued, all subdomain routes share it — no extra ACME requests per service.

The cert lives in `letsencrypt/acme.json`. Back this file up. Let's Encrypt rate limits will make a rough day if you lose it and have to re-request during setup.

## Verify

Check that cert issuance is working:

```bash
docker logs traefik 2>&1 | grep -i acme

```

Hit a service:

```bash
curl -I https://myapp.yourdomain.com

```

The Traefik dashboard lives at `traefik.yourdomain.com` once DNS is pointing at the server.