Table of contents
- Cloudflare Tunnel
- Tunnel 1 — cloudflared.service (system, file-configured)
- Tunnel 2 — cloudflared-thelinuxcast (Docker, token-auth)
- Adding a new hostname (Tunnel 1)
- DNS setup
- Method 1 — automatic (recommended)
- Method 2 — manual (Cloudflare dashboard)
- Apex domain vs subdomain
- Verifying DNS
- Removing a hostname
- One tunnel, many zones
- Why two tunnels?
- Rotating credentials
Cloudflare Tunnel
Two independent tunnels run on the host. Both initiate outbound TLS to Cloudflare — no router port-forwarding, no public IP exposed.
Tunnel 1 — cloudflared.service (system, file-configured)
Runs as a systemd unit. Ingress is in /etc/cloudflared/config.yml, credentials in /root/.cloudflared/<TUNNEL_UUID>.json.
tunnel: <TUNNEL_UUID>
credentials-file: /root/.cloudflared/<TUNNEL_UUID>.json
ingress:
- hostname: lab.justaguylinux.com
service: http://localhost:80
- hostname: justaguylinux.com
service: http://localhost:8080
- hostname: www.justaguylinux.com
service: http://localhost:8080
- hostname: butterbian.org
service: http://localhost:8082
- hostname: www.butterbian.org
service: http://localhost:8082
- hostname: files.grifmail.cloud
service: http://localhost:8083
- service: http_status:404
Reload after edits:
sudo systemctl restart cloudflared
sudo systemctl status cloudflared
sudo journalctl -u cloudflared -f # tail logs
DNS for a new hostname:
sudo cloudflared tunnel route dns <TUNNEL_UUID> myservice.justaguylinux.com
The catch-all http_status:404 is required as the last rule — Cloudflare rejects configs without it.
Tunnel 2 — cloudflared-thelinuxcast (Docker, token-auth)
Defined in /opt/forgejo/docker-compose.yml. No on-disk ingress file — ingress is configured in the Cloudflare dashboard for the tunnel. Token is the entire config:
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared-thelinuxcast
command: tunnel --no-autoupdate run --token <REDACTED>
restart: unless-stopped
network_mode: host
network_mode: host lets it reach localhost:3000 (Forgejo) and any other host port without compose networking gymnastics.
Hostnames it serves (set in the Cloudflare dashboard, not on disk):
| Hostname | Local port | Service |
|---|---|---|
git.thelinuxcast.org |
:3000 |
Forgejo web |
To inspect or change: dashboard → Zero Trust → Networks → Tunnels → cloudflared-thelinuxcast → Public Hostnames.
Adding a new hostname (Tunnel 1)
- Pick a local port (e.g.
:9000). - Append a stanza before the
http_status:404catch-all in/etc/cloudflared/config.yml. - Create the DNS record (see next section).
sudo systemctl restart cloudflared- Verify:
curl -sI https://myservice.justaguylinux.comshould return your origin's status, not Cloudflare's530/1033.
DNS setup
Cloudflare Tunnel works by mapping a hostname → a CNAME record pointing at <TUNNEL_UUID>.cfargotunnel.com, with Cloudflare's proxy turned on (orange cloud). The proxy is what makes Cloudflare receive the request and route it down the tunnel — without proxying, the CNAME would just resolve to a Cloudflare-internal hostname that doesn't answer publicly.
Method 1 — automatic (recommended)
cloudflared writes the DNS record for you:
sudo cloudflared tunnel route dns <TUNNEL_UUID> myservice.justaguylinux.com
What this does:
- Creates a CNAME
myservice.justaguylinux.com → <TUNNEL_UUID>.cfargotunnel.com - Sets it to Proxied (orange cloud) automatically
- Fails if a record with the same name already exists (use
--overwrite-dnsto replace it)
Use the tunnel's UUID, not its name. Find it with:
sudo cloudflared tunnel list
This requires cloudflared to be authenticated to the Cloudflare account that owns the zone (justaguylinux.com). For the system tunnel (Tunnel 1), that auth is already in /root/.cloudflared/cert.pem. For Tunnel 2 (cloudflared-thelinuxcast), the token-auth tunnel doesn't have a cert.pem — DNS for that tunnel must be created via the dashboard (Method 2).
Method 2 — manual (Cloudflare dashboard)
For token-auth tunnels (Tunnel 2) or when you want explicit control:
- Cloudflare dashboard → select the zone (e.g.
thelinuxcast.org). - DNS → Records → Add record:
- Type:
CNAME - Name: the subdomain (e.g.
git) or@for the apex - Target:
<TUNNEL_UUID>.cfargotunnel.com - Proxy status: Proxied (orange cloud) — required
- TTL: Auto
- Type:
- Save.
Or via the Zero Trust dashboard:
- Zero Trust → Networks → Tunnels → select tunnel → Public Hostnames → Add a public hostname.
- Fill in subdomain + domain + service URL (e.g.
http://localhost:3000). - The DNS record is created automatically; ingress is stored in Cloudflare (only relevant for token-auth tunnels — file-configured tunnels still read from
/etc/cloudflared/config.yml).
Apex domain vs subdomain
- Subdomain (e.g.
lab.justaguylinux.com) — straightforward CNAME. Works exactly as above. - Apex (e.g.
justaguylinux.com,butterbian.org) — DNS doesn't normally allow CNAME at the zone root, but Cloudflare implements CNAME flattening: a proxied CNAME at@is resolved server-side and returned as A/AAAA records. So the samecloudflared tunnel route dnscommand works for the apex on Cloudflare-hosted zones. No special handling needed. www— needs its own record. Bothjustaguylinux.comandwww.justaguylinux.comare configured here, each with its own CNAME → tunnel. The ingress block inconfig.ymllists both hostnames so traffic to either is served identically.
Verifying DNS
dig +short myservice.justaguylinux.com # returns Cloudflare anycast IPs (104.21.x / 172.67.x)
dig +short CNAME myservice.justaguylinux.com # may be empty — CF flattens proxied records at the edge
curl -sI https://myservice.justaguylinux.com # 200/302/etc from your origin = working
# 530/1033 = tunnel down or hostname not in ingress
# 522/523 = origin unreachable from cloudflared
If dig returns Cloudflare IPs but curl returns 1033, the DNS is fine — the issue is in config.yml (hostname missing or typo) or cloudflared isn't running.
Removing a hostname
- Remove the stanza from
/etc/cloudflared/config.yml. sudo systemctl restart cloudflared.- Delete the DNS record in the dashboard (or
cloudflared tunnel route dns --overwrite-dnsis one-way; deletion is dashboard-only).
One tunnel, many zones
A single tunnel can serve hostnames across multiple zones in the same Cloudflare account — Tunnel 1 serves justaguylinux.com, butterbian.org, and grifmail.cloud simultaneously. Each zone needs its own DNS record pointing at the same <TUNNEL_UUID>.cfargotunnel.com. The ingress block routes by hostname.
Why two tunnels?
Tunnel 1 belongs to my Cloudflare account, Tunnel 2 belongs to thelinuxcast's. Two separate tunnels means each of us manages our own DNS in our own dashboard without sharing logins.
Rotating credentials
Tunnel 1 credentials JSON is at /root/.cloudflared/<TUNNEL_UUID>.json. To rotate:
sudo cloudflared tunnel rotate <TUNNEL_UUID> # regenerates the credentials file
sudo systemctl restart cloudflared
Tunnel 2 token is rotated from the Cloudflare dashboard; the new token must be pasted into /opt/forgejo/docker-compose.yml and the container recreated:
cd /opt/forgejo && docker compose up -d cloudflared
Navigation
Quick Links
Projects
Server (discourse)
- Host
- Cloudflare Tunnel
- Tailscale
- SFTP
- Discourse
- Forgejo
- Filedrop
- Filebrowser
- Landing Sites
- Onboarding
- Backups
- Recovery
Window Managers
Homelab
Docker
Software
Thoughts
Archived
Connect
Butterbian — my Debian 13 distro · butterrepo — community APT repo · butterknife — TUI installer
justaguylinux.com · Butterforge · Butter Lab · The Churn · YouTube · Mastodon · Links · Contact · Support on Buy Me a Coffee
"Not an expert. Just A Guy." — Licensed CC BY-SA 4.0 · © 2026 JustAGuy Linux