A bridge between Traefik and Cloudflare Zero-Trust tunnels that enables Pangolin users to leverage Cloudflare's global network.
This tool synchronizes Traefik routes with Cloudflare Zero-Trust tunnels, providing an alternative or complementary tunneling option for Pangolin deployments. This integration allows you to:
- Expose Pangolin-managed services through Cloudflare's global network
- Take advantage of Cloudflare's DDoS protection and caching capabilities
- Provide an alternative remote access method alongside Pangolin's WireGuard tunnels
- NEW: Manage multiple domains across different Cloudflare zones
- NEW: Exclude specific resources from Cloudflare tunneling
- NEW: Automatic cleanup of DNS records for deleted resources
- Multi-Domain/Multi-Zone Support: Configure multiple domains across different Cloudflare zones
- Resource Exclusion: Ignore specific domains or patterns (e.g., Jellyfin for TOS compliance)
- Automatic DNS Cleanup: Automatically remove DNS records when resources are deleted
- TLS Route Filtering: Optionally skip or include TLS-enabled routes
- Multiple Entrypoints: Support for multiple Traefik entrypoints
- Automatic Synchronization: Real-time sync between Traefik and Cloudflare
- Robust Error Handling: Retry logic with exponential backoff
- Structured Logging: Comprehensive logging with configurable verbosity
When used with Pangolin:
- Pangolin manages your internal resources
- Traefik (used by Pangolin) handles the local routing
- This tool synchronizes Traefik routes to Cloudflare tunnels
- Cloudflare provides an additional layer of protection and global distribution
This creates a combination where you can use Pangolin for secure local deployment via Cloudflare tunnels for public-facing services for Unraid/NAS users without opening ports or buying a VPS.
| Environment Variable | Type | Description |
|---|---|---|
| CLOUDFLARED_TOKEN | String | Token for the cloudflared daemon. This is the token provided after creating a tunnel. |
| CLOUDFLARE_API_TOKEN | String | A valid Cloudflare API token |
| CLOUDFLARE_ACCOUNT_ID | String | Your account ID. Available in the URL at https://dash.cloudflare.com |
| CLOUDFLARE_TUNNEL_ID | String | The ID of your Cloudflare tunnel |
| TRAEFIK_API_ENDPOINT | String | The HTTP URI to Traefik's API (e.g., http://traefik:8080) |
| TRAEFIK_SERVICE_ENDPOINT | String | The HTTP URI to Traefik's web entrypoint (e.g., https://traefik:443) |
| Environment Variable | Type | Description |
|---|---|---|
| CLOUDFLARE_ZONE_ID | String | The Cloudflare zone ID of your site |
| DOMAIN_NAME | String | (Optional) The domain name used for this zone |
| Environment Variable | Type | Description |
|---|---|---|
| CLOUDFLARE_ZONE_IDS | String | Comma-separated list of Cloudflare zone IDs (e.g., zone1,zone2,zone3) |
| DOMAIN_NAMES | String | Comma-separated list of domain names matching the zones (e.g., example.com,test.com,demo.com) |
Note: The order of zone IDs must match the order of domain names.
| Environment Variable | Type | Description |
|---|---|---|
| TRAEFIK_ENTRYPOINTS | String | Comma-separated list of Traefik entrypoints (e.g., web,websecure) |
| TRAEFIK_ENTRYPOINT | String | (Legacy) Single Traefik entrypoint (e.g., web) |
| Environment Variable | Type | Default | Description |
|---|---|---|---|
| SKIP_TLS_ROUTES | Boolean | true |
Skip routes with TLS configured. Set to false to include TLS routes |
| POLL_INTERVAL | String | 10s |
Polling interval (e.g., 10s, 1m, 30s) |
| LOG_LEVEL | String | info |
Log level (debug or info) |
| IGNORE_PATTERNS | String | (empty) | Comma-separated regex patterns for domains to ignore (e.g., ^jellyfin\.,^media\.) |
| ENABLE_DNS_CLEANUP | Boolean | true |
Automatically remove DNS records for deleted resources |
The CLOUDFLARE_API_TOKEN is your API token which can be created at: https://dash.cloudflare.com/profile/api-tokens
Ensure the permissions for your Cloudflare token match the following:
- Account -> Cloudflare Tunnel -> Edit
- Account -> Zero Trust -> Edit
- User -> User Details -> Read
- Zone -> DNS -> Edit
This example shows how to integrate Cloudflare tunnels with a Pangolin deployment.
-
First, set up Pangolin according to its installation guide
-
Create an
.envfile with your Cloudflare credentials:
cd example
cp .env.example .env
vi .env- Add this service to your existing Pangolin
docker-compose.yml:
name: pangolin
services:
pangolin:
image: fosrl/pangolin:1.1.0
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "3s"
timeout: "3s"
retries: 5
networks:
- pangolin_network
traefik:
image: traefik:v3.3.3
container_name: traefik
restart: unless-stopped
ports:
- 443:443
- 80:80
- 8080:8080
depends_on:
pangolin:
condition: service_healthy
command:
- --configFile=/etc/traefik/traefik_config.yml
environment:
- CLOUDFLARE_DNS_API_TOKEN=your_dns_api_token_here
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/letsencrypt:/letsencrypt
- ./config/traefik/logs:/var/log/traefik
networks:
- pangolin_network
cloudflared:
image: cloudflare/cloudflared:2025.4.0
container_name: cloudflared
restart: unless-stopped
command:
- tunnel
- --no-autoupdate
- run
- --token=your_cloudflared_token_here
networks:
- pangolin_network
depends_on:
- traefik
traefik-cloudflare-tunnel:
image: "hhftechnology/pangolin-cloudflare-tunnel:latest"
container_name: pangolin-cloudflare-tunnel
restart: unless-stopped
environment:
# Required Configuration
- CLOUDFLARE_API_TOKEN=your_api_token_here
- CLOUDFLARE_ACCOUNT_ID=your_account_id_here
- CLOUDFLARE_TUNNEL_ID=your_tunnel_id_here
- TRAEFIK_SERVICE_ENDPOINT=https://traefik:443
- TRAEFIK_API_ENDPOINT=http://traefik:8080
- TRAEFIK_ENTRYPOINTS=web,websecure
# Multi-Zone Configuration (NEW)
- CLOUDFLARE_ZONE_IDS=zone_id_1,zone_id_2
- DOMAIN_NAMES=example.com,test.com
# Optional Configuration
- POLL_INTERVAL=10s
- SKIP_TLS_ROUTES=false
- LOG_LEVEL=debug
- ENABLE_DNS_CLEANUP=true
# Resource Exclusion (NEW) - Ignore Jellyfin and media services
- IGNORE_PATTERNS=^jellyfin\.,^media\.
networks:
- pangolin_network
depends_on:
- traefik
- cloudflared
networks:
pangolin_network:
driver: bridge
name: pangolin_network- Restart your Pangolin stack:
sudo docker compose up -d- Create resources in Pangolin as usual. Resources with the specified entrypoint will be automatically exposed through Cloudflare tunnels.
Manage multiple domains across different Cloudflare zones:
CLOUDFLARE_ZONE_IDS=zone1,zone2,zone3
DOMAIN_NAMES=example.com,test.com,demo.comExclude specific services that shouldn't use Cloudflare CDN (e.g., Jellyfin for TOS compliance):
IGNORE_PATTERNS=^jellyfin\.,^media\.,^plex\.This will exclude:
jellyfin.example.commedia.example.complex.example.com
Enable automatic DNS cleanup to remove records for deleted resources:
ENABLE_DNS_CLEANUP=trueWhen a resource is deleted from Traefik, its corresponding DNS record will be automatically removed from Cloudflare.
The application is structured with a clean, modular architecture:
.
├── main.go # Application entry point
├── internal/
│ ├── config/ # Configuration management
│ ├── traefik/ # Traefik API client and router management
│ ├── cloudflare/ # Cloudflare API client and operations
│ ├── sync/ # Synchronization orchestration
│ └── errors/ # Custom error types
└── pkg/
└── retry/ # Retry utility with exponential backoff
You can use this tool with multiple Docker hosts on the same hypervisor layer while keeping Gerbil in your setup. This allows connecting multiple LXC containers (each running Docker) to a single centralized Pangolin instance.
Adjust the polling interval based on your needs:
POLL_INTERVAL=30s # Less frequent polling (lower resource usage)
POLL_INTERVAL=5s # More frequent polling (faster updates)Enable debug logging for troubleshooting:
LOG_LEVEL=debug- Check that your
CLOUDFLARE_API_TOKENhas the correct permissions - Verify that
CLOUDFLARE_ZONE_IDSandDOMAIN_NAMESmatch correctly - Enable debug logging to see detailed error messages
If you see warnings like "no matching zone", ensure:
- The domain is a subdomain of one of your configured
DOMAIN_NAMES - The order of
CLOUDFLARE_ZONE_IDSmatchesDOMAIN_NAMES
If the IGNORE_PATTERNS aren't working:
- Check that your regex patterns are correct
- Test patterns at https://regex101.com
- Remember to escape special characters (e.g.,
\.for literal dots)
Contributions are welcome! Please feel free to submit issues or pull requests.
This project follows the MIT license.