Cloudflare Tunnels
Using cloudflared to expose homelab services to the internet without opening any firewall ports. Free, reliable, and integrates cleanly with Cloudflare Access for an auth layer.
Status
live
Difficulty
beginner
Time Required
20m
Last Tested
2026-06-01
What It Does
cloudflared runs on your server, creates an outbound connection to Cloudflare's edge, and routes traffic for configured hostnames through that connection. Your server needs zero inbound ports open — not even 80 or 443.
It's free, works behind CGNAT and strict firewalls, and SSL is handled automatically. For most homelab exposure use cases it's the quickest path from service to public URL.
Installing cloudflared
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared jammy main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
Authenticating
cloudflared tunnel login
A browser window opens. Select your domain. cloudflared saves a cert to ~/.cloudflared/.
Creating a Tunnel
cloudflared tunnel create homelab
Note the tunnel ID in the output. cloudflared writes a credentials JSON to ~/.cloudflared/.
Config File
Create ~/.cloudflared/config.yml:
tunnel: <your-tunnel-id>
credentials-file: /root/.cloudflared/<tunnel-id>.json
ingress:
- hostname: app.yourdomain.com
service: http://localhost:8080
- hostname: notes.yourdomain.com
service: http://localhost:3000
- service: http_status:404
The catch-all entry at the bottom is required — cloudflared returns 404 for any hostname that doesn't match.
Creating DNS Records
cloudflared tunnel route dns homelab app.yourdomain.com
cloudflared tunnel route dns homelab notes.yourdomain.com
This creates CNAME records pointing to the tunnel endpoint in Cloudflare DNS.
Running as a Service
sudo cloudflared service install
sudo systemctl start cloudflared
sudo systemctl enable cloudflared
Docker Version
If you prefer keeping it in a container:
services:
cloudflared:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=your_tunnel_token
restart: unless-stopped
The token-based approach is cleaner for Docker — no credential file mounts needed. Get the token from the Cloudflare Zero Trust dashboard under Tunnels.
Adding Auth with Cloudflare Access
For services I want protected behind a login (Grafana, internal dashboards, anything without solid built-in auth):
- Zero Trust → Access → Applications → Add
- Select the hostname
- Configure a policy — email OTP, GitHub, Google, etc.
Users hit the service URL, get the Cloudflare Access login page, authenticate, and then land on the app. Clean layer of auth without touching the app itself.
When I Use This vs Pangolin
Cloudflare Tunnels is my default for services I want publicly accessible and don't mind routing through Cloudflare. For anything where I want the full infrastructure chain under my own control — or when I need access management features that Cloudflare Access doesn't cover — I use Pangolin instead.