cftunnel
/install cftunnel
cftunnel — Cloudflare Tunnel Manager for AI Agents
Expose local services to the internet via Cloudflare Tunnels. Use this skill when you need to make a locally running application accessible at a public HTTPS URL.
Flow: Start a local service (e.g. on port 3000) → cftunnel creates a tunnel + DNS route → the service is live at https://hostname.domain.com.
Authentication
Set these environment variables before running any command:
# Option A: API Key + Email (most common)
export CLOUDFLARE_API_KEY=\x3Capi-key>
export CLOUDFLARE_EMAIL=\x3Caccount-email>
# Option B: API Token (scoped, if available)
export CLOUDFLARE_API_TOKEN=\x3Capi-token>
# Always required:
export CLOUDFLARE_ACCOUNT_ID=\x3Caccount-id>
Quick Reference
Expose a local service (fastest path)
If a tunnel and cloudflared are already running (check with tunnel list), just add a route and DNS:
# 1. Add ingress route to existing tunnel
npx cftunnel route add \x3Ctunnel-id> --hostname \x3Chostname> --service http://localhost:\x3Cport>
# 2. Create DNS CNAME
npx cftunnel dns create --zone-id \x3Czone-id> --hostname \x3Chostname> --tunnel-id \x3Ctunnel-id>
Create everything from scratch (one command)
npx cftunnel quickstart \
--name \x3Ctunnel-name> \
--hostname \x3Chostname> \
--service http://localhost:\x3Cport> \
--zone-id \x3Czone-id>
Then run the connector: npx cftunnel run \x3Ctunnel-id>
Programmatic usage (Node.js library)
import { createClient, quickstart } from 'cftunnel';
const client = createClient({ apiKey: '...', apiEmail: '...' });
const result = await quickstart(client, {
accountId: '...',
name: 'my-app',
hostname: 'app.example.com',
service: 'http://localhost:3000',
zoneId: '...',
});
console.log(result.run_cmd);
All Commands
Tunnel lifecycle
| Command | Purpose |
|---|---|
npx cftunnel tunnel list |
List all tunnels. Find existing tunnel IDs and check status (healthy/down/inactive). |
npx cftunnel tunnel create \x3Cname> |
Create a new tunnel. Returns tunnel ID and secret. Add --config-src local for YAML-managed config. |
npx cftunnel tunnel get \x3Ctunnel-id> |
Get tunnel details including connection status. |
npx cftunnel tunnel delete \x3Ctunnel-id> |
Delete a tunnel. Must have no active connections. |
npx cftunnel tunnel token \x3Ctunnel-id> |
Get the token needed to run cloudflared. Returns run_cmd and install_cmd. |
Ingress routes (hostname → local service mapping)
| Command | Purpose |
|---|---|
npx cftunnel route list \x3Ctunnel-id> |
Show current ingress rules. Always has a catch-all 404 as last rule. |
npx cftunnel route add \x3Ctunnel-id> --hostname \x3Chost> --service \x3Curl> |
Add a route. Preserves existing routes, appends before catch-all. Optional --path for path filtering. |
npx cftunnel route remove \x3Ctunnel-id> --hostname \x3Chost> |
Remove a route by hostname. |
npx cftunnel route set \x3Ctunnel-id> --route host1=svc1 --route host2=svc2 |
Replace ALL routes. Use for bulk configuration. |
DNS records
| Command | Purpose |
|---|---|
npx cftunnel dns create --zone-id \x3Czid> --hostname \x3Chost> --tunnel-id \x3Ctid> |
Create proxied CNAME pointing hostname to tunnel. Required for the hostname to resolve. |
npx cftunnel dns list --zone-id \x3Czid> |
List all DNS records in the zone. |
npx cftunnel dns delete \x3Crecord-id> --zone-id \x3Czid> |
Delete a DNS record. |
Running the connector
| Command | Purpose |
|---|---|
npx cftunnel run \x3Ctunnel-id> |
Run cloudflared in foreground. Auto-detects cloudflared from PATH or npm package. |
npx cftunnel run \x3Ctunnel-id> --install-service |
Install cloudflared as a persistent system service (survives reboots). |
Decision Guide
"I need to expose port N on a domain"
→ Check tunnel list for a healthy tunnel. If one exists, use route add + dns create. If not, use quickstart.
"I need to add another service to an existing tunnel"
→ route add + dns create. One tunnel can serve many hostnames.
"I need to change where a hostname points"
→ route remove --hostname X then route add --hostname X --service \x3Cnew-url>. DNS stays the same.
"I need to take a service offline"
→ route remove --hostname X and optionally dns delete \x3Crecord-id>.
"The tunnel exists but cloudflared isn't running"
→ npx cftunnel run \x3Ctunnel-id> or use tunnel token to get the token for manual cloudflared invocation.
Service URL Formats
The --service flag accepts these protocols:
| Format | Example | Use case |
|---|---|---|
http://host:port |
http://localhost:3000 |
HTTP web apps, APIs |
https://host:port |
https://localhost:8443 |
HTTPS backends |
tcp://host:port |
tcp://localhost:5432 |
Databases, raw TCP |
ssh://host:port |
ssh://localhost:22 |
SSH access |
unix:///path |
unix:///tmp/app.sock |
Unix socket apps |
http_status:CODE |
http_status:404 |
Static status response (catch-all) |
Output Format
All commands output JSON to stdout. Progress/errors go to stderr.
Parse with jq:
TUNNEL_ID=$(npx cftunnel tunnel create my-app | jq -r '.id')
TOKEN=$(npx cftunnel tunnel token $TUNNEL_ID | jq -r '.token')
Common Patterns
Pattern 1: Deploy a new web app
cd /path/to/app && npm start &
npx cftunnel quickstart \
--name my-web-app \
--hostname app.example.com \
--service http://localhost:8080 \
--zone-id \x3Czone-id>
npx cftunnel run \x3Ctunnel-id-from-output>
Pattern 2: Add subdomain to existing tunnel
npx cftunnel tunnel list | jq '.[] | select(.status == "healthy")'
npx cftunnel route add \x3Ctunnel-id> --hostname api.example.com --service http://localhost:4000
npx cftunnel dns create --zone-id \x3Czone-id> --hostname api.example.com --tunnel-id \x3Ctunnel-id>
Pattern 3: Swap service behind a hostname
npx cftunnel route remove \x3Ctunnel-id> --hostname app.example.com
npx cftunnel route add \x3Ctunnel-id> --hostname app.example.com --service http://localhost:9000
Pattern 4: Clean teardown
npx cftunnel route remove \x3Ctunnel-id> --hostname app.example.com
npx cftunnel dns list --zone-id \x3Czone-id>
npx cftunnel dns delete \x3Crecord-id> --zone-id \x3Czone-id>
npx cftunnel tunnel delete \x3Ctunnel-id>
Pattern 5: Programmatic usage in agent code
import { createClient, createTunnel, addRoute, createDNS } from 'cftunnel';
const client = createClient(); // reads from env vars
const tunnel = await createTunnel(client, { accountId: '...', name: 'my-app' });
await addRoute(client, {
accountId: '...',
tunnelId: tunnel.id,
hostname: 'app.example.com',
service: 'http://localhost:3000',
});
await createDNS(client, {
zoneId: '...',
hostname: 'app.example.com',
tunnelId: tunnel.id,
});
Important Notes
- A tunnel must have
cloudflaredrunning to serve traffic. Creating a tunnel and routes alone is not enough. - The catch-all
http_status:404rule is always appended automatically. Do not add it manually. - DNS CNAME records must be proxied through Cloudflare (orange cloud). This is set automatically.
- One tunnel can serve multiple hostnames. Prefer reusing existing healthy tunnels over creating new ones.
route setreplaces ALL routes. Useroute add/route removefor incremental changes.- If cloudflared is already running as a service, route changes take effect immediately (no restart needed).
Global Flags
| Flag | Env Var | Description |
|---|---|---|
--api-token |
CLOUDFLARE_API_TOKEN |
Cloudflare API token (bearer auth) |
--api-key |
CLOUDFLARE_API_KEY |
Cloudflare API key (requires --api-email) |
--api-email |
CLOUDFLARE_EMAIL |
Cloudflare account email |
--account-id |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare account ID |
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install cftunnel - 安装完成后,直接呼叫该 Skill 的名称或使用
/cftunnel触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
cftunnel 是什么?
Expose local services to the internet via Cloudflare Tunnels. CLI (npx cftunnel) and Node.js library for creating tunnels, configuring ingress routes, managi... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 104 次。
如何安装 cftunnel?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install cftunnel」即可一键安装,无需额外配置。
cftunnel 是免费的吗?
是的,cftunnel 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
cftunnel 支持哪些平台?
cftunnel 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 cftunnel?
由 Carlos Martin(@pirumpi)开发并维护,当前版本 v1.1.0。