Sanitized mirror from private repository - 2026-04-16 07:12:52 UTC
This commit is contained in:
542
docs/infrastructure/cloudflare-tunnels.md
Normal file
542
docs/infrastructure/cloudflare-tunnels.md
Normal file
@@ -0,0 +1,542 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user