Setting Up Cloudflare Tunnels with Pangolin: A Complete Guide
Pangolin is a powerful self-hosted tunneled reverse proxy management server, but what if you want to leverage Cloudflare’s global network alongside or instead of WireGuard tunnels? This guide walks you through integrating Pangolin with Cloudflare Zero Trust tunnels using the hhftechnology/pangolin-cloudflare-tunnel
bridge.
Why Use Cloudflare Tunnels with Pangolin?
This integration provides several benefits:
- Expose Pangolin-managed services through Cloudflare’s global network
- Take advantage of Cloudflare’s DDoS protection and caching capabilities
- No need to open ports on your router
- Works even behind CGNAT
- Provides an alternative remote access method alongside Pangolin’s WireGuard tunnels
Prerequisites
Before we begin, you’ll need:
- A Cloudflare account with a domain
- A Linux server/Unraid (local)
- Docker and Docker Compose installed
- Basic familiarity with terminal commands
Step 1: Set Up Your Cloudflare Tunnel
First, we need to create a Cloudflare tunnel and gather the necessary credentials:
Creating a Cloudflare Tunnel
- Log in to the Cloudflare Zero Trust Dashboard
- Navigate to Access → Tunnels
- Click “Create a tunnel”
- Give your tunnel a name (e.g., “Pangolin”)
- Click “Save tunnel”
- On the next screen, you’ll see installation instructions - copy and save the tunnel token (it looks like
eyJhIjoiMTYzNTI4ZDVkY2I...
)
Getting Required Cloudflare Credentials
You’ll need several pieces of information from Cloudflare:
-
API Token:
- Go to Cloudflare Dashboard → Profile → API Tokens
- Click “Create Token”
- Use the “Edit zone DNS” template and add these permissions:
- Account → Cloudflare Tunnel → Edit
- Account → Zero Trust → Edit
- User → User Details → Read
- Zone → DNS → Edit
- Select your domain under “Zone Resources”
- Create the token and copy it
-
Account ID:
- Found in the URL when you’re in the Cloudflare dashboard:
https://dash.cloudflare.com/
{account-id}
- Found in the URL when you’re in the Cloudflare dashboard:
-
Tunnel ID:
- Found in the URL when viewing your tunnel details:
https://dash.teams.cloudflare.com/{account-id}/access/tunnels/
{tunnel-id}
- Found in the URL when viewing your tunnel details:
-
Zone ID:
- Go to your domain overview page
- Look in the right sidebar under “API” section
Step 2: Install Pangolin Without Gerbil
Since we’ll be using Cloudflare tunnels instead of Gerbil’s WireGuard tunnels, we’ll install Pangolin without the Gerbil component:
Using the Installer
./installer
=== Basic Configuration ===
Enter your base domain (no subdomain e.g. example.com): yourdomain.com
Enter the domain for the Pangolin dashboard (default: pangolin.yourdomain.com): pangolin.yourdomain.com
Enter email for Let's Encrypt certificates: your@email.com
Do you want to use Gerbil to allow tunned connections (yes/no) (default: yes): no
=== Admin User Configuration ===
Enter admin user email (default: admin@yourdomain.com): your@email.com
Create admin user password: [enter your password]
Confirm admin user password: [confirm your password]
=== Security Settings ===
Disable signup without invite (yes/no) (default: yes): yes
Disable users from creating organizations (yes/no) (default: no): no
=== Email Configuration ===
Enable email functionality (yes/no) (default: no): no
=== Starting installation ===
Would you like to install and start the containers? (yes/no) (default: yes): no
By selecting “no” for the last prompt, we’ll pause before starting the containers so we can modify the configuration files.
Step 3: Configure Traefik for Cloudflare Integration
Modify the docker-compose.yml File
Open the docker-compose.yml
file in the installation directory and make the following changes:
- Add Cloudflare DNS API token to the Traefik service:
traefik:
image: traefik:v3.3.3
container_name: traefik
restart: unless-stopped
# Add this environment section:
environment:
- CLOUDFLARE_DNS_API_TOKEN=YourCloudflareAPIToken # Replace with your actual token
# Rest of Traefik config...
- Add the cloudflared service:
cloudflared:
image: cloudflare/cloudflared:2025.4.0
container_name: cloudflared
restart: unless-stopped
command:
- tunnel
- --no-autoupdate
- run
- --token=YourCloudflaredTunnelToken # Replace with your actual tunnel token
networks:
- pangolin_network # Or whatever your network is named
depends_on:
- traefik
- Add the pangolin-cloudflare-tunnel service:
traefik-cloudflare-tunnel:
image: "hhftechnology/pangolin-cloudflare-tunnel:latest"
container_name: pangolin-cloudflare-tunnel
restart: unless-stopped
environment:
- CLOUDFLARE_API_TOKEN=YourCloudflareAPIToken # Replace with your actual API token
- CLOUDFLARE_ACCOUNT_ID=YourCloudflareAccountID # Replace with your account ID
- CLOUDFLARE_TUNNEL_ID=YourCloudflareTunnelID # Replace with your tunnel ID
- CLOUDFLARE_ZONE_ID=YourCloudflareZoneID # Replace with your zone ID
- TRAEFIK_SERVICE_ENDPOINT=https://traefik:443
- TRAEFIK_API_ENDPOINT=http://traefik:8080
- TRAEFIK_ENTRYPOINTS=web,websecure
- POLL_INTERVAL=10s
- SKIP_TLS_ROUTES=false
- LOG_LEVEL=debug
networks:
- pangolin_network
depends_on:
- traefik
- cloudflared
Update Traefik Configuration
Next, modify the Traefik configuration to work better with Cloudflare. Edit the file at config/traefik/traefik_config.yml
:
api:
insecure: true
dashboard: true
providers:
http:
endpoint: "http://pangolin:3001/api/v1/traefik-config"
pollInterval: "5s"
file:
filename: "/etc/traefik/dynamic_config.yml"
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "v1.0.0"
log:
level: "INFO"
format: "json"
accessLog:
filePath: "/var/log/traefik/access.log"
format: json
filters:
statusCodes:
- "200-299"
- "400-499"
- "500-599"
retryAttempts: true
minDuration: "100ms"
bufferingSize: 100
fields:
defaultMode: drop
names:
ClientAddr: keep
ClientHost: keep
RequestMethod: keep
RequestPath: keep
RequestProtocol: keep
DownstreamStatus: keep
DownstreamContentSize: keep
Duration: keep
ServiceName: keep
StartUTC: keep
TLSVersion: keep
TLSCipher: keep
RetryAttempts: keep
headers:
defaultMode: drop
names:
User-Agent: keep
X-Real-Ip: keep
X-Forwarded-For: keep
X-Forwarded-Proto: keep
Content-Type: keep
Authorization: redact
Cookie: redact
x-trusted-ips: &trustedIPs
# Internal
- 127.0.0.1/32
- 172.16.0.0/12
- 192.168.0.1/24
- 10.0.0.0/8
# Cloudflare V4
- 173.245.48.0/20
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 141.101.64.0/18
- 108.162.192.0/18
- 190.93.240.0/20
- 188.114.96.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 162.158.0.0/15
- 104.16.0.0/13
- 104.24.0.0/14
- 172.64.0.0/13
- 131.0.72.0/22
# Cloudflare V6
- 2400:cb00::/32
- 2606:4700::/32
- 2803:f800::/32
- 2405:b500::/32
- 2405:8100::/32
- 2a06:98c0::/29
- 2c0f:f248::/32
certificatesResolvers:
letsencrypt:
acme:
email: "your@email.com" # Replace with your email
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
# Use DNS challenge for wildcard certs
dnsChallenge:
provider: cloudflare
delayBeforeCheck: 30
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
entryPoints:
web:
address: ":80"
forwardedHeaders:
trustedIPs: *trustedIPs
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: "30m"
http:
tls:
certResolver: letsencrypt
forwardedHeaders:
trustedIPs: *trustedIPs
serversTransport:
insecureSkipVerify: true
Update dynamic_config.yml
Make sure your Pangolin dashboard URL is using the websecure
entrypoint in config/traefik/dynamic_config.yml
:
http:
middlewares:
redirect-to-https:
redirectScheme:
scheme: https
routers:
# HTTP to HTTPS redirect router
main-app-router-redirect:
rule: "Host(`pangolin.yourdomain.com`)"
service: next-service
entryPoints:
- websecure
middlewares:
- redirect-to-https
# Next.js router (handles everything except API and WebSocket paths)
next-router:
rule: "Host(`pangolin.yourdomain.com`) && !PathPrefix(`/api/v1`)"
service: next-service
entryPoints:
- websecure
tls:
certResolver: letsencrypt
# API router (handles /api/v1 paths)
api-router:
rule: "Host(`pangolin.yourdomain.com`) && PathPrefix(`/api/v1`)"
service: api-service
entryPoints:
- websecure
tls:
certResolver: letsencrypt
# WebSocket router
ws-router:
rule: "Host(`pangolin.yourdomain.com`)"
service: api-service
entryPoints:
- websecure
tls:
certResolver: letsencrypt
services:
next-service:
loadBalancer:
servers:
- url: "http://pangolin:3002"
api-service:
loadBalancer:
servers:
- url: "http://pangolin:3000"
Step 4: Start the Stack
Now that all configurations are in place, start the Docker Compose stack:
docker compose up -d
Check if everything is running properly:
docker compose logs -f
You should see output showing:
- Pangolin starting up
- Cloudflared connecting to Cloudflare’s network
- The tunnel-bridge detecting changes and adding domains to the tunnel configuration
root@ip-10-0-8-134:~# docker compose up -d
[+] Running 37/37
✔ pangolin Pulled 29.4s
✔ 8cc209e5911c Pull complete 7.3s
✔ d7a069a788e0 Pull complete 7.4s
✔ 42ec265e2954 Pull complete 7.5s
✔ 0566c399f0e8 Pull complete 7.5s
✔ 8bed267b10d4 Pull complete 7.6s
✔ d353efd21899 Pull complete 7.7s
✔ 4beb21b31e15 Pull complete 25.7s
✔ a45febfde3f7 Pull complete 27.0s
✔ 3c86004ec028 Pull complete 27.1s
✔ 6db75e12fa7d Pull complete 27.2s
✔ 1b20ee3101ac Pull complete 27.2s
✔ fe516a2badaf Pull complete 27.3s
✔ 14c13cc84826 Pull complete 27.3s
✔ traefik-cloudflare-tunnel Pulled 6.1s
✔ 267eba41f855 Pull complete 3.5s
✔ ddedb05bbfa2 Pull complete 4.1s
✔ traefik Pulled 5.5s
✔ f18232174bc9 Pull complete 0.8s
✔ 660f734330e4 Pull complete 1.1s
✔ 0af947afec58 Pull complete 3.4s
✔ b6438ad7918f Pull complete 3.5s
✔ cloudflared Pulled 6.6s
✔ 51c1b6699f43 Pull complete 1.1s
✔ 2e4cf50eeb92 Pull complete 1.4s
✔ 4e9f20d26c87 Pull complete 1.9s
✔ 0f8b424aa0b9 Pull complete 1.9s
✔ d557676654e5 Pull complete 2.0s
✔ d82bc7a76a83 Pull complete 2.0s
✔ d858cbc252ad Pull complete 2.1s
✔ 1069fc2daed1 Pull complete 2.3s
✔ b40161cd83fc Pull complete 2.4s
✔ 3f4e2c586348 Pull complete 2.5s
✔ 80a8c047508a Pull complete 2.8s
✔ e95e5abe292b Pull complete 3.4s
✔ 04155d74e8b4 Pull complete 3.5s
✔ 7c2744d8f2aa Pull complete 4.7s
[+] Running 6/6
✔ Network pangolin_network Created 0.1s
✔ Network pangolin_default Created 0.1s
✔ Container pangolin Healthy 7.2s
✔ Container traefik Started 7.3s
✔ Container cloudflared Started 7.6s
✔ Container pangolin-cloudflare-tunnel Started
root@ip-10-0-8-134:~# docker compose logs -f
cloudflared | 2025-04-10T09:48:52Z INF Starting tunnel tunnelID=c8c6c73b-22a4-4b67-ad91-f1acce880d1f
cloudflared | 2025-04-10T09:48:52Z INF Version 2025.4.0 (Checksum 7bec9d5632a9983230f7a30f287ce002d7944ba6b2eb9cf4252664e19d9b1c94)
cloudflared | 2025-04-10T09:48:52Z INF GOOS: linux, GOVersion: go1.22.5-devel-cf, GoArch: amd64
cloudflared | 2025-04-10T09:48:52Z INF Settings: map[no-autoupdate:true token:*****]
cloudflared | 2025-04-10T09:48:52Z INF Generated Connector ID: d9ef60fe-f2a5-48b1-b7ef-ab5bc756acca
pangolin |
traefik | {"level":"warn","time":"2025-04-10T09:48:53Z","message":"delayBeforeCheck is now deprecated, please use propagation.delayBeforeChecks instead."}
pangolin | > @fosrl/pangolin@0.0.0 start
cloudflared | 2025-04-10T09:48:52Z INF Initial protocol quic
pangolin | > NODE_OPTIONS=--enable-source-maps NODE_ENV=development ENVIRONMENT=prod sh -c 'node dist/migrations.mjs && node dist/server.mjs'
traefik | {"level":"info","version":"3.3.3","time":"2025-04-10T09:48:53Z","message":"Traefik version 3.3.3 built on 2025-01-31T14:55:01Z"}
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use 172.18.0.4 as source for IPv4
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use ::1 in zone lo as source for IPv6
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://doc.traefik.io/traefik/contributing/data-collection/\n"}
traefik | {"level":"info","plugins":["badger"],"time":"2025-04-10T09:48:53Z","message":"Loading plugins..."}
traefik | {"level":"info","plugins":["badger"],"time":"2025-04-10T09:48:53Z","message":"Plugins loaded."}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider aggregator *aggregator.ProviderAggregator"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *file.Provider"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *traefik.Provider"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *http.Provider"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *acme.ChallengeTLSALPN"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *acme.Provider"}
traefik | {"level":"info","providerName":"letsencrypt.acme","acmeCA":"https://acme-v02.api.letsencrypt.org/directory","time":"2025-04-10T09:48:53Z","message":"Testing certificate renew..."}
pangolin |
pangolin | Starting migrations from version 1.1.0
pangolin | Migrations to run:
pangolin | All migrations completed successfully
pangolin | 2025-04-10T09:48:48.543Z [warn]: Email SMTP configuration is missing. Emails will not be sent.
pangolin | 2025-04-10T09:48:48.873Z [info]: Server admin (discourse@hhf.technology) already exists
pangolin | 2025-04-10T09:48:49.879Z [info]: API server is running on http://localhost:3000
pangolin | 2025-04-10T09:48:49.879Z [info]: Internal server is running on http://localhost:3001
pangolin | 2025-04-10T09:48:51.077Z [info]: Next.js server is running on http://localhost:3002
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use 172.18.0.4 as source for IPv4
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use ::1 in zone lo as source for IPv6
cloudflared | 2025-04-10T09:48:52Z INF Starting metrics server on [::]:20241/metrics
cloudflared | 2025-04-10T09:48:52Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=0 event=0 ip=198.41.192.47
cloudflared | 2025/04/10 09:48:52 failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.
cloudflared | 2025-04-10T09:48:53Z INF Registered tunnel connection connIndex=0 connection=ceec129f-56b3-45cd-98e4-c6782fcea243 event=0 ip=198.41.192.47 location=ams08 protocol=quic
cloudflared | 2025-04-10T09:48:53Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=1 event=0 ip=198.41.200.113
cloudflared | 2025-04-10T09:48:53Z INF Updated to new configuration config="{\"ingress\":[{\"hostname\":\"pangolin.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"pangolin.hhf.technology\", \"noTLSVerify\":true, \"originServerName\":\"pangolin.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"service\":\"http_status:404\"}], \"originRequest\":{}, \"warp-routing\":{\"enabled\":false}}" version=145
cloudflared | 2025-04-10T09:48:54Z INF Registered tunnel connection connIndex=1 connection=3249f4b5-a8f8-4394-838d-3f6b6cdc0d8e event=0 ip=198.41.200.113 location=arn02 protocol=quic
cloudflared | 2025-04-10T09:48:54Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=2 event=0 ip=198.41.192.67
cloudflared | 2025-04-10T09:48:54Z INF Registered tunnel connection connIndex=2 connection=989c2c47-4c7c-490c-902d-491cce88fdbc event=0 ip=198.41.192.67 location=ams08 protocol=quic
cloudflared | 2025-04-10T09:48:55Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=3 event=0 ip=198.41.200.43
cloudflared | 2025-04-10T09:48:55Z INF Registered tunnel connection connIndex=3 connection=2268e0bd-748b-4896-9cd4-b4bceaf1fbaf event=0 ip=198.41.200.43 location=arn02 protocol=quic
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Changes detected in Traefik routers"
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Adding domain to tunnel configuration" domain=pangolin.hhf.technology service="https://traefik:443"
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:04Z" level=info msg="Tunnel configuration updated successfully"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Changes detected in Traefik routers"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Adding domain to tunnel configuration" domain=wallos.hhf.technology service="https://traefik:443"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=wallos.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Adding domain to tunnel configuration" domain=pangolin.hhf.technology service="https://traefik:443"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
cloudflared | 2025-04-10T09:52:04Z INF Updated to new configuration config="{\"ingress\":[{\"hostname\":\"wallos.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"wallos.hhf.technology\", \"noTLSVerify\":true, \"originServerName\":\"wallos.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"hostname\":\"pangolin.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"pangolin.hhf.technology\", \"noTLSVerify\":true, \"originServerName\":\"pangolin.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"service\":\"http_status:404\"}], \"originRequest\":{}, \"warp-routing\":{\"enabled\":false}}" version=146
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:04Z" level=info msg="Tunnel configuration updated successfully"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:06Z" level=info msg="DNS record created successfully" domain=wallos.hhf.technology
root@ip-10-0-8-134:~# docker compose logs -f
traefik | {"level":"warn","time":"2025-04-10T09:48:53Z","message":"delayBeforeCheck is now deprecated, please use propagation.delayBeforeChecks instead."}
traefik | {"level":"info","version":"3.3.3","time":"2025-04-10T09:48:53Z","message":"Traefik version 3.3.3 built on 2025-01-31T14:55:01Z"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://doc.traefik.io/traefik/contributing/data-collection/\n"}
traefik | {"level":"info","plugins":["badger"],"time":"2025-04-10T09:48:53Z","message":"Loading plugins..."}
traefik | {"level":"info","plugins":["badger"],"time":"2025-04-10T09:48:53Z","message":"Plugins loaded."}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider aggregator *aggregator.ProviderAggregator"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *file.Provider"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *traefik.Provider"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *http.Provider"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *acme.ChallengeTLSALPN"}
traefik | {"level":"info","time":"2025-04-10T09:48:53Z","message":"Starting provider *acme.Provider"}
traefik | {"level":"info","providerName":"letsencrypt.acme","acmeCA":"https://acme-v02.api.letsencrypt.org/directory","time":"2025-04-10T09:48:53Z","message":"Testing certificate renew..."}
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Changes detected in Traefik routers"
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Adding domain to tunnel configuration" domain=pangolin.hhf.technology service="https://traefik:443"
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:49:04Z" level=info msg="Tunnel configuration updated successfully"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Changes detected in Traefik routers"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Adding domain to tunnel configuration" domain=wallos.hhf.technology service="https://traefik:443"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=wallos.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Adding domain to tunnel configuration" domain=pangolin.hhf.technology service="https://traefik:443"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:02Z" level=info msg="Skipping duplicate domain" domain=pangolin.hhf.technology
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:04Z" level=info msg="Tunnel configuration updated successfully"
pangolin-cloudflare-tunnel | time="2025-04-10T09:52:06Z" level=info msg="DNS record created successfully" domain=wallos.hhf.technology
pangolin |
pangolin | > @fosrl/pangolin@0.0.0 start
pangolin | > NODE_OPTIONS=--enable-source-maps NODE_ENV=development ENVIRONMENT=prod sh -c 'node dist/migrations.mjs && node dist/server.mjs'
pangolin |
pangolin | Starting migrations from version 1.1.0
pangolin | Migrations to run:
pangolin | All migrations completed successfully
pangolin | 2025-04-10T09:48:48.543Z [warn]: Email SMTP configuration is missing. Emails will not be sent.
pangolin | 2025-04-10T09:48:48.873Z [info]: Server admin (discourse@hhf.technology) already exists
pangolin | 2025-04-10T09:48:49.879Z [info]: API server is running on http://localhost:3000
pangolin | 2025-04-10T09:48:49.879Z [info]: Internal server is running on http://localhost:3001
pangolin | 2025-04-10T09:48:51.077Z [info]: Next.js server is running on http://localhost:3002
cloudflared | 2025-04-10T09:48:52Z INF Starting tunnel tunnelID=c8c6c73b-22a4-4b67-ad91-f1acce880d1f
cloudflared | 2025-04-10T09:48:52Z INF Version 2025.4.0 (Checksum 7bec9d5632a9983230f7a30f287ce002d7944ba6b2eb9cf4252664e19d9b1c94)
cloudflared | 2025-04-10T09:48:52Z INF GOOS: linux, GOVersion: go1.22.5-devel-cf, GoArch: amd64
cloudflared | 2025-04-10T09:48:52Z INF Settings: map[no-autoupdate:true token:*****]
cloudflared | 2025-04-10T09:48:52Z INF Generated Connector ID: d9ef60fe-f2a5-48b1-b7ef-ab5bc756acca
cloudflared | 2025-04-10T09:48:52Z INF Initial protocol quic
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use 172.18.0.4 as source for IPv4
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use ::1 in zone lo as source for IPv6
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use 172.18.0.4 as source for IPv4
cloudflared | 2025-04-10T09:48:52Z INF ICMP proxy will use ::1 in zone lo as source for IPv6
cloudflared | 2025-04-10T09:48:52Z INF Starting metrics server on [::]:20241/metrics
cloudflared | 2025-04-10T09:48:52Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=0 event=0 ip=198.41.192.47
cloudflared | 2025/04/10 09:48:52 failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.
cloudflared | 2025-04-10T09:48:53Z INF Registered tunnel connection connIndex=0 connection=ceec129f-56b3-45cd-98e4-c6782fcea243 event=0 ip=198.41.192.47 location=ams08 protocol=quic
cloudflared | 2025-04-10T09:48:53Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=1 event=0 ip=198.41.200.113
cloudflared | 2025-04-10T09:48:53Z INF Updated to new configuration config="{\"ingress\":[{\"hostname\":\"pangolin.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"pangolin.hhf.technology\", \"noTLSVerify\":true, \"originServerName\":\"pangolin.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"service\":\"http_status:404\"}], \"originRequest\":{}, \"warp-routing\":{\"enabled\":false}}" version=145
cloudflared | 2025-04-10T09:48:54Z INF Registered tunnel connection connIndex=1 connection=3249f4b5-a8f8-4394-838d-3f6b6cdc0d8e event=0 ip=198.41.200.113 location=arn02 protocol=quic
cloudflared | 2025-04-10T09:48:54Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=2 event=0 ip=198.41.192.67
cloudflared | 2025-04-10T09:48:54Z INF Registered tunnel connection connIndex=2 connection=989c2c47-4c7c-490c-902d-491cce88fdbc event=0 ip=198.41.192.67 location=ams08 protocol=quic
cloudflared | 2025-04-10T09:48:55Z INF Using [CurveID(4588) CurveID(25497) CurveP256] as curve preferences connIndex=3 event=0 ip=198.41.200.43
cloudflared | 2025-04-10T09:48:55Z INF Registered tunnel connection connIndex=3 connection=2268e0bd-748b-4896-9cd4-b4bceaf1fbaf event=0 ip=198.41.200.43 location=arn02 protocol=quic
cloudflared | 2025-04-10T09:52:04Z INF Updated to new configuration config="{\"ingress\":[{\"hostname\":\"wallos.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"wallos.hhf.technology\", \"noTLSVerify\":true, \"originServerName\":\"wallos.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"hostname\":\"pangolin.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"pangolin.hhf.technology\", \"noTLSVerify\":true, \"originServerName\":\"pangolin.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"service\":\"http_status:404\"}], \"originRequest\":{}, \"warp-routing\":{\"enabled\":false}}" version=146
cloudflared | 2025-04-10T09:55:02Z INF Updated to new configuration config="{\"ingress\":[{\"hostname\":\"wallos.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"wallos.hhf.technology\", \"noTLSVerify\":false, \"originServerName\":\"wallos.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"hostname\":\"pangolin.hhf.technology\", \"originRequest\":{\"httpHostHeader\":\"pangolin.hhf.technology\", \"noTLSVerify\":true, \"originServerName\":\"pangolin.hhf.technology\"}, \"service\":\"https://traefik:443\"}, {\"service\":\"http_status:404\"}], \"warp-routing\":{\"enabled\":false}}" version=147
traefik | {"level":"warn","time":"2025-04-10T09:58:54Z","message":"A new release of Traefik has been found: 3.3.5. Please consider updating."}
Step 5: Create a Local Site in Pangolin
- Access your Pangolin dashboard at
https://pangolin.yourdomain.com
- Log in with the admin credentials you created
- Follow the prompts to create an organization
- Go to Sites and click “Add Site”
- Choose “Local” as the connection method
- Give your site a name (e.g., “Local Services”)
- Complete the site creation
Step 6: Create a Resource and Add a Target
Now let’s set up a service that will be exposed via Cloudflare Tunnels:
- Go to Resources and click “Add Resource”
- Give your resource a name (e.g., “Wallos”)
- Choose a subdomain (e.g., “wallos”)
- Select the local site you created
- Click “Create Resource”
Once created, navigate to the Connectivity tab for your resource:
- Keep “Enable SSL” toggled on if you want HTTPS
- Add a target with:
- Method: HTTP
- IP/Hostname: Your service IP (e.g., “10.0.8.134”)
- Port: Your service port (e.g., “8282”)
- Click “Add Target” and then “Save Changes”
The pangolin-cloudflare-tunnel
container will automatically detect the new resource and:
- Update the Cloudflare Tunnel configuration
- Create DNS records pointing to your tunnel
You should see log entries like:
time="2025-04-10T09:52:02Z" level=info msg="Adding domain to tunnel configuration" domain=wallos.yourdomain.com service="https://traefik:443"
time="2025-04-10T09:52:04Z" level=info msg="Tunnel configuration updated successfully"
time="2025-04-10T09:52:06Z" level=info msg="DNS record created successfully" domain=wallos.yourdomain.com
Your service is now accessible via https://wallos.yourdomain.com
!
Example Docker Service Configuration
Here’s an example of how to set up a service (Wallos in this case) that will be accessible through your Cloudflare Tunnel:
services:
wallos:
container_name: wallos
image: bellamy/wallos:latest
ports:
- "8282:80/tcp"
environment:
TZ: 'America/Toronto'
volumes:
- './db:/var/www/html/db'
- './logos:/var/www/html/images/uploads/logos'
restart: unless-stopped
networks:
- pangolin_network # Join the same network as Pangolin
networks:
pangolin_network:
external: true # Use the existing network
name: pangolin_network
Authentication and Security
Remember that by default, Pangolin’s authentication system will protect your services. You can see in the Pangolin dashboard that a resource has “Authentication: Not Protected” if you’ve disabled SSO authentication.
For production use, it’s highly recommended to:
- Keep Pangolin SSO enabled for all resources
- Configure proper authentication for any publicly exposed service
- Consider adding additional protection using Cloudflare Access policies
Troubleshooting
DNS Records Not Updating
If DNS records aren’t being created automatically:
- Check the logs for any errors (
docker compose logs traefik-cloudflare-tunnel
) - Verify your Cloudflare API token has the correct permissions
- Make sure the zone ID matches your domain
Service Not Accessible
If your service is not accessible:
- Check if the tunnel is connected (
docker compose logs cloudflared
) - Verify that your service is running and accessible locally
- Check Traefik logs for routing issues (
docker compose logs traefik
)
Certificate Issues
If you’re having SSL certificate issues:
- Ensure your Cloudflare API token has the correct permissions
- Check that the DNS challenge is properly configured
- Verify that the
acme.json
file exists and has permissions
Conclusion
With this setup, you’ve successfully integrated Pangolin with Cloudflare Tunnels, allowing you to:
- Securely expose your internal services to the internet
- Benefit from Cloudflare’s global network and security features
- Avoid opening ports on your router
- Manage everything through Pangolin’s intuitive interface
This hybrid approach gives you the best of both worlds: Pangolin’s excellent resource management and authentication, combined with Cloudflare’s global network and security infrastructure.
Happy self-hosting!