Twelve parts in, and this series has moved from the fundamentals of how Cloudflare sits between your users and your origin, through DNS configuration, SSL certificate management, page rules, Workers, and the full Zero Trust architecture. Part 12 covered Cloudflare Access policies in depth: identity providers, device posture checks, and the logic that decides who gets through the gate and who doesn't. That was the authorization layer. This part is about what lives behind it.
Because Access only matters if there's something worth protecting. And for home labbers, developers running local stacks, and small offices with a handful of internal tools, the question isn't just "who can reach this service." It's "how does anyone reach it at all without opening a hole in the firewall."
That's the problem Cloudflare Tunnel solves. No open ports. No public IP requirement. No VPN to configure on every client machine. Just a daemon running on your server, a connection it initiates outbound to Cloudflare's network, and a public hostname that routes inbound traffic back through that connection to whatever's running on localhost. The architecture is elegant in the way that good infrastructure usually is: it removes a category of problem rather than adding a layer of mitigation on top of it.
This part walks through the full picture. What the tunnel model actually does, who it's built for, how to set one up from scratch, and three specific use cases with real configuration examples: a home lab running Proxmox and Portainer, a developer workflow with a Next.js app and internal API, and a small office with tools like Grafana, Gitea, and a NAS dashboard. By the end, you'll have a working mental model and enough concrete config to get something running today.
What Cloudflare Tunnel Actually Does for Private Networks
The core problem: reaching private services safely
Most home labs and small offices run services that were never designed to face the public internet. Proxmox, Portainer, Grafana, a self-hosted Git instance. These tools assume a trusted local network. They often ship with self-signed certificates, minimal rate limiting, and admin interfaces that hand over full control to whoever logs in. Exposing them directly is not a configuration choice. It's a risk decision, and usually the wrong one.
The traditional workarounds each carry their own friction. Port forwarding on a consumer router works, but it opens a port to the world and depends on a static or dynamic DNS setup that breaks whenever your ISP rotates your IP. A traditional reverse proxy like Nginx handles the routing, but it still requires an open inbound port, a public IP, and TLS configuration you have to maintain. A VPN solves the exposure problem but adds client configuration overhead on every device that needs access.
Cloudflare Tunnel sidesteps all of it.
How the outbound-only connection model works
The cloudflared daemon runs on your origin machine. When it starts, it opens persistent outbound connections to Cloudflare's global network across multiple edge nodes. Those connections stay open. When a request arrives at your public hostname, Cloudflare routes it inbound through one of those existing connections to cloudflared, which forwards it to the local service you've configured.
Your firewall never receives an inbound connection from the outside. The origin machine doesn't need a public IP. Your ISP doesn't need to cooperate. The only requirement is that cloudflared can make outbound HTTPS connections, which is true on virtually every network that can reach the internet at all.
This is fundamentally different from port forwarding, which requires inbound connectivity, and from a traditional reverse proxy, which still terminates connections at a public-facing server. With a tunnel, the public-facing infrastructure is Cloudflare's. Your origin stays dark to the outside world.
Who Should Use Cloudflare Tunnel (And Who Shouldn't)
Ideal users: home labbers, indie developers, small offices
The people who get the most out of Cloudflare Tunnel tend to share a few characteristics. They're running services on hardware they control. They don't want to manage VPN client configs on every device. And they want a real domain name with valid TLS, not a self-signed cert warning every time they open a browser.
Home lab enthusiasts on consumer ISPs are the clearest fit. Consumer ISPs rotate IPs, block inbound port 80 and 443, and don't offer static addressing without a business plan upgrade. Tunnel removes all of that from the equation entirely.
Indie developers get a different benefit. Sharing a local dev server with a client used to mean deploying to a staging environment or running something like ngrok. With a quick tunnel, you get a shareable URL in under a minute. With a named tunnel, you get a persistent subdomain on your own domain that works every time the service is running.
Small offices with remote workers who need occasional access to internal tools, a NAS dashboard, or an admin panel are well-served here too. The alternative is usually a site-to-site VPN or a client VPN, both of which require more ongoing maintenance than most small offices can realistically manage.
Who It's Not Built For
If a service never needs to be reached from outside the LAN, a tunnel adds complexity with no benefit. Keep internal-only tools internal. And if you're operating in a security-sensitive environment, a tunnel alone isn't enough. Cloudflare Access policies should sit in front of anything sensitive. The next section on security configurations covers that in detail.
Cases where a full VPN or private network is still better
Tunnel is not a universal replacement for network-level access control. If your team needs to reach dozens of internal services, a full VPN or a Cloudflare WARP with Zero Trust configuration gives you broader coverage with less per-service configuration. If you're handling regulated data and need strict network segmentation, a private network architecture with Cloudflare's network-level controls is the right answer. And exposing every internal tool publicly, even behind a tunnel, creates an attack surface. The risk section later in this part covers what happens when tunnels are misconfigured.
Setting Up Your First Tunnel: A Step-by-Step Walkthrough
Installing and authenticating cloudflared
The cloudflared binary is the only thing you need on the origin machine. On Debian and Ubuntu systems, Cloudflare publishes an official apt repository. Add the repository, run apt install cloudflared, and the binary lands at /usr/local/bin/cloudflared. On RHEL-based systems, the rpm package installs the same way through Cloudflare's rpm repository.
If you're running everything in containers, the cloudflare/cloudflared Docker image works cleanly. Mount your credentials directory as a volume and pass the tunnel name as an environment variable. The container approach is worth considering for home labs already running a Docker or Portainer stack, since it keeps the daemon inside the same management plane as everything else.
Once the binary is installed, authenticate it against your Cloudflare account:
cloudflared tunnel loginThis opens a browser window. Select the zone you want to associate with the tunnel. Cloudflare writes a certificate file to ~/.cloudflared/cert.pem. That certificate authorizes cloudflared to create and manage tunnels for that zone. It doesn't grant access to your account beyond tunnel operations.
Creating a named tunnel via the dashboard or CLI
Named tunnels are persistent. They survive restarts, they have stable UUIDs, and they're the right choice for anything you intend to run continuously. Create one with:
cloudflared tunnel create my-homelabCloudflare generates a UUID for the tunnel and writes a credentials JSON file to ~/.cloudflared/<UUID>.json. That file contains the tunnel's secret. Guard it. Anyone with that file can route traffic through your tunnel.
You can also create tunnels through the Cloudflare dashboard under Zero Trust > Networks > Tunnels, which gives you a visual interface for the same operation and lets you configure ingress rules through a form instead of a YAML file.
Mapping a public hostname to a local service
The config.yml file is where ingress rules live. A minimal configuration looks like this:
1tunnel: <your-tunnel-uuid>
2credentials-file: /root/.cloudflared/<UUID>.json
3
4ingress:
5 - hostname: myapp.yourdomain.com
6 service: http://localhost:3000
7 - service: http_status:404The final catch-all rule returning a 404 is required. Without it, cloudflared rejects the configuration. Every request that doesn't match a hostname rule hits that fallback.
Once the config is in place, run the tunnel:
cloudflared tunnel run my-homelabCloudflare automatically creates a CNAME DNS record pointing myapp.yourdomain.com to <UUID>.cfargotunnel.com. You don't manage that record manually. When the tunnel is running, the CNAME resolves through Cloudflare's network to your origin connection. When it's not running, requests fail cleanly rather than timing out against an unreachable IP.
Home Lab Use Case: Exposing Proxmox and Portainer
Tunneling the Proxmox web UI securely
Proxmox VE ships with a web interface on port 8006 over HTTPS with a self-signed certificate. That self-signed cert creates a problem for cloudflared's default behavior, which attempts to verify the origin certificate. The fix is the noTLSVerify option in the ingress rule:
1ingress:
2 - hostname: proxmox.yourdomain.com
3 service: https://localhost:8006
4 originRequest:
5 noTLSVerify: trueThis tells cloudflared to skip certificate validation on the connection to the origin. It does not affect the connection between the user's browser and Cloudflare's edge, which remains fully TLS-verified with a Cloudflare-issued certificate. The user sees a valid cert. cloudflared accepts the self-signed one on the backend. That's a reasonable tradeoff for a home lab where you control both ends.
The Proxmox console uses WebSockets for the VNC and SPICE connections. Cloudflare Tunnel handles WebSocket traffic, but you may see latency in the console that you wouldn't on a local connection. For routine management tasks, it's fine. For intensive console sessions, expect some lag.
Do Not Expose This Without Access Policies
The Proxmox web UI grants full hypervisor control to whoever can authenticate. A tunnel makes it reachable. Cloudflare Access makes it safe. Before you point a public hostname at Proxmox, configure an Access application in front of it. Part 12 covered exactly how to do that.
Accessing Portainer for container management remotely
Portainer runs on HTTP at port 9000 by default. The ingress rule is simpler than Proxmox since there's no TLS on the origin side:
- hostname: portainer.yourdomain.com
service: http://localhost:9000Portainer's interface is also WebSocket-heavy. The container log view and the exec console both use WebSocket connections, and these work through the tunnel without additional configuration. Response times are acceptable for remote management. For the same reason as Proxmox, this should never be publicly accessible without an Access policy sitting in front of it. Portainer with full admin rights is effectively root on every container on the host.
Developer Use Case: Sharing a Next.js App and Internal API
Spinning up a quick tunnel for a Next.js dev server
The fastest way to share a local service is a quick tunnel. No account required, no configuration file, no named tunnel. Just:
cloudflared tunnel --url http://localhost:3000Cloudflare assigns a random subdomain on trycloudflare.com and starts proxying traffic to your local port 3000. The URL appears in the terminal output within a few seconds. Share it with a client, let them click through the app, and when you close the terminal the URL is gone.
Quick tunnels are purpose-built for client demos and short-lived sharing. They're not for production. The URL changes every time. There's no persistent DNS record, no custom domain, and no Access policy support. Use them for "look at this thing I built" and nothing else.
Exposing an internal REST API for team or client testing
For anything that needs to persist across sessions, a named tunnel with a custom hostname is the right move. A Next.js app at dev.yourdomain.com and an internal API at api-dev.yourdomain.com can both route through the same tunnel:
1tunnel: <your-tunnel-uuid>
2credentials-file: /root/.cloudflared/<UUID>.json
3
4ingress:
5 - hostname: dev.yourdomain.com
6 service: http://localhost:3000
7 - hostname: api-dev.yourdomain.com
8 service: http://localhost:4000
9 originRequest:
10 httpHostHeader: api-dev.yourdomain.com
11 - service: http_status:404The httpHostHeader option on the API route ensures the correct Host header reaches the origin, which matters if your API server validates the host. You can also inject custom headers at the ingress rule level using the originRequest.headers field. This is useful for passing an internal auth token, setting a X-Forwarded-For override, or adding CORS headers that your dev API doesn't handle natively.
The combination of a persistent frontend URL and a persistent API URL means your team can build against api-dev.yourdomain.com throughout a sprint without updating environment variables every time someone restarts their machine. For small teams working asynchronously across time zones, that consistency is worth more than it sounds.
Small Office Use Case: Remote Admin Panels and Private Tools
Reaching an office router or NAS dashboard from anywhere
A Synology DSM or TrueNAS dashboard routed through a tunnel gives remote staff access to file management, backup status, and storage health without a VPN. The ingress rule follows the same pattern as any other local service. For Synology running on port 5000 over HTTP, the config is straightforward. For TrueNAS, which defaults to HTTPS on port 443 with a self-signed cert, add noTLSVerify: true the same way as Proxmox.
Router admin panel access through a tunnel is possible but carries specific risks. Router interfaces often lack brute-force protection, session timeout controls, and modern authentication options. If you tunnel a router admin panel, it must be behind a Cloudflare Access policy with MFA enforced. No exceptions.
"Every internal tool you expose remotely is a tool someone else might try to use remotely. The question isn't whether to protect it. It's whether your protection is proportional to what it controls."
Tunneling internal tools like Grafana, Gitea, or Jellyfin
Grafana for a small office server room gives remote staff visibility into system health without needing to be on-site. Route it through a tunnel, put an Access policy in front of it, and your sysad
Security Warnings: Why You Should Not Expose Everything Publicly
A tunnel gets your service off a blocked port and onto the public internet without opening firewall holes. That's genuinely useful. It's also genuinely dangerous if you stop thinking there. The tunnel handles transport. It does not handle trust. Those are two separate problems, and conflating them is how admin panels end up indexed by Shodan.
The False Sense of Security from 'It's Just a Subdomain'
Bots don't care that your subdomain is obscure. They scan everything. Certificate transparency logs are public, which means the moment Cloudflare issues a cert for proxmox.yourdomain.com, that hostname is visible to anyone watching CT log aggregators. Scanners pick up new subdomains within minutes of issuance. "Nobody knows about it" is not a security posture.
Proxmox, Portainer, and consumer router management interfaces have all had exploitable CVEs in the past several years. These aren't theoretical vulnerabilities. They've been used in the wild against exactly the kind of low-attention home lab and small office deployments that tunnel access makes easy to reach. Putting a Proxmox web UI on a public hostname without a second authentication layer is not a configuration decision. It's a gamble.
What Happens When an Admin Panel Has No Second Layer of Auth
The tunnel delivers the request to your service. That's all it does. If Portainer's login page is the only thing standing between an attacker and your container environment, you're relying entirely on that application's own security. Credential stuffing, brute force, and session vulnerabilities all become live risks the moment that panel is reachable. Cloudflare Access changes that equation by requiring identity verification before the request ever reaches your origin. Never expose a database administration tool like phpMyAdmin or Adminer without Access in front of it. The attack surface on those tools is well-documented and actively targeted.
Hard Rule
Database admin UIs exposed without Cloudflare Access are not a risk to accept. They're a breach waiting for a date on the calendar.
Rate Limiting, Bot Protection, and WAF as Additional Defenses
Defense in depth means layers that each assume the previous one failed. The tunnel is layer one. Cloudflare Access is layer two. Cloudflare WAF rules and rate limiting are layer three. WAF rulesets catch known exploit patterns before they reach your origin. Rate limiting stops credential stuffing cold by capping how many requests a single IP can make in a given window. Both are configurable in the Cloudflare dashboard under your zone's Security settings. Use them. Cloudflare logs every request that passes through your tunnel. Pull those logs regularly and look at what's hitting your services. You'll find things that will change how you think about exposure.
Running cloudflared as a Persistent System Service
Getting a tunnel running manually is satisfying. Having it disappear the moment you close the terminal is not. A production-quality tunnel setup means cloudflared starts automatically, restarts on failure, and logs somewhere you can actually find later. That's what service mode gives you.
Installing cloudflared as a systemd Service on Linux
After authenticating and creating your tunnel, installing it as a systemd service takes one command:
sudo cloudflared service installThat command reads your config file, writes a systemd unit file to /etc/systemd/system/cloudflared.service, and registers the service. Then you enable and start it:
sudo systemctl enable cloudflared
sudo systemctl start cloudflaredLogs flow to the system journal. Pull them with journalctl -u cloudflared -f for a live tail, or drop the -f to read the full history. The config file in service mode lives at /etc/cloudflared/config.yml. That's different from the default path when you run cloudflared manually as your own user, which is ~/.cloudflared/config.yml. Getting that path wrong is the most common reason a service install works but routes nothing correctly.
On Windows, the equivalent is cloudflared service install run from an elevated command prompt. The service appears in the Windows Services panel and can be managed with standard sc commands or the GUI.
Running cloudflared in Docker Compose for Containerized Home Labs
If your services already run in Docker, cloudflared fits naturally as a sidecar container in the same Compose file:
1services:
2 cloudflared:
3 image: cloudflare/cloudflared:latest
4 restart: unless-stopped
5 command: tunnel --config /etc/cloudflared/config.yml run
6 volumes:
7 - ./cloudflared:/etc/cloudflared
8 healthcheck:
9 test: ["CMD", "cloudflared", "tunnel", "info"]
10 interval: 30s
11 timeout: 10s
12 retries: 3Mount your credentials file and config into the container. The restart: unless-stopped policy keeps it running through reboots and crashes. The health check gives Docker visibility into whether the tunnel is actually functional, not just whether the process is alive.
Troubleshooting Common Tunnel Problems
Most tunnel problems fall into three categories. The error message points you at the right one if you know what to look for.
Connection Refused and Origin Unreachable Errors
ERR_CONNECTION_REFUSED means cloudflared reached your machine but nothing answered on the port you specified. Before debugging the tunnel, confirm the origin service is actually running and listening on the right address. curl http://localhost:8006 from the tunnel host itself is the fastest check. If that fails, the tunnel isn't the problem.
Ingress rule order matters more than most people expect. Cloudflared matches rules top-to-bottom and stops at the first match. If you have a wildcard rule before a specific hostname rule, the wildcard wins every time and the specific rule never fires. Put specific rules first, wildcards last. The final rule in every config must be a catch-all with no hostname:
- service: http_status:404Without that final rule, cloudflared will refuse to start.
Use cloudflared tunnel info <tunnel-name> to see connection state and which Cloudflare edge regions you're connected to. Use cloudflared tunnel list to confirm the tunnel exists and is associated with the right credentials.
TLS and Certificate Mismatch Issues
If your origin service uses a self-signed certificate, cloudflared will refuse to connect by default because it can't verify the cert. You have two options. Add noTLSVerify: true under the origin service in your config to skip verification entirely, which is acceptable for internal services where you control the origin. Or set originServerName to the hostname the cert was issued for, which lets cloudflared verify the cert against the right name. Don't use noTLSVerify on anything facing real traffic without understanding what you're giving up.
Tunnel Not Routing to the Right Service
DNS propagation after tunnel creation usually completes in under five minutes, but occasionally takes longer. If the CNAME record isn't resolving yet, wait and check with dig or nslookup before assuming something is broken in your config.
Debug Flag
Add --loglevel debug to your cloudflared run command to get verbose output showing exactly which ingress rule matched each request and what response the origin returned.
The Cloudflare dashboard tunnel health panel shows connector status and recent error rates. Green connectors with low error rates mean the tunnel itself is healthy. If errors are high, the problem is almost always at the origin, not in the tunnel configuration.
Quick Reference Checklist: Launching a Secure Tunnel
Every step here matters. Skipping the Access layer or skipping the service install turns a solid setup into a fragile one.
Run through this list in order. The external network test at the end catches assumptions that local testing never will.
What's Next: Pairing Tunnels with Zero Trust Access Policies
Part 13 covered the practical side of Cloudflare Tunnel. You've seen how to set it up for home labs, developer environments, and small offices. You've seen how to run it as a persistent service, how to troubleshoot the problems that actually show up in the real world, and why transport security alone isn't enough to call something safe.
The tunnel handles transport. It gets your service reachable. What it doesn't do is decide who's allowed in. That's the job of Cloudflare Access, and it's what Part 14 covers in full.
Part 14 walks through building Access policies that require real identity verification before a request reaches your origin. Email OTP, GitHub OAuth, Google Workspace, service tokens for machine-to-machine flows. The combination of a tunnel and an Access policy is what "Zero Trust" actually means in practice, not as a marketing phrase but as a working architecture. Get your first tunnel running before you get there. The next part builds directly on what you've set up here.