Port forwarding is the traditional approach to accessing home services remotely, but it creates security risks. Every open port is an attack surface. Cloudflare Tunnels eliminate port forwarding entirely—the tunnel connects your server to Cloudflare's edge, and traffic flows through an encrypted tunnel without any inbound ports exposed.
How Cloudflare Tunnels Work
A Cloudflare Tunnel creates an outbound connection from your server to Cloudflare's network. This connection remains persistent, providing a path for incoming traffic without inbound firewall rules. Your services live at a .trycloudflare.com domain initially, or at your own domain once configured.
The tunnel agent (cloudflared) runs on your server and maintains the connection. When you access your tunnel URL, Cloudflare routes traffic through the established tunnel to your service. The entire path is encrypted—client to Cloudflare edge via HTTPS, Cloudflare edge to your server via the tunnel's own encryption.
Zero Trust Access Beyond Tunnels
Tunnels are the foundation; Cloudflare Access adds identity-based access control. Instead of exposing a service to the public internet with just a password, Access requires authentication through your Cloudflare identity provider (Google Workspace, GitHub, Azure AD). Users must be logged into an approved identity provider before reaching your service.
This transforms self-hosted services from "password protected" to "organization member only." A Nextcloud instance accessible only to your family, a Home Assistant dashboard without VPN, a Bitwarden password vault without exposing the registration page—all become possible with Access policies.
Setting Up the Tunnel
The setup process:
- Create a Cloudflare account if you don't have one
- Add your domain to Cloudflare (free tier suffices)
- Install cloudflared on your server
- Authenticate with
cloudflared tunnel login - Create a tunnel with
cloudflared tunnel create name - Configure services in the tunnel config file
- Route traffic through the tunnel
The configuration file maps subdomains to local services:
tunnel: <tunnel-UUID>
credentials-file: /root/.cloudflared/<tunnel-UUID>.json
ingress:
- hostname: home.yourdomain.com
service: http://localhost:8123
- hostname: files.yourdomain.com
service: http://localhost:8080
- service: http_status:404
Running as a Service
Manual tunnel startup works for testing; systemd service handles production. Create a unit file:
[Unit]
Description=Cloudflare Tunnel
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/cloudflared tunnel run
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Enable and start the service: sudo systemctl enable --now cloudflared. The tunnel now survives reboots and starts automatically.
Performance Considerations
Cloudflare's network is globally distributed; traffic exits near your location, not near your server's location. This means latency to European services accessed from the US goes through Cloudflare's European edge rather than traversing directly. For most home server use cases, this is imperceptible.
Bandwidth through Cloudflare doesn't count against your home ISP's upload limits. For connections where your upload bandwidth is limited (common with cable internet), tunneling through Cloudflare means remote access uses Cloudflare's bandwidth rather than yours. This is particularly valuable for streaming video or large file access.