543 lines
13 KiB
Markdown
543 lines
13 KiB
Markdown
# Cloudflare Tunnels Guide
|
|
|
|
**Last Updated:** 2026-01-29
|
|
|
|
This guide covers how to use Cloudflare Tunnels (cloudflared) to expose local services to the internet securely, without opening ports on your router.
|
|
|
|
## Table of Contents
|
|
|
|
- [What is Cloudflared?](#what-is-cloudflared)
|
|
- [Quick Temporary Tunnel](#quick-temporary-tunnel-no-account-needed)
|
|
- [Named Tunnel Setup](#named-tunnel-setup)
|
|
- [Docker Compose Setup](#docker-compose-setup-recommended)
|
|
- [Adding Authentication](#adding-authentication-cloudflare-access)
|
|
- [Common Use Cases](#common-use-cases)
|
|
- [Troubleshooting](#troubleshooting)
|
|
|
|
---
|
|
|
|
## What is Cloudflared?
|
|
|
|
**Cloudflared** is Cloudflare's tunnel client that creates a secure, encrypted connection between your local machine and Cloudflare's edge network. It allows you to expose local services to the internet **without opening ports on your router** or having a public IP.
|
|
|
|
### How It Works
|
|
|
|
```
|
|
Your Local Service → cloudflared → Cloudflare Edge → Public URL → Visitor's Browser
|
|
(port 8080) (outbound) (proxy/CDN) (your domain)
|
|
```
|
|
|
|
**Key insight:** cloudflared makes an OUTBOUND connection to Cloudflare, so you don't need to configure any firewall rules or port forwarding.
|
|
|
|
### Benefits
|
|
|
|
- ✅ No port forwarding required
|
|
- ✅ DDoS protection via Cloudflare
|
|
- ✅ Free SSL certificates
|
|
- ✅ Optional authentication (Cloudflare Access)
|
|
- ✅ Works behind CGNAT
|
|
- ✅ Multiple services on one tunnel
|
|
|
|
---
|
|
|
|
## Quick Temporary Tunnel (No Account Needed)
|
|
|
|
This is the fastest way to share something temporarily. No Cloudflare account required.
|
|
|
|
### Option 1: Using Docker (Easiest)
|
|
|
|
```bash
|
|
# Expose a local service running on port 8080
|
|
docker run --rm -it --network host cloudflare/cloudflared:latest tunnel --url http://localhost:8080
|
|
|
|
# Examples for specific services:
|
|
# Jellyfin
|
|
docker run --rm -it --network host cloudflare/cloudflared:latest tunnel --url http://localhost:8096
|
|
|
|
# Grafana
|
|
docker run --rm -it --network host cloudflare/cloudflared:latest tunnel --url http://localhost:3000
|
|
|
|
# Any web service
|
|
docker run --rm -it --network host cloudflare/cloudflared:latest tunnel --url http://localhost:PORT
|
|
```
|
|
|
|
### Option 2: Install cloudflared Directly
|
|
|
|
```bash
|
|
# On Debian/Ubuntu
|
|
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
|
|
sudo dpkg -i cloudflared.deb
|
|
|
|
# On macOS
|
|
brew install cloudflared
|
|
|
|
# On Windows (PowerShell)
|
|
winget install Cloudflare.cloudflared
|
|
|
|
# Then run:
|
|
cloudflared tunnel --url http://localhost:8080
|
|
```
|
|
|
|
### What You'll See
|
|
|
|
```
|
|
INF Thank you for trying Cloudflare Tunnel...
|
|
INF Your quick Tunnel has been created! Visit it at:
|
|
INF https://random-words-here.trycloudflare.com
|
|
```
|
|
|
|
Share that URL with your friend! When done, press **Ctrl+C** to close the tunnel.
|
|
|
|
### Quick Tunnel Limitations
|
|
|
|
- URL changes every time you restart
|
|
- No authentication
|
|
- No uptime guarantee
|
|
- Single service per tunnel
|
|
|
|
---
|
|
|
|
## Named Tunnel Setup
|
|
|
|
Named tunnels give you a **permanent, custom URL** on your own domain with optional authentication.
|
|
|
|
### Prerequisites
|
|
|
|
- Cloudflare account (free tier works)
|
|
- Domain on Cloudflare DNS (e.g., vish.gg, thevish.io)
|
|
- cloudflared installed
|
|
|
|
### Step 1: Install cloudflared
|
|
|
|
```bash
|
|
# For Synology/Debian/Ubuntu:
|
|
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
|
|
chmod +x /usr/local/bin/cloudflared
|
|
|
|
# Verify installation
|
|
cloudflared --version
|
|
```
|
|
|
|
### Step 2: Authenticate with Cloudflare
|
|
|
|
```bash
|
|
cloudflared tunnel login
|
|
```
|
|
|
|
This will:
|
|
1. Open a browser (or provide a URL to visit)
|
|
2. Ask you to log into Cloudflare
|
|
3. Select which domain to authorize
|
|
4. Save a certificate to `~/.cloudflared/cert.pem`
|
|
|
|
### Step 3: Create a Named Tunnel
|
|
|
|
```bash
|
|
# Create a tunnel named "homelab"
|
|
cloudflared tunnel create homelab
|
|
```
|
|
|
|
Output:
|
|
```
|
|
Created tunnel homelab with id a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
|
```
|
|
|
|
**Save that UUID!** It's your tunnel's unique identifier.
|
|
|
|
This also creates a credentials file at:
|
|
`~/.cloudflared/<TUNNEL_UUID>.json`
|
|
|
|
### Step 4: Create a Config File
|
|
|
|
Create `~/.cloudflared/config.yml`:
|
|
|
|
```yaml
|
|
# Tunnel UUID (from step 3)
|
|
tunnel: a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
|
credentials-file: /root/.cloudflared/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json
|
|
|
|
# Route traffic to local services
|
|
ingress:
|
|
# Jellyfin at jellyfin.vish.gg
|
|
- hostname: jellyfin.vish.gg
|
|
service: http://localhost:8096
|
|
|
|
# Paperless at docs.vish.gg
|
|
- hostname: docs.vish.gg
|
|
service: http://localhost:8000
|
|
|
|
# Grafana at grafana.vish.gg
|
|
- hostname: grafana.vish.gg
|
|
service: http://localhost:3000
|
|
|
|
# SSH access at ssh.vish.gg
|
|
- hostname: ssh.vish.gg
|
|
service: ssh://localhost:22
|
|
|
|
# Catch-all (required) - returns 404 for unmatched hostnames
|
|
- service: http_status:404
|
|
```
|
|
|
|
### Step 5: Create DNS Routes
|
|
|
|
For each hostname, create a DNS record pointing to your tunnel:
|
|
|
|
```bash
|
|
# Automatically create CNAME records
|
|
cloudflared tunnel route dns homelab jellyfin.vish.gg
|
|
cloudflared tunnel route dns homelab docs.vish.gg
|
|
cloudflared tunnel route dns homelab grafana.vish.gg
|
|
cloudflared tunnel route dns homelab ssh.vish.gg
|
|
```
|
|
|
|
This creates CNAME records pointing to `<TUNNEL_UUID>.cfargotunnel.com`
|
|
|
|
### Step 6: Run the Tunnel
|
|
|
|
```bash
|
|
# Test it first
|
|
cloudflared tunnel run homelab
|
|
|
|
# Or run with specific config file
|
|
cloudflared tunnel --config ~/.cloudflared/config.yml run homelab
|
|
```
|
|
|
|
### Step 7: Run as a Service (Persistent)
|
|
|
|
```bash
|
|
# Install as a systemd service
|
|
sudo cloudflared service install
|
|
|
|
# Start and enable
|
|
sudo systemctl start cloudflared
|
|
sudo systemctl enable cloudflared
|
|
|
|
# Check status
|
|
sudo systemctl status cloudflared
|
|
|
|
# View logs
|
|
sudo journalctl -u cloudflared -f
|
|
```
|
|
|
|
---
|
|
|
|
## Docker Compose Setup (Recommended)
|
|
|
|
For homelab use, running cloudflared as a Docker container is recommended.
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
cloudflared/
|
|
├── docker-compose.yml
|
|
├── config.yml
|
|
└── credentials.json # Copy from ~/.cloudflared/<UUID>.json
|
|
```
|
|
|
|
### docker-compose.yml
|
|
|
|
```yaml
|
|
version: "3.9"
|
|
services:
|
|
cloudflared:
|
|
image: cloudflare/cloudflared:latest
|
|
container_name: cloudflared
|
|
restart: unless-stopped
|
|
command: tunnel --config /etc/cloudflared/config.yml run
|
|
volumes:
|
|
- ./config.yml:/etc/cloudflared/config.yml:ro
|
|
- ./credentials.json:/etc/cloudflared/credentials.json:ro
|
|
networks:
|
|
- homelab
|
|
|
|
networks:
|
|
homelab:
|
|
external: true
|
|
```
|
|
|
|
### config.yml (Docker version)
|
|
|
|
```yaml
|
|
tunnel: a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
|
credentials-file: /etc/cloudflared/credentials.json
|
|
|
|
ingress:
|
|
# Use container names when on same Docker network
|
|
- hostname: jellyfin.vish.gg
|
|
service: http://jellyfin:8096
|
|
|
|
- hostname: paperless.vish.gg
|
|
service: http://paperless-ngx:8000
|
|
|
|
- hostname: grafana.vish.gg
|
|
service: http://grafana:3000
|
|
|
|
# For services on the host network, use host IP
|
|
- hostname: portainer.vish.gg
|
|
service: http://192.168.0.200:9000
|
|
|
|
# Catch-all (required)
|
|
- service: http_status:404
|
|
```
|
|
|
|
### Deploy
|
|
|
|
```bash
|
|
cd cloudflared
|
|
docker-compose up -d
|
|
|
|
# Check logs
|
|
docker logs -f cloudflared
|
|
```
|
|
|
|
---
|
|
|
|
## Adding Authentication (Cloudflare Access)
|
|
|
|
Protect services with Cloudflare Access (free for up to 50 users).
|
|
|
|
### Setup via Dashboard
|
|
|
|
1. Go to **Cloudflare Dashboard** → **Zero Trust** → **Access** → **Applications**
|
|
|
|
2. Click **Add an Application** → **Self-hosted**
|
|
|
|
3. Configure:
|
|
- **Application name**: Grafana
|
|
- **Session duration**: 24 hours
|
|
- **Application domain**: `grafana.vish.gg`
|
|
|
|
4. Create a **Policy**:
|
|
- **Policy name**: Allow Me
|
|
- **Action**: Allow
|
|
- **Include**:
|
|
- Emails: `your-email@gmail.com`
|
|
- Or Emails ending in: `@yourdomain.com`
|
|
|
|
5. Save the application
|
|
|
|
### How It Works
|
|
|
|
```
|
|
Friend visits grafana.vish.gg
|
|
→ Cloudflare Access login page
|
|
→ Enters email
|
|
→ Receives one-time PIN via email
|
|
→ Enters PIN
|
|
→ Authenticated → Sees Grafana
|
|
```
|
|
|
|
### Authentication Options
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| One-time PIN | Email-based OTP (default) |
|
|
| Google/GitHub/etc. | OAuth integration |
|
|
| SAML/OIDC | Enterprise SSO |
|
|
| Service Token | For API/automated access |
|
|
| mTLS | Certificate-based |
|
|
|
|
---
|
|
|
|
## Common Use Cases
|
|
|
|
### Share Jellyfin for Movie Night
|
|
|
|
```bash
|
|
# Quick tunnel (temporary)
|
|
docker run --rm -it --network host cloudflare/cloudflared:latest tunnel --url http://localhost:8096
|
|
|
|
# Named tunnel (permanent)
|
|
# Add to config.yml:
|
|
# - hostname: watch.vish.gg
|
|
# service: http://localhost:8096
|
|
```
|
|
|
|
### Expose SSH Access
|
|
|
|
```yaml
|
|
# In config.yml
|
|
ingress:
|
|
- hostname: ssh.vish.gg
|
|
service: ssh://localhost:22
|
|
```
|
|
|
|
Client connects via:
|
|
```bash
|
|
# Install cloudflared on client
|
|
cloudflared access ssh --hostname ssh.vish.gg
|
|
```
|
|
|
|
Or configure SSH config (`~/.ssh/config`):
|
|
```
|
|
Host ssh.vish.gg
|
|
ProxyCommand cloudflared access ssh --hostname %h
|
|
```
|
|
|
|
### Expose RDP/VNC
|
|
|
|
```yaml
|
|
ingress:
|
|
- hostname: rdp.vish.gg
|
|
service: rdp://localhost:3389
|
|
|
|
- hostname: vnc.vish.gg
|
|
service: tcp://localhost:5900
|
|
```
|
|
|
|
### Multiple Services Example
|
|
|
|
```yaml
|
|
tunnel: your-tunnel-uuid
|
|
credentials-file: /etc/cloudflared/credentials.json
|
|
|
|
ingress:
|
|
# Media
|
|
- hostname: jellyfin.vish.gg
|
|
service: http://jellyfin:8096
|
|
- hostname: plex.vish.gg
|
|
service: http://plex:32400
|
|
|
|
# Productivity
|
|
- hostname: paperless.vish.gg
|
|
service: http://paperless:8000
|
|
- hostname: wiki.vish.gg
|
|
service: http://dokuwiki:80
|
|
|
|
# Development
|
|
- hostname: git.vish.gg
|
|
service: http://gitea:3000
|
|
- hostname: code.vish.gg
|
|
service: http://code-server:8080
|
|
|
|
# Monitoring
|
|
- hostname: grafana.vish.gg
|
|
service: http://grafana:3000
|
|
- hostname: uptime.vish.gg
|
|
service: http://uptime-kuma:3001
|
|
|
|
# Catch-all
|
|
- service: http_status:404
|
|
```
|
|
|
|
---
|
|
|
|
## Reference Commands
|
|
|
|
```bash
|
|
# Authentication
|
|
cloudflared tunnel login # Authenticate with Cloudflare
|
|
cloudflared tunnel logout # Remove authentication
|
|
|
|
# Tunnel Management
|
|
cloudflared tunnel list # List all tunnels
|
|
cloudflared tunnel info <name> # Get tunnel details
|
|
cloudflared tunnel create <name> # Create new tunnel
|
|
cloudflared tunnel delete <name> # Delete tunnel (must stop first)
|
|
|
|
# DNS Routes
|
|
cloudflared tunnel route dns <tunnel> <hostname> # Create DNS route
|
|
cloudflared tunnel route dns list # List all routes
|
|
|
|
# Running Tunnels
|
|
cloudflared tunnel run <name> # Run tunnel
|
|
cloudflared tunnel --config config.yml run # Run with config
|
|
cloudflared tunnel ingress validate # Validate config
|
|
|
|
# Debugging
|
|
cloudflared tunnel --loglevel debug run <name> # Debug logging
|
|
cloudflared tunnel info <name> # Tunnel info
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Tunnel won't start
|
|
|
|
```bash
|
|
# Check config syntax
|
|
cloudflared tunnel ingress validate
|
|
|
|
# Run with debug logging
|
|
cloudflared tunnel --loglevel debug run homelab
|
|
```
|
|
|
|
### DNS not resolving
|
|
|
|
```bash
|
|
# Verify DNS route exists
|
|
cloudflared tunnel route dns list
|
|
|
|
# Check CNAME in Cloudflare dashboard
|
|
# Should point to: <UUID>.cfargotunnel.com
|
|
```
|
|
|
|
### Service unreachable
|
|
|
|
1. **Check service is running locally:**
|
|
```bash
|
|
curl http://localhost:8080
|
|
```
|
|
|
|
2. **Check Docker networking:**
|
|
- If using container names, ensure same Docker network
|
|
- If using localhost, use `--network host` or host IP
|
|
|
|
3. **Check ingress rules order:**
|
|
- More specific rules should come before catch-all
|
|
- Catch-all (`http_status:404`) must be last
|
|
|
|
### Certificate errors
|
|
|
|
```bash
|
|
# Re-authenticate
|
|
cloudflared tunnel login
|
|
|
|
# Check cert exists
|
|
ls -la ~/.cloudflared/cert.pem
|
|
```
|
|
|
|
### View tunnel metrics
|
|
|
|
Cloudflare provides metrics at:
|
|
- Dashboard → Zero Trust → Tunnels → Select tunnel → Metrics
|
|
|
|
---
|
|
|
|
## Quick vs Named Tunnel Comparison
|
|
|
|
| Feature | Quick Tunnel | Named Tunnel |
|
|
|---------|--------------|--------------|
|
|
| URL | `random.trycloudflare.com` | `app.yourdomain.com` |
|
|
| Cloudflare Account | ❌ Not needed | ✅ Required |
|
|
| Persistence | ❌ Dies with process | ✅ Permanent |
|
|
| Custom domain | ❌ No | ✅ Yes |
|
|
| Multiple services | ❌ One per tunnel | ✅ Many via ingress |
|
|
| Authentication | ❌ None | ✅ Cloudflare Access |
|
|
| Setup time | 10 seconds | 10 minutes |
|
|
| Best for | Quick demos | Production |
|
|
|
|
---
|
|
|
|
## Security Best Practices
|
|
|
|
1. **Always use HTTPS** - Cloudflare handles this automatically
|
|
2. **Enable Cloudflare Access** for sensitive services
|
|
3. **Use service tokens** for automated/API access
|
|
4. **Monitor tunnel logs** for suspicious activity
|
|
5. **Rotate credentials** periodically
|
|
6. **Limit ingress rules** to only what's needed
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [Cloudflare Tunnel Docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/)
|
|
- [Cloudflare Access Docs](https://developers.cloudflare.com/cloudflare-one/policies/access/)
|
|
- [Zero Trust Dashboard](https://one.dash.cloudflare.com/)
|
|
|
|
---
|
|
|
|
*Last Updated: 2026-01-29*
|