Having issue that started a few weeks ago with Cloudflare & acme

I've been having issues with Traefik issuing Let's Encrypt certs and I can't seem to find the solution to them.

I had previously successfully gotten it to work, but since a few weeks I've been getting errors with the variable naming for cloudflare tokens. The latest documentation calls for CF_DNS_API_TOKEN and CF_API_EMAIL, but the logs are showing errors saying that "CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY" or "CLOUDFLARE_DNS_API_TOKEN,CLOUDFLARE_ZONE_API_TOKEN" are missing.

Here is the folder tree:

$ tree -pau 
[drwxr-xr-x user   ]  .
|-- [-rw------- user   ]  .env
|-- [drwxr-xr-x user   ]  data
|   |-- [-rw------- user   ]  .htpasswd
|   |-- [-rw------- root    ]  acme.json
|   `-- [drwxr-xr-x user   ]  dynamic
|       `-- [-rw-r--r-- user   ]  dynamic-pihole.yml
`-- [-rw-r--r-- user   ]  docker-compose.yml

My compose file:

services:
  traefik:
    image: "traefik:3.3"
    hostname: '{{.Node.Hostname}}'
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    networks:
      - traefik_net
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "/home/user/docker/traefik/data:/etc/traefik"
      - "/home/user/docker/traefik/data/dynamic:/etc/traefik/dynamic:ro"
    command:
      # API & Dashboard
      - "--api=true"
      - "--api.dashboard=true"
      - "--log.level=DEBUG"

      # Providers
      - "--providers.swarm=true"
      - "--providers.swarm.exposedByDefault=false"
      - "--providers.swarm.network=traefik_net"
      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.file.watch=true"

      # Entrypoints
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.web.http.redirections.entrypoint.permanent=true"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.asDefault=true"

      # Certificate Resolvers
      - "--certificatesresolvers.cloudflare.acme.email=${CF_API_EMAIL}"
      - "--certificatesresolvers.cloudflare.acme.storage=/etc/traefik/acme.json"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge=true"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge.propagation.delayBeforeChecks=10s"
      - "--certificatesresolvers.cloudflare.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"

    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
      - CF_API_EMAIL=${CF_API_EMAIL}
      - CF_ZONE_API_TOKEN=${CF_DNS_API_TOKEN}
        #- CLOUDFLARE_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
        #- CLOUDFLARE_EMAIL=${CF_API_EMAIL}
      - TZ=Europe/Nicosia

    deploy:
      placement:
        constraints:
          - node.hostname == ubuntu01
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 300s
      labels:
        - "traefik.enable=true"

        # Dashboard router:
        - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.my.domain`)"
        - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
        - "traefik.http.routers.traefik-dashboard.service=api@internal"
        - "traefik.http.routers.traefik-dashboard.middlewares=admin-auth"
        - "traefik.http.routers.traefik-dashboard.tls=true"
        - "traefik.http.routers.traefik-dashboard.tls.certresolver=cloudflare"
        - "traefik.http.routers.traefik-dashboard.tls.domains[0].main=my.domain"
        - "traefik.http.routers.traefik-dashboard.tls.domains[0].sans=*.my.domain"

        # Middleware
        - "traefik.http.middlewares.admin-auth.basicauth.usersfile=/etc/traefik/.htpasswd"

        # Dummy port
        - "traefik.http.services.traefik.loadbalancer.server.port=80"

networks:
  traefik_net:
    name: traefik_net
    external: true

What I think are the relevant log lines:

