Published on

Cloudflared Tunnel Guide

Authors

🧭 Motivation & Use Cases

Self-hosted tools like dashboards, file servers, or development UIs are great—until you need to access them from outside your local network. Opening firewall ports feels risky, dynamic IPs are annoying, and VPN setups can get complex. Reverse tunnels are a practical middle ground.

This guide covers one such tunnel solution, using cloudflared, to make a local service securely accessible via a subdomain, without requiring root privileges or port forwarding.


✨ Requirements

  • A domain managed via Cloudflare (e.g. yourdomain.com)
  • A subdomain already created in DNS (e.g. home.yourdomain.com)
  • A running local service (e.g. localhost:9000 for Portainer, Homer, etc.)
  • Basic CLI usage

Optional:

  • Dynamic DNS (if you want fallback SSH or VPN access)
  • API token if you'd rather use automation over browser login

♻️ Step 1: Clean Up Old Versions (Optional)

sudo rm -rf /usr/local/bin/cloudflared
sudo rm -rf /etc/cloudflared
sudo rm -rf ~/.cloudflared

🛠️ Step 2: Install cloudflared (User-level)

mkdir -p ~/bin
cd ~/bin
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared

Add to your PATH if needed:

echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

Test:

cloudflared --version

🔐 Step 3: Login with Cloudflare

cloudflared tunnel login

This opens a browser window to link your domain account.

Alternatively, use an API token:

export CLOUDFLARE_API_TOKEN="your-token-here"
cloudflared tunnel login

Token scopes required:

  • Zone.DNS
  • Zone:Read
  • Account.Tunnel:Edit

⚡ Step 4: Create the Tunnel

cloudflared tunnel create dashboard-tunnel

This generates a .json credential file in ~/.cloudflared.


cloudflared tunnel route dns dashboard-tunnel home.yourdomain.com

Make sure no existing A/AAAA/CNAME record is already defined for the subdomain.


🧾 Step 6: Create Tunnel Config

nano ~/.cloudflared/config.yml

Example config:

tunnel: dashboard-tunnel
credentials-file: /home/youruser/.cloudflared/xxxxxxxxx.json

ingress:
  - hostname: yoursubdomain.yourdomain.com
    service: http://localhost:9000
  - service: http_status:404

🚀 Step 7: Start the Tunnel

cloudflared tunnel --config ~/.cloudflared/config.yml run

Access via:

https://yoursubdomain.yourdomain.com

🔄 Optional: Autostart via systemd (User Scope)

mkdir -p ~/.config/systemd/user
sudo loginctl enable-linger $USER

Then create:

nano ~/.config/systemd/user/cloudflared.service

Contents:

[Unit]
Description=Cloudflare Tunnel
After=network.target

[Service]
ExecStart=%h/bin/cloudflared --config %h/.cloudflared/config.yml tunnel run
Restart=on-failure

[Install]
WantedBy=default.target

Enable the service:

systemctl --user daemon-reexec
systemctl --user daemon-reload
systemctl --user enable cloudflared
systemctl --user start cloudflared

🧪 Debug & Test

Check running process:

ps aux | grep cloudflared

Test internal service:

curl http://localhost:9000

🌐 Optional: Dynamic DNS

While tunnels don’t require a static IP, other services (like SSH or VPN) might. I configured DDNS using the Cloudflare DNS API to keep fallback access possible. Since my domain is already on Cloudflare, that choice kept things simple.


✅ Summary

  • A local service (e.g. dashboard) is securely accessible via HTTPS
  • No port forwarding or root access required
  • Can survive reboots using systemd user services
  • Optional fallback via Dynamic DNS

This solution isn’t the only one – but if you already manage your DNS through Cloudflare, it’s relatively low-friction and secure.