- 
      
- 
        Save mattdy/d741344366a4fbb86f5034adfd1ad191 to your computer and use it in GitHub Desktop. 
| Please see https://mattdyson.org/blog/2024/02/using-traefik-with-cloudflare-tunnels for a detailed write-up of this configuration | 
| ROOT_DOMAIN=yourdomain.com | |
| HTTP_TIMEOUT=60 | |
| POLLING_INTERVAL=10 | |
| PROPAGATION_TIMEOUT=3600 | |
| TTL=300 | |
| PROVIDERS_GOOGLE_CLIENT_ID=<GOOGLE CLIENT ID> | |
| PROVIDERS_GOOGLE_CLIENT_SECRET=<GOOGLE CLIENT SECRET> | |
| SECRET=RandomTextGoesHere | |
| WHITELIST=<YOUR GOOGLE ACCOUNT EMAIL> | |
| LOG_LEVEL=INFO | |
| ZONE_ID=<YOUR CLOUDFLARE ZONE ID> | |
| TUNNEL_TOKEN=<YOUR CLOUDFLARE TUNNEL TOKEN> | 
| version: '3.7' | |
| services: | |
| whoami: | |
| image: traefik/whoami | |
| command: | |
| - --name=externalapp | |
| deploy: | |
| labels: | |
| - "traefik.enable=true" | |
| - "traefik.docker.network=traefik" | |
| - "traefik.http.routers.external.rule=Host(`external.yourdomain.com`)" | |
| - "traefik.http.routers.external.entrypoints=websecure" | |
| - "traefik.http.routers.external.tls=true" | |
| - "traefik.http.routers.external.middlewares=forward-auth" | |
| - "traefik.http.services.external.loadbalancer.server.port=80" | |
| - "traefik.constraint=proxy-public" | |
| networks: | |
| traefik: | |
| external: true | 
| version: '3.7' | |
| services: | |
| whoami: | |
| image: traefik/whoami | |
| command: | |
| - --name=internalapp | |
| deploy: | |
| labels: | |
| - "traefik.enable=true" | |
| - "traefik.docker.network=traefik" | |
| - "traefik.http.routers.internal.rule=Host(`internal.yourdomain.com`)" | |
| - "traefik.http.routers.internal.entrypoints=websecure" | |
| - "traefik.http.routers.internal.tls=true" | |
| - "traefik.http.services.internal.loadbalancer.server.port=80" | |
| networks: | |
| traefik: | |
| external: true | 
| version: '3.7' | |
| services: | |
| reverse-proxy: | |
| image: traefik:v2.10 | |
| command: | |
| - "--log" | |
| - "--log.level=${LOG_LEVEL:-INFO}" | |
| - "--log.format=json" | |
| - "--api.insecure=true" | |
| - "--providers.docker" | |
| - "--providers.docker.swarmMode=true" | |
| - "--providers.docker.exposedbydefault=false" | |
| - "--providers.file.directory=/config" | |
| - "--providers.file.watch=true" | |
| - "--serversTransport.insecureSkipVerify=true" # Allow self-signed certificates for target hosts - https://doc.traefik.io/traefik/routing/overview/#insecureskipverify | |
| - "--metrics" | |
| - "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0" | |
| - "--entrypoints.web.address=:80" | |
| - "--entrypoints.web.http.redirections.entrypoint.to=websecure" | |
| - "--entrypoints.web.http.redirections.entrypoint.scheme=https" | |
| - "--entrypoints.websecure.address=:443" | |
| - "--entrypoints.websecure.http.tls=true" | |
| - "--entrypoints.websecure.http.tls.certresolver=letsencrypt" | |
| - "--entrypoints.webinternal.address=:82" | |
| - "--certificatesresolvers.letsencrypt.acme.email=<YOUR EMAIL>" | |
| - "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/letsencrypt.json" | |
| - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare" | |
| - "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=300" | |
| - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=8.8.8.8:53" | |
| secrets: | |
| - cf_token | |
| environment: | |
| - CLOUDFLARE_DNS_API_TOKEN_FILE=/run/secrets/cf_token | |
| - CLOUDFLARE_HTTP_TIMEOUT=${HTTP_TIMEOUT} | |
| - CLOUDFLARE_POLLING_INTERVAL=${POLLING_INTERVAL} | |
| - CLOUDFLARE_PROPAGATION_TIMEOUT=${PROPAGATION_TIMEOUT} | |
| - CLOUDFLARE_TTL=${TTL} | |
| deploy: | |
| restart_policy: | |
| condition: any | |
| delay: 5s | |
| max_attempts: 3 | |
| window: 120s | |
| update_config: # Start new instance before stopping existing one | |
| delay: 10s | |
| order: start-first | |
| parallelism: 1 | |
| rollback_config: | |
| parallelism: 0 | |
| order: stop-first | |
| placement: | |
| constraints: | |
| - node.role == manager | |
| labels: | |
| - traefik.enable=true | |
| - traefik.http.routers.api.rule=Host(`traefik.${ROOT_DOMAIN}`) | |
| - traefik.http.routers.api.service=api@internal | |
| - traefik.http.routers.api.entrypoints=websecure | |
| - traefik.http.routers.api.tls=true | |
| - traefik.http.services.api.loadbalancer.server.port=8080 | |
| ports: | |
| # HTTP | |
| - target: 80 | |
| published: 80 | |
| # HTTPS | |
| - target: 443 | |
| published: 443 | |
| # Web UI (enabled by --api.insecure=true) | |
| - target: 8080 | |
| published: 8080 | |
| networks: | |
| - traefik | |
| - internal | |
| volumes: | |
| # So that Traefik can listen to the Docker events | |
| - /var/run/docker.sock:/var/run/docker.sock | |
| - acme:/etc/traefik/acme | |
| - traefik:/config | |
| - cloudflare:/cloudflare | |
| traefik-forward-auth: | |
| image: thomseddon/traefik-forward-auth:2.1.0 | |
| networks: | |
| - traefik | |
| environment: | |
| - PROVIDERS_GOOGLE_CLIENT_ID=${PROVIDERS_GOOGLE_CLIENT_ID} | |
| - PROVIDERS_GOOGLE_CLIENT_SECRET=${PROVIDERS_GOOGLE_CLIENT_SECRET} | |
| - SECRET=${SECRET} | |
| - AUTH_HOST=auth.${ROOT_DOMAIN} | |
| - COOKIE_DOMAIN=${ROOT_DOMAIN} | |
| - WHITELIST=${WHITELIST} | |
| deploy: | |
| labels: | |
| - traefik.enable=true | |
| - traefik.docker.network=traefik | |
| - traefik.http.routers.auth.rule=Host(`auth.${ROOT_DOMAIN}`) | |
| - traefik.http.routers.auth.entrypoints=websecure | |
| - traefik.http.routers.auth.tls=true | |
| - traefik.http.routers.auth.tls.domains[0].main=${ROOT_DOMAIN} | |
| - traefik.http.routers.auth.tls.domains[0].sans=*.${ROOT_DOMAIN} | |
| - traefik.http.routers.auth.tls.certresolver=letsencrypt | |
| - traefik.http.routers.auth.service=auth@docker | |
| - traefik.http.services.auth.loadbalancer.server.port=4181 | |
| - traefik.http.middlewares.forward-auth.forwardauth.address=http://traefik-forward-auth:4181 | |
| - traefik.http.middlewares.forward-auth.forwardauth.trustForwardHeader=true | |
| - traefik.http.middlewares.forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User | |
| - traefik.http.routers.auth.middlewares=forward-auth | |
| - traefik.constraint=proxy-public | |
| tunnel: | |
| container_name: cloudflared-tunnel | |
| image: cloudflare/cloudflared | |
| restart: unless-stopped | |
| command: tunnel run | |
| deploy: | |
| mode: replicated | |
| replicas: 3 | |
| update_config: | |
| delay: 30s | |
| order: start-first | |
| monitor: 20s | |
| networks: | |
| - traefik | |
| environment: | |
| - TUNNEL_TOKEN=${TUNNEL_TOKEN} | |
| error-pages: | |
| image: tarampampam/error-pages:2.26.0 | |
| environment: | |
| TEMPLATE_NAME: l7-dark | |
| networks: | |
| - traefik | |
| deploy: | |
| mode: replicated | |
| replicas: 2 | |
| update_config: | |
| delay: 20s | |
| order: start-first | |
| monitor: 10s | |
| labels: | |
| - traefik.enable=true | |
| - traefik.docker.network=traefik | |
| # use as "fallback" for any non-registered services (with priority below normal) | |
| - traefik.http.routers.error-pages.rule=HostRegexp(`{host:.+}`) | |
| - traefik.http.routers.error-pages.priority=10 | |
| # should say that all of your services work on https | |
| - traefik.http.routers.error-pages.tls='true' | |
| - traefik.http.routers.error-pages.entrypoints=websecure | |
| - traefik.http.routers.error-pages.middlewares=error-pages | |
| - traefik.http.services.error-pages.loadbalancer.server.port=8080 | |
| # "errors" middleware settings | |
| - traefik.http.middlewares.error-pages.errors.status=400-599 | |
| - traefik.http.middlewares.error-pages.errors.service=error-pages | |
| - traefik.http.middlewares.error-pages.errors.query=/{status}.html | |
| cloudflare-companion: | |
| image: ghcr.io/tiredofit/docker-traefik-cloudflare-companion:latest | |
| volumes: | |
| - /var/run/docker.sock:/var/run/docker.sock | |
| deploy: | |
| placement: | |
| constraints: | |
| - node.role == manager | |
| environment: | |
| - TIMEZONE=Europe/London | |
| - LOG_TYPE=CONSOLE | |
| - LOG_LEVEL=INFO | |
| - TRAEFIK_VERSION=2 | |
| - RC_TYPE=CNAME | |
| - TARGET_DOMAIN=${ROOT_DOMAIN} | |
| - REFRESH_ENTRIES=TRUE | |
| - DOCKER_SWARM_MODE=TRUE | |
| - ENABLE_TRAEFIK_POLL=TRUE | |
| - TRAEFIK_POLL_URL=https://traefik.${ROOT_DOMAIN}/api | |
| - TRAEFIK_FILTER_LABEL=traefik.constraint | |
| - TRAEFIK_FILTER=proxy-public | |
| - DOMAIN1=${ROOT_DOMAIN} | |
| - DOMAIN1_ZONE_ID=${ZONE_ID} | |
| - DOMAIN1_PROXIED=TRUE | |
| restart: always | |
| networks: | |
| - internal | |
| secrets: | |
| - cf_token | |
| networks: | |
| traefik: | |
| external: true | |
| internal: | |
| volumes: | |
| acme: | |
| traefik: | |
| cloudflare: | |
| secrets: | |
| cf_token: | |
| external: true | 
noticed one small item [TUNNEL_TOKEN] is missing from your env file.
Good spot @jstimmer, thanks very much - I've updated the environment file to include that. Unfortunately cloudflared can't accept a file name containing the token yet - it looks like this functionality is in development though: cloudflare/cloudflared#645
Glad you found the writeup useful!
@mattdy Thank you for the writeup!! trying to set it up but not using docker swarm. Below is my whole config and it throws error for companion app:
`version: '3.7'
services:
reverse-proxy:
image: traefik:v2.10
container_name: traefik1
command:
- "--log"
- "--log.level=${LOG_LEVEL:-INFO}"
- "--log.format=json"
- "--api.insecure=true"
- "--providers.docker"
- "--providers.docker.exposedbydefault=false"
- "--providers.file.directory=/config"
- "--providers.file.watch=true"
- "--serversTransport.insecureSkipVerify=true"
- "--metrics"
- "--metrics.prometheus.buckets=0.1,0.3,1.2,5.0"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
- "--entrypoints.websecure.http.tls.certresolver=letsencrypt"
- "--entrypoints.webinternal.address=:82"
- "--certificatesresolvers.letsencrypt.acme.email=EMAIL"
- "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/letsencrypt.json"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=300"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53"
environment:
- CF_DNS_API_TOKEN=${CF_TOKEN}
- CLOUDFLARE_HTTP_TIMEOUT=${HTTP_TIMEOUT}
- CLOUDFLARE_POLLING_INTERVAL=${POLLING_INTERVAL}
- CLOUDFLARE_PROPAGATION_TIMEOUT=${PROPAGATION_TIMEOUT}
- CLOUDFLARE_TTL=${TTL}
restart: always
ports:
- "80:80"
- "443:443"
- "8080:8080"
networks:
- traefik
- internal
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme:/etc/traefik/acme
- ./traefik:/config
- ./cloudflare:/cloudflare
labels:
- traefik.enable=true
- traefik.http.routers.api.rule=Host(traefik.${ROOT_DOMAIN})
- traefik.http.routers.api.service=api@internal
- traefik.http.routers.api.entrypoints=websecure
- traefik.http.routers.api.tls=true
- traefik.http.services.api.loadbalancer.server.port=8080
tunnel:
container_name: cloudflared-tunnel1
image: cloudflare/cloudflared
restart: unless-stopped
command: tunnel run
networks:
- traefik
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN}
error-pages:
image: tarampampam/error-pages:2.26.0
container_name: error-pages1
environment:
TEMPLATE_NAME: l7-dark
networks:
- traefik
restart: always
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.http.routers.error-pages.rule=HostRegexp({host:.+})
- traefik.http.routers.error-pages.priority=10
- traefik.http.routers.error-pages.tls='true'
- traefik.http.routers.error-pages.entrypoints=websecure
- traefik.http.routers.error-pages.middlewares=error-pages
- traefik.http.services.error-pages.loadbalancer.server.port=8080
- traefik.http.middlewares.error-pages.errors.status=400-599
- traefik.http.middlewares.error-pages.errors.service=error-pages
- traefik.http.middlewares.error-pages.errors.query=/{status}.html
cloudflare-companion:
image: ghcr.io/tiredofit/docker-traefik-cloudflare-companion:latest
container_name: cloudflare-companion1
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./cf_token:/run/secrets/cf_token:ro
environment:
- TIMEZONE=Asia/Kolkata
- LOG_TYPE=CONSOLE
- LOG_LEVEL=INFO
- TRAEFIK_VERSION=2
- RC_TYPE=CNAME
- TARGET_DOMAIN=${ROOT_DOMAIN}
- REFRESH_ENTRIES=TRUE
- ENABLE_TRAEFIK_POLL=TRUE
- CF_EMAIL=${YOUR_EMAIL}
- CF_TOKEN=${CF_TOKEN}
- TRAEFIK_POLL_URL=https://traefik.${ROOT_DOMAIN}/api
- TRAEFIK_FILTER_LABEL=traefik.constraint
- TRAEFIK_FILTER=proxy-public
- DOMAIN1=${ROOT_DOMAIN}
- DOMAIN1_ZONE_ID=${ZONE_ID}
- DOMAIN1_PROXIED=TRUE
restart: always
networks:
- internal
networks:
traefik:
name: traefik
external: true
internal:
name: internal
volumes:
acme:
traefik:
cloudflare:`
The error I am getting is:
cloudflare-companion1  | Traceback (most recent call last):
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 716, in urlopen
cloudflare-companion1  |     httplib_response = self._make_request(
cloudflare-companion1  |                        ^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 404, in _make_request
cloudflare-companion1  |     self._validate_conn(conn)
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 1061, in validate_conn
cloudflare-companion1  |     conn.connect()
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/connection.py", line 419, in connect
cloudflare-companion1  |     self.sock = ssl_wrap_socket(
cloudflare-companion1  |                 ^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/util/ssl.py", line 458, in ssl_wrap_socket
cloudflare-companion1  |     ssl_sock = ssl_wrap_socket_impl(
cloudflare-companion1  |                ^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/util/ssl.py", line 502, in _ssl_wrap_socket_impl
cloudflare-companion1  |     return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
cloudflare-companion1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/ssl.py", line 455, in wrap_socket
cloudflare-companion1  |     return self.sslsocket_class._create(
cloudflare-companion1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/ssl.py", line 1041, in _create
cloudflare-companion1  |     self.do_handshake()
cloudflare-companion1  |   File "/usr/lib/python3.12/ssl.py", line 1319, in do_handshake
cloudflare-companion1  |     self._sslobj.do_handshake()
cloudflare-companion1  | ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1000)
cloudflare-companion1  |
cloudflare-companion1  | During handling of the above exception, another exception occurred:
cloudflare-companion1  |
cloudflare-companion1  | Traceback (most recent call last):
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/requests/adapters.py", line 667, in send
cloudflare-companion1  |     resp = conn.urlopen(
cloudflare-companion1  |            ^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/connectionpool.py", line 802, in urlopen
cloudflare-companion1  |     retries = retries.increment(
cloudflare-companion1  |               ^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/urllib3/util/retry.py", line 594, in increment
cloudflare-companion1  |     raise MaxRetryError(_pool, url, error or ResponseError(cause))
cloudflare-companion1  | urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='traefik.domain.com', port=443): Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1000)')))
cloudflare-companion1  |
cloudflare-companion1  | During handling of the above exception, another exception occurred:
cloudflare-companion1  |
cloudflare-companion1  | Traceback (most recent call last):
cloudflare-companion1  |   File "/usr/sbin/cloudflare-companion", line 551, in 
cloudflare-companion1  |     sync_mappings(get_initial_mappings(traefik_included_hosts, traefik_excluded_hosts), doms)
cloudflare-companion1  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/sbin/cloudflare-companion", line 432, in get_initial_mappings
cloudflare-companion1  |     add_to_mappings(mappings, check_traefik(included_hosts, excluded_hosts))
cloudflare-companion1  |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/sbin/cloudflare-companion", line 356, in check_traefik
cloudflare-companion1  |     r = requests.get("{}/api/http/routers".format(TRAEFIK_POLL_URL))
cloudflare-companion1  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/requests/api.py", line 73, in get
cloudflare-companion1  |     return request("get", url, params=params, **kwargs)
cloudflare-companion1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/requests/api.py", line 59, in request
cloudflare-companion1  |     return session.request(method=method, url=url, **kwargs)
cloudflare-companion1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/requests/sessions.py", line 589, in request
cloudflare-companion1  |     resp = self.send(prep, **send_kwargs)
cloudflare-companion1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/requests/sessions.py", line 703, in send
cloudflare-companion1  |     r = adapter.send(request, **kwargs)
cloudflare-companion1  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cloudflare-companion1  |   File "/usr/lib/python3.12/site-packages/requests/adapters.py", line 698, in send
cloudflare-companion1  |     raise SSLError(e, request=request)
cloudflare-companion1  | requests.exceptions.SSLError: HTTPSConnectionPool(host='traefik.domain.com', port=443): Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1000)')))
@kazesaurav - Unless you've obfuscated the logs, it looks like you haven't set the ROOT_DOMAIN correctly - it's trying to connect to "traefik.domain.com"
@kazesaurav - Unless you've obfuscated the logs, it looks like you haven't set the ROOT_DOMAIN correctly - it's trying to connect to "traefik.domain.com"
It's obfuscated.
It's obfuscated.
In that case, it looks like there's an issue with verifying your SSL certificate for that domain, I'd check that the certificate is being provisioned properly by Traefik
@mattdy got the SSL working, but companion is failing to authenticate with cloudflare:
2025-03-06.01:00:14 [STARTING] ** [traefik-cloudflare-companion] [6] Starting Traefik Cloudflare Companion
2025-03-06T01:00:14+0530 INFO | Found Service ID: {obfuscate} with Hostname webtop.{obfuscate}
Traceback (most recent call last):
File "/usr/sbin/cloudflare-companion", line 551, in
sync_mappings(get_initial_mappings(traefik_included_hosts, traefik_excluded_hosts), doms)
File "/usr/sbin/cloudflare-companion", line 405, in sync_mappings
if point_domain(k, domain_infos):
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/sbin/cloudflare-companion", line 177, in point_domain
records = cf.zones.dns_records.get(domain_info['zone_id'], params={u'name': name})
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/site-packages/CloudFlare/cloudflare.py", line 747, in get
raise CloudFlareAPIError(e=e) from None
CloudFlare.exceptions.CloudFlareAPIError: Unable to authenticate request
2025-03-06.01:00:16 [STARTING] ** [traefik-cloudflare-companion] [7] Starting Traefik Cloudflare Companion
2025-03-06T01:00:16+0530 INFO | Found Service ID: {obfuscate} with Hostname webtop.{obfuscate}
Traceback (most recent call last):
File "/usr/sbin/cloudflare-companion", line 551, in
sync_mappings(get_initial_mappings(traefik_included_hosts, traefik_excluded_hosts), doms)
File "/usr/sbin/cloudflare-companion", line 405, in sync_mappings
if point_domain(k, domain_infos):
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/sbin/cloudflare-companion", line 177, in point_domain
records = cf.zones.dns_records.get(domain_info['zone_id'], params={u'name': name})
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/site-packages/CloudFlare/cloudflare.py", line 747, in get
raise CloudFlareAPIError(e=e) from None
CloudFlare.exceptions.CloudFlareAPIError: Unable to authenticate request
My config for companion app:
cloudflare-companion:
image: ghcr.io/tiredofit/docker-traefik-cloudflare-companion:latest
container_name: cloudflare-companion2
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# - ./cf-token:/run/secrets/cf-token:ro
environment:
- TIMEZONE=Asia/Kolkata
- LOG_TYPE=CONSOLE
- LOG_LEVEL=INFO
- TRAEFIK_VERSION=2
- CF_EMAIL={obfuscate}
- CF_TOKEN={obfuscate}
- RC_TYPE=CNAME
- CF_DNS_API_TOKEN={obfuscate}
- TARGET_DOMAIN=htb.in.net
- REFRESH_ENTRIES=TRUE
- DOCKER_SWARM_MODE=FALSE
- ENABLE_TRAEFIK_POLL=TRUE
- TRAEFIK_POLL_URL=https://traefik.${ROOT_DOMAIN}/api
- TRAEFIK_FILTER_LABEL=traefik.constraint
- TRAEFIK_FILTER=proxy-public
- DOMAIN1={obfuscate}
- DOMAIN1_ZONE_ID={obfuscate}
- DOMAIN1_PROXIED=TRUE
restart: always
networks:
- internal
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))
I had so many issues with acme that I just disabled it and only manage DNS (direct CNAME) records via cloudflare-companion and left SSL to be sorted out by Cloudflare but allowing https through the tunnel (using no TLSVerify and 443 on CF tunnel path | CF SSL/TLS: Full (strict) and edge certificates: 'always use HTTPS' & 'automatic HTTPS rewrites' set to true.)
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))I had so many issues with acme that I just disabled it and only manage DNS (direct CNAME) records via cloudflare-companion and left SSL to be sorted out by Cloudflare but allowing https through the tunnel (using no TLSVerify and 443 on CF tunnel path | CF SSL/TLS: Full (strict) and edge certificates: 'always use HTTPS' & 'automatic HTTPS rewrites' set to true.)
Did you do this config only on Cloudflare portal or something on docker compose as well, if you can help on that.
@mattdy okay I see it authenticated now but seeing below error:
Max retries exceeded with url: /api/api/http/routers (Caused by SSLError(SSLError(1, '[SSL: TLSV1_UNRECOGNIZED_NAME] tlsv1 unrecognized name (_ssl.c:1000)')))
Maybe try without /api at the end of TRAEFIK_POLL_URL? Other than that, I'm afraid I don't know!
Hello. Thank you for this write up. Port forwarding is not an option. Cloudflare tunnel is. I've mentioned to got this setup and an almost ready to convert over from nginx proxy manager. I have question. I have 2 domains I am wanting to use use. How would I setup another tunnel in docker? I know I will need to create another tunnel id in cloudflare but am unsure what I need to edit in docker compose file.
I know I will need to create another tunnel id in cloudflare but am unsure what I need to edit in docker compose file.
You should be able to create another instance of the cloudflared-tunnel container if that's your desired approach, although I don't think there's anything stopping you running both domains over the same tunnel.
UPDATE: Got it to work using application routes and using origin TLS setting.
I have a few self hosted apps and I have my tunnel UP, but I'm stuck on routing the tunnel to each one of my Traefik routers since the ports are not exposed other than to the docker containers.
example I have status.mydomain.com running Uptime Kuma. Traefik handles the hand off of that service. But how to I set up the tunnel to use it?
I think this seems to be the missing piece for me as well, I know I have to be missing a config, or setup on the cloudflare side, I have a healthy tunnel, I just have not been able to figure out how to setup the "Published application routes" section so I believe that's why the cloudflare-companion app will not start.
Published application routes - Basic Information:
Published hostname = *.mydomain.com
Path = *
Service = https//:container_name
Origin configurations = 0
cloudflare-companion log: # This just scrolls until app stopped
2025-10-11.23:58:20 [STARTING] ** [traefik-cloudflare-companion] [32] Starting Traefik Cloudflare Companion
Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/requests/models.py", line 974, in json
    return complexjson.loads(self.text, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/decoder.py", line 338, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/decoder.py", line 356, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/sbin/cloudflare-companion", line 545, in <module>
    sync_mappings(get_initial_mappings(traefik_included_hosts, traefik_excluded_hosts), doms)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/sbin/cloudflare-companion", line 426, in get_initial_mappings
    add_to_mappings(mappings, check_traefik(included_hosts, excluded_hosts))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/sbin/cloudflare-companion", line 352, in check_traefik
    for router in r.json():
                  ^^^^^^^^
  File "/usr/lib/python3.12/site-packages/requests/models.py", line 978, in json
    raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
TEST Application.yml
services:
  # Overseerr - Media Requests and Discovery for Plex
  overseerr:
    image: lscr.io/linuxserver/overseerr:latest
    container_name: overseerr
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped
    profiles: ["apps", "all"]
    networks:
      - default
    ports:
      - "$OVERSEERR_PORT:5055"
    volumes:
      - $DOCKERDIR/appdata/overseerr:/config
    environment:
      PUID: $PUID
      PGID: $PGID
      TZ: $TZ
      traefik.constraint: proxy-public
Test Traefik Rule:
http:
  routers:
    overseerr-rtr-bypass:
      rule: "Host(`overseerr.{{env "DOMAINNAME_1"}}`) && Header(`traefik-auth-bypass-key`, `{{env "TRAEFIK_AUTH_BYPASS_KEY" }}`)"
      entryPoints:
        - websecure-external
        - websecure-internal
      middlewares:
        - chain-no-auth
      service: overseerr-svc
      priority: 100
    overseerr-rtr:
      rule: "Host(`overseerr.{{env "DOMAINNAME_1"}}`)" 
      entryPoints:
        - websecure-external
        - websecure-internal
      middlewares:
        - chain-authelia
      service: overseerr-svc
      traefik.constraint: public-proxy
      priority: 99
      tls:
        certResolver: dns-cloudflare
        options: tls-opts@file
  services:
    overseerr-svc:
      loadBalancer:
        servers:
          - url: "http://10.10.0.55:5055" # http://IP-ADDRESS:PORT
TEST cloudflare-companion.yml
services:
  cloudflare-companion:
    container_name: cloudflare-companion
    image: tiredofit/traefik-cloudflare-companion:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - $DOCKERDIR/logs/$HOSTNAME/cloudflare-companion:/logs
    networks:
      - socket_proxy
    #security_opt:
    #  - no-new-privileges:true
    restart: always
    profiles: ["apps", "all"]
    deploy:
      placement:
        constraints:
          - node.role == manager
    environment:
      - TIMEZONE=$TZ
      - LOG_TYPE=BOTH
      - LOG_LEVEL=INFO
      - TRAEFIK_VERSION=2
      - RC_TYPE=CNAME
      - TARGET_DOMAIN=host.mydomain.com
      - REFRESH_ENTRIES=TRUE
      - ENABLE_TRAEFIK_POLL=TRUE
      - TRAEFIK_POLL_URL=https://traefik.$DOMAINNAME_1/api
      - TRAEFIK_FILTER_LABEL=traefik.constraint
      - TRAEFIK_FILTER=proxy-public
      # - DOCKER_HOST=tcp://socket-proxy:2375
      - TRAEFIK_FILTER=proxy-public
      - DOMAIN1=$DOMAINNAME_1
      - DOMAIN1_ZONE_ID=$CLOUDFLARE_ZONE_ID
      - DOMAIN1_PROXIED=TRUE
      - CF_TOKEN=qwelrfhjerfjpewifjpewijfjedfsdfsdfwfrfj 
      - DEFAULT_TTL=1
      - ENABLE_TRAEFIK_POLL=TRUE
Thanks for the writeup. I've been struggling with acme timeout/ cloudflare propagation issues and it looks like you've got a fix in here. I'm just adapting / integrating your (wow, comprehensive) solution into what I've got but noticed one small item [TUNNEL_TOKEN] is missing from your env file.