traefik_traefik.1.q0a1tiu3444u@ubuntu01    | 2025-07-17T12:21:18+03:00 DBG github.com/traefik/traefik/v3/pkg/provider/acme/provider.go:270 > Building ACME client... providerName=cloudflare.acme
traefik_traefik.1.q0a1tiu3444u@ubuntu01    | 2025-07-17T12:21:18+03:00 DBG github.com/traefik/traefik/v3/pkg/provider/acme/provider.go:276 > https://acme-staging-v02.api.letsencrypt.org/directory providerName=cloudflare.acme
traefik_traefik.1.q0a1tiu3444u@ubuntu01    | 2025-07-17T12:21:19+03:00 INF github.com/traefik/traefik/v3/pkg/provider/acme/provider.go:457 > Register... providerName=cloudflare.acme
traefik_traefik.1.q0a1tiu3444u@ubuntu01    | 2025-07-17T12:21:19+03:00 DBG github.com/traefik/traefik/v3/pkg/provider/acme/provider.go:317 > Using DNS Challenge provider: cloudflare providerName=cloudflare.acme
traefik_traefik.1.q0a1tiu3444u@ubuntu01    | 2025-07-17T12:21:19+03:00 ERR github.com/traefik/traefik/v3/pkg/provider/acme/provider.go:553 > Unable to obtain ACME certificate for domains error="cannot get ACME client cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_DNS_API_TOKEN,CLOUDFLARE_ZONE_API_TOKEN" ACME CA=https://acme-staging-v02.api.letsencrypt.org/directory acmeCA=https://acme-staging-v02.api.letsencrypt.org/directory domains=["my.domain","*.my.domain"] providerName=cloudflare.acme routerName=traefik-dashboard@swarm rule=Host(`traefik.my.domain`)

It's always those same ones repeated (.../acme/provider.go:270, 276, 457, 317, 553.

What am I missing? Please help!

Thanks!

Make sure the doc version (shown on desktop left bottom) matches your Traefik version.

Hi @bluepuma77, thank you for your fast reply. I don't understand where you mean to see the docversion? I'm running docker on an Ubuntu Server (no desktop) VM which is running on Proxmox.

If you mean the version number on the docs page I'm following, both 3.3 and 3.4 refer to the same variable names for Cloudflare.

v3.4:

v3.3:

Even the lego documentation states the same:

I'm not sure if it's a permissions issue reading the .env, or writing to the acme.json.... I've tried both user:user and root:root iterations.

I've tried including both versions of the variables (CF_DNS_API_TOKEN as well as CLOUDFLARE_DNS_API_TOKEN) but I get the same errors...

traefik_traefik.1.pmc344a4nlr7@ubuntu01 | 2025-07-18T15:10:09+03:00 ERR **github.com/traefik/traefik/v3/pkg/provider/acme/provider.go:555** > **Unable to obtain ACME certificate for domains** error=**"cannot get ACME client cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_DNS_API_TOKEN,CLOUDFLARE_ZONE_API_TOKEN"** ACME CA=https://acme-staging-v02.api.letsencrypt.org/directory acmeCA=https://acme-staging-v02.api.letsencrypt.org/directory domains=["my.domain","*.my.domain"] providerName=cloudflare.acme routerName=traefik-dashboard@swarm rule=Host(traefik.my.domain)

I ended up re-writing everything using claude opus 4 (neither my chatgpt nor my grok4 researched solved this)... final setup:

Files & folder structure, all files are user:user, nothing needs to be root:root. The acme.json file permission needs to be 600:

