Tunnel
A Cloudflare Tunnel provides a secure connection between your origin server and Cloudflare’s global network without exposing your server’s IP address.
Minimal Example
Section titled “Minimal Example”Create a basic tunnel and run it with the returned token:
import { Tunnel } from "alchemy/cloudflare";
const tunnel = await Tunnel("my-app", { name: "my-app-tunnel",});
// Run cloudflared with:// cloudflared tunnel run --token <tunnel.token.unencrypted>
With Ingress Rules
Section titled “With Ingress Rules”Create a tunnel with routing configuration. DNS records are automatically created for each hostname:
import { Tunnel } from "alchemy/cloudflare";
const webTunnel = await Tunnel("web-app", { name: "web-app-tunnel", ingress: [ { hostname: "app.example.com", service: "http://localhost:3000", }, { service: "http_status:404", // catch-all rule (required by Cloudflare API) }, ],});
// A CNAME record for app.example.com → {tunnelId}.cfargotunnel.com// is automatically created in the appropriate zone
Multiple Services
Section titled “Multiple Services”Route different paths and hostnames to different services:
import { Tunnel } from "alchemy/cloudflare";
const apiTunnel = await Tunnel("api", { name: "api-tunnel", ingress: [ { hostname: "api.example.com", path: "/v1/*", service: "http://localhost:8080", originRequest: { httpHostHeader: "api.internal", connectTimeout: 30, }, }, { hostname: "api.example.com", path: "/v2/*", service: "http://localhost:8081", }, { hostname: "admin.example.com", service: "http://localhost:9000", }, { service: "http_status:404", }, ],});
Private Network Access
Section titled “Private Network Access”Enable WARP routing for private network connectivity:
import { Tunnel } from "alchemy/cloudflare";
const privateTunnel = await Tunnel("private-network", { name: "private-network-tunnel", warpRouting: { enabled: true, },});
Origin Configuration
Section titled “Origin Configuration”Configure how the tunnel connects to your origin servers:
import { Tunnel } from "alchemy/cloudflare";
const secureTunnel = await Tunnel("secure", { name: "secure-tunnel", originRequest: { noTLSVerify: false, connectTimeout: 30, httpHostHeader: "internal.service", http2Origin: true, keepAliveConnections: 10, }, ingress: [ { hostname: "secure.example.com", service: "https://localhost:8443", }, { service: "http_status:404", }, ],});
Per-Rule Configuration
Section titled “Per-Rule Configuration”Apply different origin settings to specific routes:
import { Tunnel } from "alchemy/cloudflare";
const mixedTunnel = await Tunnel("mixed", { name: "mixed-tunnel", ingress: [ { hostname: "fast.example.com", service: "http://localhost:3000", originRequest: { connectTimeout: 10, keepAliveTimeout: 90, }, }, { hostname: "secure.example.com", service: "https://localhost:8443", originRequest: { caPool: "/path/to/ca.pem", noTLSVerify: false, }, }, { service: "http_status:404", }, ],});
Adopting Existing Tunnels
Section titled “Adopting Existing Tunnels”Take over management of an existing tunnel:
import { Tunnel } from "alchemy/cloudflare";
const existingTunnel = await Tunnel("existing", { name: "existing-tunnel", adopt: true, // Won't fail if tunnel already exists ingress: [ { hostname: "updated.example.com", service: "http://localhost:5000", }, { service: "http_status:404", }, ],});
With Custom Secret
Section titled “With Custom Secret”Provide your own tunnel secret:
import alchemy from "alchemy";import { Tunnel } from "alchemy/cloudflare";
const tunnel = await Tunnel("custom-secret", { name: "custom-secret-tunnel", tunnelSecret: alchemy.secret("your-secret-value"), ingress: [ { hostname: "app.example.com", service: "http://localhost:3000", }, { service: "http_status:404", }, ],});
Running the Tunnel
Section titled “Running the Tunnel”After creating a tunnel, use the returned token to run cloudflared:
# Using the token (recommended)cloudflared tunnel run --token <tunnel.token.unencrypted>
# Or using credentials file (for locally-managed tunnels)cloudflared tunnel run <tunnel-name>
Automatic DNS Management
Section titled “Automatic DNS Management”The Tunnel resource automatically creates DNS CNAME records for hostnames specified in ingress rules:
import { Tunnel } from "alchemy/cloudflare";
const appTunnel = await Tunnel("app", { name: "app-tunnel", ingress: [ { hostname: "app.example.com", service: "http://localhost:3000", }, { hostname: "api.example.com", service: "http://localhost:8080", }, { service: "http_status:404", }, ],});
// DNS CNAME records are automatically created:// - app.example.com → {tunnelId}.cfargotunnel.com// - api.example.com → {tunnelId}.cfargotunnel.com
For advanced DNS configurations, omit hostnames from ingress rules and manage DNS records separately:
import { Tunnel, DnsRecords } from "alchemy/cloudflare";
const tunnel = await Tunnel("manual-dns", { name: "manual-dns-tunnel", ingress: [ { service: "http://localhost:3000", }, { service: "http_status:404", }, ],});
// Create DNS records with custom configurationconst dns = await DnsRecords("tunnel-dns", { zone: "example.com", records: [ { name: "app", type: "CNAME", content: `${tunnel.tunnelId}.cfargotunnel.com`, proxied: true, ttl: 1, // Auto TTL }, ],});
Configuration Management
Section titled “Configuration Management”Tunnels can be configured in two ways:
Remotely-Managed (Default)
Section titled “Remotely-Managed (Default)”Configuration is stored in Cloudflare and managed via API:
import { Tunnel } from "alchemy/cloudflare";
const remoteTunnel = await Tunnel("remote", { name: "remote-tunnel", configSrc: "cloudflare", // Default ingress: [...],});
Locally-Managed
Section titled “Locally-Managed”Configuration is managed via local config files:
import { Tunnel } from "alchemy/cloudflare";
const localTunnel = await Tunnel("local", { name: "local-tunnel", configSrc: "local", // Ingress rules are ignored for locally-managed tunnels});
// Configure via ~/.cloudflared/config.yml or specify --config flag
Tunnel Properties
Section titled “Tunnel Properties”Ingress Rule Options
Section titled “Ingress Rule Options”Property | Type | Description |
---|---|---|
hostname | string | Hostname to match (omit for catch-all) |
service | string | Service URL or status (e.g., http://localhost:8080 , http_status:404 ) |
path | string | Path pattern to match |
originRequest | object | Origin configuration for this rule |
Origin Request Options
Section titled “Origin Request Options”Property | Type | Default | Description |
---|---|---|---|
connectTimeout | number | 30 | Timeout for origin connection (seconds) |
tlsTimeout | number | 10 | Timeout for TLS handshake (seconds) |
httpHostHeader | string | - | Override Host header sent to origin |
noTLSVerify | boolean | false | Disable TLS certificate verification |
http2Origin | boolean | false | Use HTTP/2 for origin connections |
keepAliveConnections | number | 100 | Number of keep-alive connections |
keepAliveTimeout | number | 90 | Keep-alive timeout (seconds) |
proxyProtocol | string | ”off” | Proxy protocol version (“off”, “v1”, “v2”) |
Tunnel Lifecycle
Section titled “Tunnel Lifecycle”- Create: Generates tunnel credentials and optional configuration
- Update: Modifies configuration (names are immutable)
- Delete: Removes tunnel and cleans up DNS records
- Adopt: Takes over existing tunnel management