· Jonathan Cutrer · Engineering · 4 min read
Cloudflare Tunnels: Expose Local Services Without Opening Ports
A step-by-step guide to running Cloudflare Tunnel on a home server — no port forwarding, no firewall rules, no static IP required.
I have a home server running a handful of internal services — a Grafana instance, a small FastAPI service for personal automation, a few Docker containers that I want to reach from anywhere. For years, I ran a VPN to access them. It worked, but every time I was on a network that blocked non-standard ports, I was stuck.
Cloudflare Tunnels replaced all of that. The server makes an outbound connection to Cloudflare’s network, and Cloudflare routes traffic to it through a subdomain on your domain. No inbound ports. No firewall rules. No static IP. Works from anywhere.
Here’s how to set it up from scratch.
What You Need Before Starting
- A domain managed by Cloudflare DNS (free)
- A server or machine running the service you want to expose
cloudflaredinstalled on that machine- A Cloudflare account (free tier works)
Step 1: Install cloudflared
On Ubuntu/Debian:
curl -L --output cloudflared.deb \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.debVerify it’s installed:
cloudflared --versionStep 2: Authenticate cloudflared
cloudflared tunnel loginThis opens a browser window and asks you to select which Cloudflare zone (domain) you want the tunnel associated with. After you authorize, cloudflared writes a certificate to ~/.cloudflared/cert.pem.
Step 3: Create the Tunnel
cloudflared tunnel create my-home-serverThis creates a tunnel with a UUID and writes credentials to ~/.cloudflared/<UUID>.json. Note the UUID — you’ll need it.
cloudflared tunnel listShould show your new tunnel with its ID.
Step 4: Write the Config File
Create ~/.cloudflared/config.yml:
tunnel: <your-tunnel-uuid>
credentials-file: /home/youruser/.cloudflared/<your-tunnel-uuid>.json
ingress:
- hostname: grafana.yourdomain.com
service: http://localhost:3000
- hostname: api.yourdomain.com
service: http://localhost:8000
- service: http_status:404The last service: http_status:404 is required — it’s the catch-all for requests that don’t match any hostname rule. Without it, cloudflared will refuse to start.
Step 5: Create DNS Records
cloudflared tunnel route dns my-home-server grafana.yourdomain.com
cloudflared tunnel route dns my-home-server api.yourdomain.comThis creates CNAME records in Cloudflare DNS pointing to <uuid>.cfargotunnel.com. You can verify them in the Cloudflare dashboard under DNS.
Step 6: Run the Tunnel
cloudflared tunnel run my-home-serverTest that it works by navigating to grafana.yourdomain.com. If you see your Grafana login page, it’s working.
Step 7: Run as a System Service
You don’t want to keep a terminal session open. Install it as a systemd service:
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflaredCheck the status:
sudo systemctl status cloudflared
journalctl -u cloudflared -fStep 8: Add Zero-Trust Access (Optional but Recommended)
By default, your services are publicly accessible to anyone who knows the URL. If that’s not what you want, add Cloudflare Access policies in the Cloudflare Zero Trust dashboard.
For personal services, I use a simple email-based policy — anyone with my email address can authenticate through a one-time code. It adds maybe two extra clicks when opening a protected service for the first time.
Step 9: Test Failover Behavior
Kill one of your local services and try to reach it through the tunnel. You should get a Cloudflare error page rather than a connection timeout. This is correct behavior — Cloudflare handles the error gracefully rather than hanging the connection.
Bring the service back up and confirm it starts serving again within a few seconds. The tunnel reconnects automatically.
Step 10: Keep cloudflared Updated
sudo apt update && sudo apt upgrade cloudflaredOr check the GitHub releases page and re-download. The .deb update is straightforward. Tunnel credentials are not affected by updates.
What This Doesn’t Cover
Access control beyond the basic email policy, multiple tunnels from one machine, or high-availability setups with multiple machines pointing at the same hostname. Those are all possible but depend on what you’re building. The Cloudflare docs are genuinely good for the more advanced configurations.
This setup has been running on my home server for eight months without any issues. The tunnel reconnects automatically after reboots, power outages, and ISP glitches. It’s the most reliable piece of infrastructure I’m running.