What Cloudflare Tunnel actually does
Cloudflare Tunnel flips the usual model of exposing a server to the internet. Instead of Cloudflare (or anyone else) initiating a connection to your machine, your machine initiates the connection to Cloudflare. The cloudflared daemon runs on your server, laptop, Raspberry Pi, or inside a container, and opens a persistent, encrypted outbound connection to the nearest Cloudflare data center. That connection stays open. When a visitor requests your hostname, the request lands on Cloudflare’s edge, gets matched against the ingress rules you defined for that tunnel, and gets forwarded back down the existing outbound tunnel to your local service. The practical result: no inbound firewall rule, no port forward on your router, and no need for a static or even public IP address. Your origin server can sit behind carrier-grade NAT on a residential connection and still serve traffic reliably, because the connection direction never reverses. This is also why the setup plays well with mobile hotspots, CGNAT ISPs, and locked-down corporate networks where inbound traffic is blocked by policy — cloudflared only ever needs outbound HTTPS. Cloudflare Tunnel is distinct from Cloudflare WARP, which is the client-side agent that runs on end-user devices and routes their traffic through Cloudflare’s network for policy enforcement. Tunnel is the server-side half: it’s what exposes a private origin or network to Cloudflare. The two are commonly paired — WARP-enrolled devices can reach an entire private subnet published through a tunnel using Cloudflare’s private network routing, without publishing each individual application hostname by hand. Why teams are moving off port forwarding
Port forwarding has always carried a specific set of risks that most self-hosters accept without fully pricing in. Forwarding a port means the router hands a direct path from the public internet straight to a device on the local network. Every automated scanner sweeping IPv4 space eventually finds it, and from that point on the exposed service — not Cloudflare’s edge, not a CDN, just the raw application — is the only thing standing between an attacker and whatever’s running behind it. Unpatched software, weak default credentials, and misconfigured admin panels turn a convenience feature into an incident. Cloudflare Tunnel changes the attack surface rather than just adding a layer on top of it. Because the origin never listens on a public port, there’s nothing for a port scanner to find in the first place — the only publicly resolvable address is Cloudflare’s own edge IP, which also happens to absorb the bulk of common Layer 3/4 DDoS traffic before it ever reaches your ingress rules. That doesn’t make an application immune to being compromised through legitimate-looking HTTP requests, but it removes an entire category of opportunistic, automated port-scanning attacks that have nothing to do with the application’s own security posture. There’s also a practical, non-security reason this approach has spread through homelab and small-team setups so quickly: residential ISPs increasingly deploy carrier-grade NAT, which makes traditional port forwarding impossible without paying extra for a static IP or begging an ISP for one. An outbound-only tunnel sidesteps that constraint entirely, since it never needs the ISP to route anything inbound at all. Prerequisites
Before starting, make sure the following are in place. None of this requires a paid Cloudflare plan. - A domain added to Cloudflare with active nameservers (the Free plan is sufficient)
- cloudflared version 2026.9.1 or later (check with cloudflared --version after install)
- A Linux, macOS, or Windows machine, or a Docker host, to run the connector
- A local service to expose — this guide uses a self-hosted app on port 8080 as the running example
- A Cloudflare account with the domain’s zone (no credit card required for the Free Zero Trust plan)
- Root or sudo access on the host machine for the systemd service step
- Roughly 45–60 minutes for the full walkthrough, including the Access policy at the end
If you don’t have a spare domain, Cloudflare’s registrar and most third-party registrars support adding a domain to a Cloudflare zone for free; DNS propagation typically completes within a few minutes to a few hours depending on your registrar and previous TTL settings. Step 1: Install cloudflared
Cloudflare distributes cloudflared as standalone binaries, .deb and .rpm packages, a Homebrew formula, and a Docker image, covering Windows, macOS, Linux, ARM, and ARM64. Pick the method that matches your platform. - # macOS (Homebrew)
- brew install cloudflared
- # Debian/Ubuntu
- curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
- sudo dpkg -i cloudflared.deb
- # Verify the install
- cloudflared --version
复制代码
Expected output looks like cloudflared version 2026.9.1 (built 2026-09-11). If the command isn’t found on Linux after the .deb install, confirm /usr/local/bin or /usr/bin is on your PATH — the package generally places the binary automatically, but minimal containers sometimes need a manual symlink. Step 2: Authenticate cloudflared against your account
Named tunnels — the persistent, production-grade kind — need to be tied to your Cloudflare account and a specific zone. Run the login command, which opens a browser window for authentication and zone authorization.
Select the domain you want to use in the browser prompt. On success, cloudflared writes a certificate file to ~/.cloudflared/cert.pem (or the equivalent path on Windows). That certificate is what authorizes this machine to create and manage tunnels for the zone — treat it like any other credential and don’t commit it to a repository. Step 3: Create a named tunnel
With the certificate in place, create the tunnel itself. This generates a UUID and a credentials JSON file that cloudflared uses to authenticate the tunnel connection going forward. - cloudflared tunnel create home-labcloudflared tunnel list
复制代码
Output example: - ID NAME CREATED
- a1b2c3d4-e5f6-7890-abcd-ef1234567890 home-lab 2026-09-22T14:02:11Z
- Tunnel credentials written to /root/.cloudflared/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json.
- cloudflared chose this file based on where your origin certificate was found.
- Keep this file secret. To revoke these credentials, delete the tunnel.</span>
复制代码
That credentials file is the single point of compromise for the tunnel — anyone with it can impersonate your connector. Restrict its permissions with chmod 600 and keep it out of version control and shared backups. Step 4: Write the ingress configuration
Cloudflare Tunnel routes traffic based on an ingress block in a YAML config file, evaluated top to bottom, with the last rule acting as a catch-all. Create /etc/cloudflared/config.yml: - tunnel: a1b2c3d4-e5f6-7890-abcd-ef1234567890
- credentials-file: /etc/cloudflared/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json
- ingress:
- - hostname: app.example.com
- service: http://192.168.1.20:8080
- - hostname: ssh.example.com
- service: ssh://192.168.1.30:22
- - service: http_status:404</span>
复制代码
Each hostname maps to a local service address. The final line, http_status:404, is mandatory in practice — without a catch-all rule, cloudflared will refuse to start or will silently drop unmatched requests. Before running anything, validate the syntax: - cloudflared tunnel ingress validate
复制代码
Step 5: Route DNS to the tunnel
Each hostname in your ingress block needs a matching DNS record pointing at the tunnel. cloudflared can create this automatically: - cloudflared tunnel route dns home-lab app.example.com
- <div>cloudflared tunnel route dns home-lab ssh.example.com</div>
复制代码 Step 6: Run the tunnel
Start the tunnel in the foreground first to confirm everything connects before wiring it into a service manager: - cloudflared tunnel --config /etc/cloudflared/config.yml run home-lab
复制代码
A healthy connection produces log lines similar to: - INF Starting tunnel tunnelID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
- INF Connection registered connIndex=0 location=lhr08
- INF Connection registered connIndex=1 location=lhr03
- INF Registered tunnel connection connIndex=2
复制代码
cloudflared opens multiple redundant connections (typically four) to different edge locations for resilience. Visit https://app.example.com in a browser — traffic should now reach your local service with zero inbound firewall rules involved. Step 7: Install cloudflared as a persistent systemd service
Running the tunnel in a foreground terminal is fine for testing but won’t survive a reboot or a closed SSH session. On Linux, install it as a systemd service: - sudo cloudflared service install
- sudo systemctl enable --now cloudflared
- sudo systemctl status cloudflared
- journalctl -u cloudflared -f
复制代码
The service install command reads your existing config and credentials from the default cloudflared directory and registers a unit that starts on boot. Confirm the service user has read access to both the config file and the credentials JSON — a common failure here is a permissions mismatch after moving files into /etc/cloudflared/. Step 8: Run cloudflared in Docker instead
If your workloads already live in containers, skip the CLI-managed tunnel and run cloudflared itself as a container, authenticated with a token generated from the Zero Trust dashboard rather than the local certificate file. This is the simpler path for anyone running Docker Compose stacks. - services:
- cloudflared:
- image: cloudflare/cloudflared:2026.9.1
- container_name: cloudflared
- command: tunnel --no-autoupdate run
- environment:
- - TUNNEL_TOKEN=${TUNNEL_TOKEN}
- restart: unless-stopped
- networks:
- - app-net
- networks:
- app-net:
- external: true
复制代码
Create the tunnel and its token in the Zero Trust dashboard under Networks > Tunnels, copy the token into a local .env file as TUNNEL_TOKEN=eyJ..., and point the ingress hostnames at your container service names (for example http://plex:32400) instead of raw IP addresses, since containers on the same Docker network resolve each other by name. Step 9: Add Cloudflare Access in front of the tunnel
A tunnel alone only handles connectivity — it doesn’t gate who’s allowed in. For anything beyond a public marketing page, put a Cloudflare Access policy in front of the hostname. In the Zero Trust dashboard, go to Access > Applications, add a self-hosted application for app.example.com, and attach a policy. - Require login via an identity provider (Google, GitHub, Microsoft Entra, or a generic SAML/OIDC provider)
- Restrict access to a specific email address or an entire email domain
- Require a specific group membership synced from your identity provider
- Enforce device posture checks (managed device, disk encryption, certain OS version)
- Use a service token for machine-to-machine calls that shouldn’t go through a login screen
Once the policy is active, visiting the hostname redirects to a Cloudflare-hosted login page before any request reaches your origin server at all — the authentication happens at the edge, not in your application. Teams that need to block automated bot traffic on a public-facing hostname rather than gate it behind a login can layer in Cloudflare Turnstile as a lighter-weight alternative to a full Access policy. This is the layer that turns “no open ports” into “no open ports and no unauthenticated access,” which matters far more once SSH or RDP hostnames are involved. Step 10: Publish SSH and RDP through the tunnel
The same ingress mechanism that routes HTTP traffic can front raw TCP services like SSH and RDP. Add entries to the ingress block: - ingress:
- - hostname: ssh.example.com
- service: ssh://10.0.0.10:22
- - hostname: desktop.example.com
- service: rdp://10.0.0.20:3389
- - service: http_status:404
复制代码
Connect through the tunnel with the cloudflared access ssh helper or an SSH config entry using ProxyCommand cloudflared access ssh --hostname %h. Never publish SSH or RDP without an Access policy in front of it — a bare tunnel hostname with no authentication layer is functionally the same exposure as forwarding the port directly, just with better DDoS protection. Step 11: Test with a Quick Tunnel before committing to the named setup
For one-off testing or sharing a local dev server without touching DNS at all, cloudflared supports Quick Tunnels — no account, no config file, no login required. - cloudflared tunnel --url http://localhost:3000
复制代码
This prints a random trycloudflare.com hostname that proxies to your local port for as long as the process stays running. It’s genuinely useful for demoing a build to a client or testing a webhook callback, but the hostname is temporary, unauthenticated by default, and disappears the moment you kill the process — it isn’t a substitute for a named tunnel in any production or even semi-permanent homelab context. Step 12: Run cloudflared as a Kubernetes ingress
For clusters, cloudflared can run as a standard Deployment authenticated with a tunnel token, routing traffic to Kubernetes Services without exposing a NodePort or LoadBalancer at all. - apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: cloudflared
- spec:
- replicas: 2
- selector:
- matchLabels:
- app: cloudflared
- template:
- metadata:
- labels:
- app: cloudflared
- spec:
- containers:
- - name: cloudflared
- image: cloudflare/cloudflared:2026.9.1
- args: ["tunnel", "--no-autoupdate", "run"]
- env:
- - name: TUNNEL_TOKEN
- valueFrom:
- secretKeyRef:
- name: cloudflared-token
- key: token
复制代码
Running two replicas gives you redundant connectors to the same tunnel — if one pod is rescheduled, the other keeps serving traffic. Point the ingress rules at internal ClusterIP service DNS names (for example http://my-app.default.svc.cluster.local:80) rather than pod IPs, since those change on every restart. Step 13: Verify, monitor, and rotate credentials
With the tunnel live, confirm its health and set up basic monitoring hygiene: - cloudflared tunnel info home-lab
- cloudflared tunnel list
复制代码
In the Zero Trust dashboard under Networks > Tunnels, each tunnel shows connector health, uptime, and the data centers it’s connected to. Application-level Access logs — who logged in, from where, and whether the policy allowed or blocked the request — live under Logs > Access. Rotate the tunnel credentials periodically by deleting and recreating the tunnel if you suspect the credentials file has leaked; there’s no in-place key rotation for a locally-managed tunnel’s certificate. Worth setting up early rather than after something goes wrong: alerting on connector disconnects. The Zero Trust dashboard supports notification rules that fire when a tunnel’s connector count drops to zero, which is the earliest signal that a homelab service has quietly gone offline — often hours or days before anyone notices manually. For anything you actually depend on daily, that alert is the difference between a five-minute fix and discovering the outage the next time you try to use the service yourself.
September 22, 2026 Nadia Dubois
来自圈子: 动手动脚 |