Expose localhost on your own domain in one command.
cftunn 3000 dev.example.comcftunn wraps Cloudflare Tunnel and collapses tunnel creation, DNS routing, and ingress config into a single command. Your local server on port 3000 is now live at https://dev.example.com, behind Cloudflare's network.
Raw cloudflared makes you create a tunnel, manage its UUID and credentials file, write an ingress YAML, and add a CNAME by hand — every time. cftunn does all of that from cftunn <port> <domain>.
Why not ngrok? Custom, durable domains. Your URL is dev.example.com, not a random subdomain that changes on restart. Same hostname every run, served over Cloudflare.
- One command — no YAML, no UUIDs, no credentials juggling.
- Automatic DNS — manages the CNAME for you, confirms before overwriting.
- Dual auth — uses your existing
cloudflaredlogin or aCLOUDFLARE_API_TOKEN(CI-friendly). - Self-healing — rotates credentials on existing named tunnels to guarantee a clean connect.
Homebrew (macOS):
brew install thatjuan/tap/cftunncurl (macOS/Linux):
curl -fsSL https://raw.githubusercontent.com/thatjuan/cftunn/main/install.sh | bashRe-run either command to update. Install from source →
1. Install cloudflared (the daemon cftunn drives):
brew install cloudflared # macOSLinux: install guide.
2. Authenticate with Cloudflare — pick one:
-
Interactive login (recommended):
cloudflared tunnel login
Opens a browser, writes
~/.cloudflared/cert.pem. Done once per machine. -
API token (CI/CD):
export CLOUDFLARE_API_TOKEN=<token>
Token needs
Cloudflare Tunnel:Edit(Account),DNS:EditandZone:Read(Zone). How to create one →
You also need the domain on Cloudflare (its nameservers pointed at Cloudflare).
cftunn 3000 dev.example.com # expose localhost:3000
cftunn 8080 dev.example.com --host 1.2.3.4 # expose another host (VM/container)What it does: finds or creates a tunnel named cftunn-dev-example-com, points dev.example.com at it (asking before overwriting an existing record), then forwards traffic to your port.
Flags:
-d, --domain string Domain to expose (e.g. dev.example.com)
-p, --port int Local port to tunnel to
-H, --host string Target host (default: localhost)
-y, --yes Auto-confirm prompts (non-interactive/CI)
-D, --debug Verbose output for troubleshooting
-v, --version Print version
Hitting problems? Run with --debug for auth detection, cloudflared commands, and API call traces.
Skip this if you use interactive login. For API-token auth, mint a token with these scopes:
| Scope | Permission | Why |
|---|---|---|
| Account | Cloudflare Tunnel:Edit |
Create/delete tunnels. |
| Zone | DNS:Edit |
Manage the CNAME. |
| Zone | Zone:Read |
Resolve your domain's zone ID. |
- Open dash.cloudflare.com/profile/api-tokens.
- Create Token → Create Custom Token.
- Add the three permissions above.
- Account Resources: Include → your account. Zone Resources: Include → All zones (or one zone).
- Continue → Create Token, copy it.
export CLOUDFLARE_API_TOKEN=<token>and verify:cftunn auth whoami.
cftunncan also mint its own scoped token from a privileged bootstrap token — see Advanced. The dashboard step above is unavoidable for your first token: Cloudflare has no zero-state token API.
Community project, not affiliated with Cloudflare. Use at your own risk.