Skip to content
GitHubXDiscordRSS

Tunnel

A Cloudflare Tunnel provides a secure connection between your origin server and Cloudflare’s global network without exposing your server’s IP address.

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>

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

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",
},
],
});

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,
},
});

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",
},
],
});

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",
},
],
});

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",
},
],
});

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",
},
],
});

After creating a tunnel, use the returned token to run cloudflared:

Terminal window
# Using the token (recommended)
cloudflared tunnel run --token <tunnel.token.unencrypted>
# Or using credentials file (for locally-managed tunnels)
cloudflared tunnel run <tunnel-name>

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 configuration
const dns = await DnsRecords("tunnel-dns", {
zone: "example.com",
records: [
{
name: "app",
type: "CNAME",
content: `${tunnel.tunnelId}.cfargotunnel.com`,
proxied: true,
ttl: 1, // Auto TTL
},
],
});

Tunnels can be configured in two ways:

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: [...],
});

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
PropertyTypeDescription
hostnamestringHostname to match (omit for catch-all)
servicestringService URL or status (e.g., http://localhost:8080, http_status:404)
pathstringPath pattern to match
originRequestobjectOrigin configuration for this rule
PropertyTypeDefaultDescription
connectTimeoutnumber30Timeout for origin connection (seconds)
tlsTimeoutnumber10Timeout for TLS handshake (seconds)
httpHostHeaderstring-Override Host header sent to origin
noTLSVerifybooleanfalseDisable TLS certificate verification
http2OriginbooleanfalseUse HTTP/2 for origin connections
keepAliveConnectionsnumber100Number of keep-alive connections
keepAliveTimeoutnumber90Keep-alive timeout (seconds)
proxyProtocolstring”off”Proxy protocol version (“off”, “v1”, “v2”)
  1. Create: Generates tunnel credentials and optional configuration
  2. Update: Modifies configuration (names are immutable)
  3. Delete: Removes tunnel and cleans up DNS records
  4. Adopt: Takes over existing tunnel management