← All articles
NETWORKING Cloudflare Tunnel: Expose Home Lab Services Without ... 2026-02-09 · cloudflare · tunnel · zero-trust

Cloudflare Tunnel: Expose Home Lab Services Without Port Forwarding

Networking 2026-02-09 cloudflare tunnel zero-trust networking

You've got services running in your homelab — Nextcloud, Jellyfin, Gitea, a dashboard. They work fine on your local network, but accessing them from outside means port forwarding, dynamic DNS, and hoping your ISP doesn't block inbound connections. If you're behind CGNAT, it might not work at all.

Cloudflare Tunnel solves this by creating an outbound connection from your server to Cloudflare's network. No inbound ports needed. No port forwarding. No exposing your home IP address. Your services get a public URL with HTTPS, DDoS protection, and optional authentication — all through a single daemon running on your server.

How Cloudflare Tunnel Works

The cloudflared daemon runs on your server and establishes an outbound connection to Cloudflare's edge network. Since the connection is outbound, it works through NAT, firewalls, and CGNAT without any router configuration.

When someone visits nextcloud.yourdomain.com, the request hits Cloudflare's edge, routes through the tunnel to your server, and cloudflared proxies it to the local service. The response goes back the same way.

Your home IP address is never exposed. DNS records point to Cloudflare, not to your home. Even if someone looks up your domain's IP, they'll see Cloudflare's addresses.

The tunnel maintains persistent connections to multiple Cloudflare data centers for redundancy. If one connection drops, traffic routes through another. It reconnects automatically after network interruptions.

Prerequisites

You need:

Installing cloudflared

Debian/Ubuntu

# Add Cloudflare's GPG key and repository
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list

sudo apt update
sudo apt install cloudflared

Fedora/RHEL

sudo dnf install -y https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm

Docker

docker pull cloudflare/cloudflared:latest

Verify Installation

cloudflared --version
# cloudflared version 2025.x.x

Creating a Tunnel

Authenticate with Cloudflare

cloudflared tunnel login

This opens a browser window where you log in to your Cloudflare account and authorize cloudflared to manage tunnels for a specific zone (domain). A certificate is saved to ~/.cloudflared/cert.pem.

Create the Tunnel

cloudflared tunnel create homelab

This creates a tunnel named "homelab" and generates a credentials file at ~/.cloudflared/<TUNNEL_ID>.json. Note the tunnel ID — you'll need it for the config.

# List your tunnels
cloudflared tunnel list

Configure the Tunnel

Create ~/.cloudflared/config.yml:

tunnel: <YOUR_TUNNEL_ID>
credentials-file: /home/youruser/.cloudflared/<YOUR_TUNNEL_ID>.json

ingress:
  # Nextcloud
  - hostname: cloud.yourdomain.com
    service: http://localhost:8080

  # Jellyfin
  - hostname: media.yourdomain.com
    service: http://localhost:8096

  # Gitea
  - hostname: git.yourdomain.com
    service: http://localhost:3000

  # Home Assistant
  - hostname: ha.yourdomain.com
    service: http://localhost:8123

  # Catch-all (required — handles unmatched requests)
  - service: http_status:404

The ingress section maps public hostnames to local services. Each hostname becomes a subdomain of your domain that routes to the specified local port.

The catch-all rule at the end is required. It tells cloudflared what to do with requests that don't match any hostname.

Create DNS Records

# Create CNAME records for each hostname
cloudflared tunnel route dns homelab cloud.yourdomain.com
cloudflared tunnel route dns homelab media.yourdomain.com
cloudflared tunnel route dns homelab git.yourdomain.com
cloudflared tunnel route dns homelab ha.yourdomain.com

This creates CNAME records in Cloudflare DNS pointing each subdomain to your tunnel. You can also create these manually in the Cloudflare dashboard — they're CNAME records pointing to <TUNNEL_ID>.cfargotunnel.com.

Start the Tunnel

# Test it
cloudflared tunnel run homelab

# Once it works, install as a system service
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared

Now visit https://cloud.yourdomain.com from anywhere — it routes through Cloudflare to your local Nextcloud instance. HTTPS is handled automatically by Cloudflare; you don't need to set up certificates.

Running cloudflared in Docker

If you prefer Docker:

# docker-compose.yml
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel run homelab
    volumes:
      - ./cloudflared:/etc/cloudflared
    network_mode: host  # Needed to reach services on localhost

Place your config.yml and credentials JSON in the ./cloudflared directory. Using network_mode: host lets cloudflared access services running on localhost ports. Alternatively, point the ingress services at the Docker host's IP instead of localhost.

Cloudflare Access: Adding Authentication

Exposing services to the internet means anyone can reach them. Cloudflare Access adds an authentication layer in front of your tunnel, so only authorized users can access your services.

This is the real power of Cloudflare Tunnel for homelabs. You get enterprise-grade zero-trust authentication in front of your self-hosted services, for free.

Set Up an Access Policy

Go to the Cloudflare Zero Trust dashboard (one.dash.cloudflare.com) and navigate to Access > Applications.

Click Add an application and choose Self-hosted.

Configure the application:

Then add a policy:

Now, when someone visits cloud.yourdomain.com, Cloudflare presents a login page before the request ever reaches your server. Only your email address gets through.

Authentication Methods

Cloudflare Access supports several identity providers:

For a personal homelab, the email one-time PIN is the simplest. Add your email to the allow list, and Cloudflare sends you a verification code whenever you access a service. No passwords to manage.