$ tree -agpu docker/traefik/
[drwxr-xr-x user    user   ]  docker/traefik/
|-- [-rw------- user    user   ]  .env
|-- [drwxr-xr-x user    user   ]  data
|   |-- [-rw-r----- user    user   ]  .htpasswd
|   |-- [-rw------- user    user   ]  acme.json
|   `-- [drwxr-xr-x user    user   ]  dynamic
|       |-- [-rw-r--r-- user    user   ]  dynamic-pihole.yml
|       |-- [-rw-r--r-- user    user   ]  middlewares.yml
|       `-- [-rw-r--r-- user    user   ]  tls-config.yml
`-- [-rw-r--r-- user    user   ]  docker-compose.yml

3 directories, 7 files

docker-compose:

version: '3.8'  # Added version

services:
  traefik:
    image: "traefik:3.3"
    hostname: '{{.Node.Hostname}}'
    ports:
      - "80:80"
      - "443:443"
    networks:
      - traefik_net
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "/home/user/docker/traefik/data:/etc/traefik"
      - "/home/user/docker/traefik/data/dynamic:/etc/traefik/dynamic:ro"
      - "/home/user/docker/traefik/data/.htpasswd:/etc/traefik/.htpasswd:ro"
    command:
      # API & Dashboard
      - "--api=true"
      - "--api.dashboard=true"
      - "--log.level=DEBUG"
      # Global
      - "--global.sendAnonymousUsage=false"
      # Providers
      - "--providers.swarm=true"
      - "--providers.swarm.exposedByDefault=false"
      - "--providers.swarm.network=traefik_net"
      - "--providers.file.directory=/etc/traefik/dynamic"
      - "--providers.file.watch=true"
      # Entrypoints
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      # Certificate Resolvers
      - "--certificatesresolvers.cloudflare.acme.email=${CF_API_EMAIL}"
      - "--certificatesresolvers.cloudflare.acme.storage=/etc/traefik/acme.json"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge=true"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
      - "--certificatesresolvers.cloudflare.acme.dnschallenge.delayBeforeCheck=30s"#
    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
      - CF_API_EMAIL=${CF_API_EMAIL}
      - TZ=Europe/Nicosia
    deploy:
      placement:
        constraints:
          - node.hostname == ubuntu01
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 300s
      labels:
        - "traefik.enable=true"
        # Dashboard router
        - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.my.domain`)"
        - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
        - "traefik.http.routers.traefik-dashboard.service=api@internal"
        - "traefik.http.routers.traefik-dashboard.middlewares=admin-auth@swarm"
        - "traefik.http.routers.traefik-dashboard.tls=true"
        - "traefik.http.routers.traefik-dashboard.tls.certresolver=cloudflare"
        - "traefik.http.routers.traefik-dashboard.tls.domains[0].main=my.domain"
        - "traefik.http.routers.traefik-dashboard.tls.domains[0].sans=*.my.domain"
        # Middleware
        - "traefik.http.middlewares.admin-auth.basicauth.usersfile=/etc/traefik/.htpasswd"
        # Service port
        - "traefik.http.services.traefik.loadbalancer.server.port=8080"

networks:
  traefik_net:
    name: traefik_net
    external: true

The .env file contains:

CF_API_EMAIL=my@email.domain
CF_DNS_API_TOKEN=<token string without quotes>

.htpasswd generated with `echo $(htpasswd -B -n user) > data/.htpasswd

then touch data/acme.json && chmod 600 data/acme.json

then the dynamic folder contains the following configs:

for dynamic-pihole.yml:

http:
  routers:
    pihole1:
      rule: "Host(`pihole1.my.domain`)"
      service: pihole1
      entryPoints:
        - web
      middlewares:
        - redirect-to-https@file  # Added @file suffix

    pihole1-secure:
      rule: "Host(`pihole1.my.domain`)"
      entryPoints:
        - websecure
      service: pihole1
      middlewares:
        - security-headers@file  # Added security headers
      tls:
        certResolver: cloudflare

    pihole2:
      rule: "Host(`pihole2.my.domain`)"
      service: pihole2
      entryPoints:
        - web
      middlewares:
        - redirect-to-https@file

    pihole2-secure:
      rule: "Host(`pihole2.my.domain`)"
      entryPoints:
        - websecure
      service: pihole2
      middlewares:
        - security-headers@file
      tls:
        certResolver: cloudflare

  services:
    pihole1:
      loadBalancer:
        servers:
          - url: "http://192.168.1.3"  # Removed /admin

    pihole2:
      loadBalancer:
        servers:
          - url: "http://192.168.1.4"  # Removed /admin

tls-config.yml:

tls:
  options:
    default:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true

middlewares.yml:

http:
  middlewares:
    security-headers:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "DENY"
        referrerPolicy: "strict-origin-when-cross-origin"
    
    rate-limit:
      rateLimit:
        average: 100
        burst: 200
        period: 1m
    
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: true

So it works after your config re-write?

Did you try the example on CLI?

CLOUDFLARE_EMAIL=you@example.com \
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run

or

CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run