___ ___ _______ __
| | |_ _|.--.--.-----.-----.-----.| |
\ / | | | | | | | -__|| |
|___| |___| |_____|__|__|__|__|_____||__|
Cloudflare tunnels made easy!
A TUI-first CLI for managing Cloudflare Tunnels with custom domains. Think ngrok, but using your own Cloudflare domain with persistent URLs and a dashboard to manage them.
Supported Platforms: macOS and Linux
- TUI Dashboard - Interactive interface to manage all your tunnels
- Live Metrics - Real-time request counts, error rates, and connection status
- Persistent tunnels - Tunnels run as background daemons (launchd on macOS, systemd on Linux)
- Automatic DNS management - Creates and updates CNAME records automatically
- Multi-account support - Manage tunnels across multiple Cloudflare accounts
- Multi-zone support - Use different domains for different tunnels
- SSL/HTTPS - Automatic via Cloudflare
- Ephemeral mode - Quick one-off tunnels with automatic cleanup on exit
-
cloudflared - Cloudflare's tunnel daemon
macOS:
brew install cloudflare/cloudflare/cloudflared
Linux (Debian/Ubuntu):
curl -L https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive-keyring.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
Linux (other):
# Download the latest release from https://github.com/cloudflare/cloudflared/releases sudo cp cloudflared /usr/local/bin/ sudo chmod +x /usr/local/bin/cloudflaredNote: You only need to install cloudflared. Do NOT run it as a system service. YTunnel manages cloudflared processes directly.
-
Cloudflare API Token with these permissions:
- Zone → Zone → Edit
- Zone → DNS → Edit
- Account → Cloudflare Tunnel → Edit
Create one at: https://dash.cloudflare.com/profile/api-tokens
-
A domain managed by Cloudflare (free tier works)
brew install yetidevworks/ytunnel/ytunnelcargo install ytunnelgit clone https://github.com/yetidevworks/ytunnel
cd ytunnel
cargo install --path .Download from GitHub Releases.
# First-time setup
ytunnel init
# Open the TUI dashboard
ytunnel
# Or add a tunnel directly from CLI
ytunnel add myapp localhost:3000 --startYTunnel is a management tool, not a daemon itself. Here's how the pieces fit together:
┌─────────────────────────────────────────────────────────────────┐
│ ytunnel (CLI/TUI) │
│ Management tool - runs only when you invoke it │
└─────────────────────────────────────────────────────────────────┘
│
Creates & manages configs for
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ launchd (macOS) / systemd (Linux) │
│ System service manager - always running │
│ │
│ macOS: ~/Library/LaunchAgents/com.ytunnel.<account>.<name>.plist │
│ Linux: ~/.config/systemd/user/ytunnel-<account>-<name>.service │
└─────────────────────────────────────────────────────────────────┘
│
Starts/stops/monitors
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ cloudflared processes │
│ One process per tunnel - runs in background │
│ │
│ • cloudflared tunnel --config myapp.yml run │
│ • cloudflared tunnel --config api.yml run │
└─────────────────────────────────────────────────────────────────┘
│
Connects to
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Cloudflare Edge │
│ Routes traffic to your tunnels │
└─────────────────────────────────────────────────────────────────┘
| Mode | How it runs | Survives reboot? | Use case |
|---|---|---|---|
Ephemeral (ytunnel run) |
Foreground process | No | Quick testing, one-off tunnels |
Persistent (ytunnel add --start) |
launchd/systemd | Yes* | Production, always-on services |
*Tunnels don't auto-start by default. They start when you run ytunnel start and keep running until you ytunnel stop or reboot. To auto-start on login, press A in the TUI to toggle auto-start (⟳ indicator shows when enabled).
When you run ytunnel add myapp localhost:3000 --start:
- Cloudflare Tunnel - Created via API, persists in your Cloudflare account
- DNS Record - CNAME pointing
myapp.yourdomain.com→ tunnel - Credentials - Tunnel credentials JSON file
- Config - cloudflared YAML config file
- Service - launchd plist (macOS) or systemd unit (Linux)
- State - Entry in
tunnels.toml
The service file tells launchd/systemd to run cloudflared with your config. Logs go to the logs directory.
Run ytunnel with no arguments to open the interactive dashboard:
┌─ Tunnels (3) ─────────────────────┬─ Logs: myapp ─────────────────────────────────┐
│ ● myapp myapp.example.com │ 2024-01-20 10:30:15 INF Starting tunnel │
│ ● api api.example.com │ 2024-01-20 10:30:16 INF Connection registered │
│ ○ staging staging.example.com │ 2024-01-20 10:30:17 INF Tunnel connected │
│ │ 2024-01-20 10:30:18 INF Route propagated │
│ │ 2024-01-20 10:30:21 INF Request served GET / │
│ ├─ Metrics ─────────────────────────────────────┤
│ │ Requests: 1,247 Errors: 3 Active: 2 │
│ │ Health: ✓ healthy │
│ │ HA Connections: 4 Edge: dfw08, den01 │
│ │ Status Codes: 200:1198 304:42 404:3 500:4 │
│ │ Traffic: ▁▂▃▅▆▄▃▂▁▂▃▄▅▆▇█▆▅▄▃▂▁▂▃▄▅▆▇ │
├───────────────────────────────────┴───────────────────────────────────────────────┤
│ Started myapp │
│ [a]dd [s]tart [S]top [R]estart [c]opy [o]pen [h]ealth [d]elete [r]efresh [q]uit │
└───────────────────────────────────────────────────────────────────────────────────┘
Status indicators:
●Running (green)○Stopped (yellow)✗Error (red)⟳Auto-start enabled (cyan, shown after hostname)
Keyboard shortcuts:
| Key | Action |
|---|---|
a |
Add a new tunnel |
s |
Start selected tunnel |
S |
Stop selected tunnel |
R |
Restart tunnel (updates daemon config) |
c |
Copy tunnel URL to clipboard |
o |
Open tunnel URL in browser |
h |
Check tunnel health |
A |
Toggle auto-start on login (⟳ = enabled) |
d |
Delete selected tunnel |
m |
Import ephemeral tunnel as managed |
; |
Cycle through accounts (when multiple configured) |
r |
Refresh status |
↑/↓ or j/k |
Navigate list |
q |
Quit |
Tunnels continue running in the background after you close the TUI.
For running tunnels, the TUI displays live metrics from cloudflared's Prometheus endpoint:
- Requests - Total requests handled by the tunnel
- Errors - Number of failed requests (red if > 0)
- Active - Currently in-flight concurrent requests
- Health - Whether the tunnel URL is reachable (✓ healthy / ✗ unreachable)
- HA Connections - Number of connections to Cloudflare edge (4 = healthy)
- Edge - Cloudflare edge locations (e.g.,
dfw08= Dallas) - Status Codes - Breakdown of HTTP response codes
- Traffic - Sparkline showing request rate over time
Metrics auto-refresh every 5 seconds. Health checks run every 30 seconds. Use h for immediate health check.
When a tunnel goes down or comes back up, ytunnel sends a system notification. This helps you catch issues even when the TUI isn't visible.
- macOS: Uses
terminal-notifier(if installed) orosascript - Linux: Uses
notify-send(requireslibnotify)
Ephemeral tunnels (created with ytunnel run) also appear in the TUI marked as [ephemeral]. You can:
- View them alongside managed tunnels
- Delete them from Cloudflare
- Import them as managed tunnels (press
m) to add daemon control
Any other tunnel on your Cloudflare account shows up marked [not managed by ytunnel], in both ytunnel list and the TUI. This covers tunnels you made in the Cloudflare dashboard or by hand with cloudflared, including ones whose cloudflared runs on a completely different machine.
These are read-only. ytunnel has no local config, credentials, or daemon for them, so it can't start, stop, edit, import, or delete them. The status shown comes from Cloudflare (healthy / degraded / down / inactive), not from this machine, and the destination and hostname read as unknown because that config lives wherever cloudflared is actually running.
Note: ytunnel manages
cloudflaredprocesses on the machine it runs on. It is not a remote control for acloudflaredrunning elsewhere. Tunnels on other machines are listed so you can see them, nothing more.
# Add a tunnel (doesn't start it)
ytunnel add myapp localhost:3000
# Add and start immediately
ytunnel add myapp localhost:3000 --start
# Use a specific zone
ytunnel add api localhost:8080 -z dev.example.com
# Start/stop/restart tunnels
ytunnel start myapp
ytunnel stop myapp
ytunnel restart myapp # Stop, update config, start
# View logs
ytunnel logs myapp # Last 50 lines
ytunnel logs myapp -n 100 # Last 100 lines
ytunnel logs myapp -f # Follow (like tail -f)
# List all tunnels with status
ytunnel list
# Delete a tunnel
ytunnel delete myapp
# Reset all configuration (start fresh)
ytunnel reset
ytunnel reset -y # Skip confirmationFor quick one-off tunnels that stop when you press Ctrl+C:
# Auto-generated subdomain (ytunnel-abc123.example.com)
ytunnel run localhost:3000
# Named subdomain (myapp.example.com)
ytunnel run myapp localhost:3000
# Different zone
ytunnel run api -z dev.example.com localhost:8080# Add a new account (interactive)
ytunnel init
# List all configured accounts
ytunnel account list
# Set the default account
ytunnel account select production
# or
ytunnel account default production
# Remove an account
ytunnel account remove old-account
# Use a specific account for any command
ytunnel add myapp localhost:3000 --account production
ytunnel list --account dev# List available zones
ytunnel zones
# Change default zone
ytunnel zones default dev.example.com# Report leftovers on the selected account
ytunnel doctor
# Check every configured account
ytunnel doctor --all
# Delete what it finds (prompts first)
ytunnel doctor --fix
# Skip the prompt
ytunnel doctor --fix --yesdoctor looks for two things:
- Orphaned DNS records - CNAMEs still pointing at
<tunnel-id>.cfargotunnel.comfor a tunnel that no longer exists. These resolve to nothing. They're usually left over from aytunnel runthat was killed rather than stopped cleanly, or from an older ytunnel that didn't clean up DNS on delete - Stale local state - tunnels in
tunnels.tomlwhose Cloudflare tunnel has been deleted elsewhere
Nothing is removed without --fix, and --fix prompts before deleting unless you pass --yes.
macOS:
| Path | Purpose |
|---|---|
~/Library/Application Support/ytunnel/config.toml |
API credentials and zones |
~/Library/Application Support/ytunnel/tunnels.toml |
Persistent tunnel state |
~/Library/Application Support/ytunnel/<tunnel-id>.json |
Cloudflare tunnel credentials |
~/Library/Application Support/ytunnel/tunnel-configs/<name>.yml |
cloudflared config files |
~/Library/Application Support/ytunnel/logs/<name>.log |
Tunnel daemon logs |
~/Library/LaunchAgents/com.ytunnel.<account>.<name>.plist |
launchd service files |
Linux:
| Path | Purpose |
|---|---|
~/.config/ytunnel/config.toml |
API credentials and zones |
~/.config/ytunnel/tunnels.toml |
Persistent tunnel state |
~/.config/ytunnel/<tunnel-id>.json |
Cloudflare tunnel credentials |
~/.config/ytunnel/tunnel-configs/<name>.yml |
cloudflared config files |
~/.config/ytunnel/logs/<name>.log |
Tunnel daemon logs |
~/.config/systemd/user/ytunnel-<account>-<name>.service |
systemd service files |
Config file location: ~/Library/Application Support/ytunnel/config.toml (macOS) or ~/.config/ytunnel/config.toml (Linux):
selected_account = "dev"
[[accounts]]
name = "dev"
api_token = "your-token"
account_id = "your-account-id"
default_zone_id = "zone-id"
default_zone_name = "example.com"
[[accounts.zones]]
id = "zone-id"
name = "example.com"
[[accounts]]
name = "production"
api_token = "another-token"
account_id = "another-account-id"
default_zone_id = "prod-zone-id"
default_zone_name = "mysite.io"
[[accounts.zones]]
id = "prod-zone-id"
name = "mysite.io"tunnels.toml (same directory as config.toml):
[[tunnels]]
name = "myapp"
account_name = "dev"
target = "localhost:3000"
zone_id = "abc123"
zone_name = "example.com"
hostname = "myapp.example.com"
tunnel_id = "cf-tunnel-id"
enabled = true
auto_start = false # Set to true to start on login# Via ytunnel
ytunnel list
# Via system service manager
launchctl list | grep ytunnel # macOS
systemctl --user list-units 'ytunnel-*' # Linux# In TUI: select tunnel and view right pane
# Or directly
tail -f ~/Library/Application\ Support/ytunnel/logs/myapp.log # macOS
tail -f ~/.config/ytunnel/logs/myapp.log # Linux- Check if cloudflared is installed:
cloudflared --version - Check the log file for errors
- Verify credentials exist in the config directory
- Try running manually:
cloudflared tunnel --config <config-path> run
macOS:
# Stop (replace <account> with your account name, e.g., "dev")
launchctl unload ~/Library/LaunchAgents/com.ytunnel.<account>.myapp.plist
# Start
launchctl load ~/Library/LaunchAgents/com.ytunnel.<account>.myapp.plist
# Remove completely
launchctl unload ~/Library/LaunchAgents/com.ytunnel.<account>.myapp.plist
rm ~/Library/LaunchAgents/com.ytunnel.<account>.myapp.plistLinux:
# Stop (replace <account> with your account name, e.g., "dev")
systemctl --user stop ytunnel-<account>-myapp.service
# Start
systemctl --user start ytunnel-<account>-myapp.service
# Remove completely
systemctl --user stop ytunnel-<account>-myapp.service
systemctl --user disable ytunnel-<account>-myapp.service
rm ~/.config/systemd/user/ytunnel-<account>-myapp.service
systemctl --user daemon-reloadMIT
