Cloudflare Tunnel in Practice: Expose Your Home Lab Without a Public IP
Got a NAS at home you wish you could access from anywhere? Self-hosting a blog but your ISP refuses to hand out a public IP? Want remote access to HomeAssistant without poking holes in your router?
The traditional answers are: rent a VPS as a reverse proxy, fight with dynamic DNS, or give up. Cloudflare Tunnel offers a fourth path: your internal service dials out to Cloudflare's network, and visitors reach you through that tunnel — no inbound ports, no public IP required, completely free.
This guide gets you from zero to your first published service in 30 minutes.
What Is Cloudflare Tunnel
The One-Sentence Explanation
cloudflared is a lightweight daemon that runs on your internal machine and opens a persistent outbound connection to Cloudflare's network. When external users hit your domain, requests land at a Cloudflare edge node first and travel back through the tunnel to your internal service.
What It Solves
| Pain Point | Traditional Fix | Cloudflare Tunnel |
|---|---|---|
| No public IP | VPS + frp/nps | Zero cost, cloudflared dials out |
| Can't open router ports | Beg ISP for a public IP | No inbound ports needed |
| Worried about exposing origin IP | Add a CDN | IP hidden behind Cloudflare permanently |
| TLS certificate hassle | Let's Encrypt + renewal scripts | Auto-issued, auto-renewed |
| Need SSO or 2FA | Set up Authelia yourself | One-click via Cloudflare Zero Trust |
Cloudflare Tunnel was previously called Argo Tunnel. After 2021 it was folded into Cloudflare's Zero Trust suite and made completely free. The free tier covers up to 50 users, which is more than enough for personal projects.
Tunnel vs Port Forwarding vs Frp
| Dimension | Router Port Forwarding | Frp / Nps Self-Hosted | Cloudflare Tunnel |
|---|---|---|---|
| Needs public IP | Required | Server VPS required | Not required |
| Exposes origin IP | Fully exposed | Exposes VPS IP | Hidden |
| DDoS protection | None | Depends on VPS | Global Cloudflare protection |
| Setup complexity | Medium | High | Low |
| Cost | Public IP fee | Monthly VPS fee | Free |
Prerequisites
Three things before you start:
- A domain managed by Cloudflare (NS records pointing to Cloudflare). If not yet, go to Cloudflare dashboard → Add a site and follow the NS change instructions.
- A machine running the service you want to publish — your NAS, a Mac mini, a Raspberry Pi, or a Windows PC all work.
- Outbound network access with TCP port
7844open to Cloudflare. Almost every home network allows this by default.
The free tier auto-issues certificates for the apex domain plus first-level subdomains only. If you want something like app.dev.example.com you need an Advanced Certificate ($10/month and up). Stick with first-level subdomains when planning hostnames.
Method 1: Remote Management (Dashboard, Recommended for Beginners)
This is the path Cloudflare now pushes — all configuration happens in the web console and cloudflared pulls it from the cloud.
Step 1: Create a Tunnel in Zero Trust
- Cloudflare dashboard → left sidebar Zero Trust
- Navigate to Networks → Tunnels
- Click Create a tunnel
- Pick Cloudflared as connector type, click Next
- Give it a name (use the host name, e.g.
home-nas), save
Step 2: Install cloudflared on Your Internal Host
The dashboard generates a one-line install command with a token baked in based on your OS — copy and run it.
Linux (amd64, apt family):
macOS (Homebrew):
Windows (PowerShell as Administrator):
Docker:
After install the tunnel status in the dashboard will flip to Healthy.
Step 3: Publish a Service (Public Hostname)
Open the tunnel detail page, switch to the Public Hostnames tab, click Add a public hostname:
| Field | Example | Notes |
|---|---|---|
| Subdomain | nas | Subdomain prefix |
| Domain | example.com | The CF-managed domain |
| Type | HTTP | Protocol of the internal service |
| URL | localhost:5000 | Internal service address |
Within 30 seconds https://nas.example.com will route from the public internet to your internal localhost:5000. HTTPS certificates are issued automatically by Cloudflare — zero manual config.
Cloudflare auto-creates a CNAME pointing to <tunnel-uuid>.cfargotunnel.com with proxy mode enabled (orange cloud). Don't edit DNS manually.
Method 2: Local Configuration (YAML, Recommended for Advanced Users)
If you want config in Git or need multiple services on the same host, local YAML is cleaner.
Step 1: Log In and Create the Tunnel
Step 2: Write the Config File
Create ~/.cloudflared/config.yml:
Step 3: Bind DNS and Run
Use Case: Move SSH onto the Tunnel
Public SSH gets brute-forced relentlessly. With Tunnel you can close the SSH public port entirely and only allow access through Cloudflare.
Server Side (Where You Connect To)
Add to your ingress config:
Client Side
Option A: Browser-Rendered Terminal (most convenient, requires Zero Trust application config)
Zero Trust → Access → Applications → Add → Self-hosted, pick ssh.example.com, application type SSH, then add an identity provider (GitHub / Google / corporate SSO). Now opening ssh.example.com in a browser drops you straight into a web terminal.
Option B: Local ssh Through cloudflared Proxy
Now ssh [email protected] works as if you were connecting directly.
For the SSH key setup itself, see our SSH Key Authentication Guide. Pairing it with Tunnel maxes out your security posture.
Add an Access Layer: Zero Trust Access
Publishing a service isn't always enough — you probably don't want the whole internet to see your HomeAssistant login page. Zero Trust Access challenges users before they touch your application.
Configuration Steps
-
Zero Trust → Access → Applications → Add an application
-
Pick Self-hosted
-
Application domain:
ha.example.com -
Pick at least one identity provider (the default One-time PIN email works fine)
-
Add an Allow policy:
Save. Now anyone hitting ha.example.com gets stopped by Cloudflare first — they must complete an email PIN or OAuth login before reaching your service. Your internal application doesn't change at all.
Common Pitfalls
1. Tunnel Stuck on Inactive / Down
90% of the time it's blocked outbound. Test manually:
Timeout means your network blocks the port. Rare on home networks, common on corporate/school networks — ask IT to whitelist.
2. HTTPS Backend Returns 502
If the internal service is HTTPS with a self-signed cert (typical for HomeAssistant or Synology DSM), add to ingress:
3. WebSocket Connections Keep Dropping
Bump idle timeouts:
4. Multiple Tunnels on the Same Host Conflict
cloudflared reads only one ~/.cloudflared/config.yml by default. For multiple tunnels, explicitly point to each config:
5. Docker Config Mount Locations
Remember credentials-file paths must reference the container path, not the host path.
Performance and Limits
| Item | Free Tier Limit |
|---|---|
| Number of tunnels | No hard cap |
| Concurrent connections per tunnel | Generally fine for personal use |
| Bandwidth | Unmetered |
| Latency | Depends on the nearest CF edge (typically 20-80ms in most regions) |
| Zero Trust users | 50 free, paid above |
Cloudflare's Terms of Service discourage using the CDN for long-form video or large-file streaming. Running your entire Jellyfin family server through Tunnel sits in a gray area — fine for low-frequency personal use, don't share or commercialize.
Uninstall
If you ever want to back out:
Wrap-Up
Cloudflare Tunnel overturns the old assumption that you need a public IP to host anything. For solo developers and home-lab enthusiasts, it's almost a no-brainer:
- Zero cost — completely free
- Zero exposed ports — pure outbound, far safer than direct public hosting
- Zero certificate ops — HTTPS handled automatically
- Bonus protection — Cloudflare DDoS shield plus Zero Trust auth
The only "cost" is that your traffic flows through Cloudflare's network. For most self-hosting scenarios that trade is completely acceptable.
Next step: put every internal service in your home (NAS, blog, monitoring, IoT consoles) behind a single tunnel and use Access policies for per-service permissions. That's a modern zero-trust architecture for your home lab.