Bypass for Specific Services

Some services (like Jellyfin) have their own authentication and you might want them publicly accessible. You can create a policy with Action: Bypass for specific applications:

Policy: Public access
Action: Bypass
Include: Everyone

Or set different authentication levels per subdomain — strict access for admin tools, public access for your media server.

Service Tokens for APIs

If you have services that need machine-to-machine communication through the tunnel (webhooks, API calls), create a Service Token in Access:

  1. Go to Access > Service Auth > Service Tokens
  2. Create a token
  3. Use the CF-Access-Client-Id and CF-Access-Client-Secret headers in API requests
curl -H "CF-Access-Client-Id: <CLIENT_ID>" \
     -H "CF-Access-Client-Secret: <CLIENT_SECRET>" \
     https://api.yourdomain.com/webhook

Advanced Configuration

WebSocket Support

Services like Home Assistant, Nextcloud Talk, and code-server use WebSockets. Cloudflare Tunnel supports them natively — no extra configuration needed. Just point the ingress to the service and WebSockets work.

Origin Server Configuration

For services running with self-signed certificates or on HTTPS locally:

ingress:
  - hostname: secure.yourdomain.com
    service: https://localhost:8443
    originRequest:
      noTLSVerify: true  # Skip certificate verification for self-signed certs

Load Balancing Across Multiple Servers

If you run the same service on multiple machines, you can point the same hostname at different origins by running cloudflared on each server with the same tunnel config. Cloudflare will load balance across the connections.

Private Network Access

Beyond HTTP services, Cloudflare Tunnel can route entire IP ranges through the tunnel using WARP (Cloudflare's VPN client):

cloudflared tunnel route ip add 192.168.1.0/24 homelab

Install the WARP client on your devices, and you can access your entire home LAN as if you were on-site. This is similar to what Tailscale provides, but through Cloudflare's network.

Cloudflare Tunnel vs Port Forwarding vs VPN

Feature Cloudflare Tunnel Port Forwarding VPN (WireGuard/Tailscale)
Inbound ports needed None Yes One (WireGuard) or none (Tailscale)
Works through CGNAT Yes No Tailscale yes, WireGuard no
Home IP exposed No Yes WireGuard yes, Tailscale no
HTTPS/SSL Automatic Manual (Let's Encrypt) N/A (private network)
DDoS protection Yes No N/A
Authentication layer Cloudflare Access None (app-level only) VPN auth
Public-facing services Yes Yes No (private access only)
Latency Slight increase (Cloudflare edge hop) Direct Slight increase (tunnel overhead)
Dependency Cloudflare Your ISP/router Tailscale servers or self-hosted
Cost Free Free Free

When to Use Cloudflare Tunnel

When to Use a VPN Instead

When to Use Both

Many homelabbers run Cloudflare Tunnel for public-facing services and Tailscale for private access. This is a solid architecture:

Limitations and Gotchas

HTTP/HTTPS only (mostly): Cloudflare Tunnel is primarily designed for HTTP traffic. TCP and UDP tunneling exist but require WARP on the client side. If you need to expose SSH, game servers, or other non-HTTP services to the public, port forwarding or a VPN is simpler.

Cloudflare's Terms of Service: Cloudflare's free plan TOS historically prohibited using their network as a CDN for serving large amounts of non-HTML content (video streaming). They relaxed this significantly, but if you're streaming terabytes of video through Jellyfin, be aware of the terms. In practice, personal homelab usage is fine, but don't point a popular public video streaming site through a free Cloudflare tunnel.

Added latency: Every request routes through Cloudflare's edge. For services you use from home (same network), this adds unnecessary latency. Use local DNS or split-horizon DNS to access services directly when you're on your home network, and through the tunnel when you're away.

Single point of failure: If Cloudflare has an outage, your tunneled services are unreachable. Cloudflare's uptime is excellent, but it's a dependency you should be aware of. Keep a VPN as backup for critical services.

Upload speed: Your home internet upload bandwidth is still the bottleneck. Cloudflare Tunnel doesn't make your connection faster — it just makes it easier to reach from the outside.

Monitoring Your Tunnel

Check tunnel status from the command line:

# See active connections
cloudflared tunnel info homelab

# Check the Cloudflare dashboard for metrics
# Zero Trust Dashboard > Networks > Tunnels

The Zero Trust dashboard shows request counts, response times, and error rates per service. Set up alerts for tunnel disconnections.

You can also monitor locally:

# cloudflared exposes a metrics endpoint
# Add to your config.yml:
# metrics: 0.0.0.0:60123

# Then scrape with Prometheus or check manually:
curl http://localhost:60123/metrics

Quick Start Summary

For those who just want the commands:

# Install
sudo apt install cloudflared  # or dnf, or Docker

# Authenticate
cloudflared tunnel login

# Create tunnel
cloudflared tunnel create homelab

# Create config (~/.cloudflared/config.yml)
# Map hostnames to local services

# Create DNS records
cloudflared tunnel route dns homelab app.yourdomain.com

# Run as service
sudo cloudflared service install
sudo systemctl enable --now cloudflared

# Add Cloudflare Access policies in the Zero Trust dashboard

Cloudflare Tunnel is one of those tools that feels too good to be free. You get secure, authenticated, DDoS-protected access to your homelab services with no inbound ports, no certificate management, and no exposed home IP. The main trade-off is depending on Cloudflare — but for most homelabbers, that's a trade-off worth making.