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.
Set these environment variables before running any command:
# Option A: API Key + Email (most common)
export CLOUDFLARE_API_KEY=<api-key>
export CLOUDFLARE_EMAIL=<account-email>
# Option B: API Token (scoped, if available)
export CLOUDFLARE_API_TOKEN=<api-token>
# Always required:
export CLOUDFLARE_ACCOUNT_ID=<account-id>
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 <tunnel-id> --hostname <hostname> --service http://localhost:<port>
# 2. Create DNS CNAME
npx cftunnel dns create --zone-id <zone-id> --hostname <hostname> --tunnel-id <tunnel-id>
npx cftunnel quickstart \
--name <tunnel-name> \
--hostname <hostname> \
--service http://localhost:<port> \
--zone-id <zone-id>
Then run the connector: npx cftunnel run
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);
| Command | Purpose |
|---|---|
| --- | --- |
npx cftunnel tunnel list | List all tunnels. Find existing tunnel IDs and check status (healthy/down/inactive). |
npx cftunnel tunnel create | Create a new tunnel. Returns tunnel ID and secret. Add --config-src local for YAML-managed config. |
npx cftunnel tunnel get | Get tunnel details including connection status. |
npx cftunnel tunnel delete | Delete a tunnel. Must have no active connections. |
npx cftunnel tunnel token | Get the token needed to run cloudflared. Returns run_cmd and install_cmd. |
| Command | Purpose |
|---|---|
| --- | --- |
npx cftunnel route list | Show current ingress rules. Always has a catch-all 404 as last rule. |
npx cftunnel route add | Add a route. Preserves existing routes, appends before catch-all. Optional --path for path filtering. |
npx cftunnel route remove | Remove a route by hostname. |
npx cftunnel route set | Replace ALL routes. Use for bulk configuration. |
| Command | Purpose |
|---|---|
| --- | --- |
npx cftunnel dns create --zone-id | Create proxied CNAME pointing hostname to tunnel. Required for the hostname to resolve. |
npx cftunnel dns list --zone-id | List all DNS records in the zone. |
npx cftunnel dns delete | Delete a DNS record. |
| Command | Purpose |
|---|---|
| --- | --- |
npx cftunnel run | Run cloudflared in foreground. Auto-detects cloudflared from PATH or npm package. |
npx cftunnel run | Install cloudflared as a persistent system service (survives reboots). |
"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 . DNS stays the same.
"I need to take a service offline"
→ route remove --hostname X and optionally dns delete .
"The tunnel exists but cloudflared isn't running"
→ npx cftunnel run or use tunnel token to get the token for manual cloudflared invocation.
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) |
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')
cd /path/to/app && npm start &
npx cftunnel quickstart \
--name my-web-app \
--hostname app.example.com \
--service http://localhost:8080 \
--zone-id <zone-id>
npx cftunnel run <tunnel-id-from-output>
npx cftunnel tunnel list | jq '.[] | select(.status == "healthy")'
npx cftunnel route add <tunnel-id> --hostname api.example.com --service http://localhost:4000
npx cftunnel dns create --zone-id <zone-id> --hostname api.example.com --tunnel-id <tunnel-id>
npx cftunnel route remove <tunnel-id> --hostname app.example.com
npx cftunnel route add <tunnel-id> --hostname app.example.com --service http://localhost:9000
npx cftunnel route remove <tunnel-id> --hostname app.example.com
npx cftunnel dns list --zone-id <zone-id>
npx cftunnel dns delete <record-id> --zone-id <zone-id>
npx cftunnel tunnel delete <tunnel-id>
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,
});
cloudflared running to serve traffic. Creating a tunnel and routes alone is not enough.http_status:404 rule is always appended automatically. Do not add it manually.route set replaces ALL routes. Use route add/route remove for incremental changes.| 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 |
共 1 个版本