Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nearai/ironclaw/llms.txt

Use this file to discover all available pages before exploring further.

Overview

IronClaw’s tunnel system wraps external tunnel binaries (cloudflared, ngrok, tailscale, etc.) behind a common trait. The gateway starts a tunnel after binding its local port and stops it on shutdown.

Supported Providers

ProviderTypeUse CaseCost
NoneLocal-onlyDevelopmentFree
CloudflareZero Trust tunnelProductionFree (with Cloudflare account)
TailscalePrivate mesh VPNTeam accessFree (up to 3 users)
ngrokPublic URLTesting, demosFree tier available
CustomAny binaryCustom setupsVaries

Configuration

Environment Variables

# Provider selection
TUNNEL_PROVIDER=cloudflare  # Options: none, cloudflare, tailscale, ngrok, custom

# Cloudflare
TUNNEL_CF_TOKEN=eyJh...  # From Zero Trust dashboard

# Tailscale
TUNNEL_TS_FUNNEL=false  # true = public, false = tailnet-only
TUNNEL_TS_HOSTNAME=my-ironclaw  # Optional override

# ngrok
TUNNEL_NGROK_TOKEN=...  # Auth token
TUNNEL_NGROK_DOMAIN=my-app.ngrok.app  # Optional (paid plan)

# Custom
TUNNEL_CUSTOM_COMMAND="cloudflared tunnel --no-autoupdate run --url http://{host}:{port}"
TUNNEL_CUSTOM_HEALTH_URL=http://localhost:8080/health
TUNNEL_CUSTOM_URL_PATTERN="https://"

Provider Setup

None (Local Only)

No tunnel. Agent is only accessible on localhost.
TUNNEL_PROVIDER=none
# Or omit TUNNEL_PROVIDER entirely
Use Cases:
  • Local development
  • Single-user setups
  • Behind existing reverse proxy

Cloudflare Zero Trust

Secure, authenticated tunnel via Cloudflare’s network.

Setup

  1. Create Cloudflare account at cloudflare.com
  2. Install cloudflared:
    # macOS
    brew install cloudflare/cloudflare/cloudflared
    
    # Linux
    wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
    sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared
    sudo chmod +x /usr/local/bin/cloudflared
    
  3. Create tunnel in Zero Trust dashboard:
    • Navigate to Networks > Tunnels
    • Click “Create a tunnel”
    • Choose “Cloudflared”
    • Copy the tunnel token
  4. Configure IronClaw:
    TUNNEL_PROVIDER=cloudflare
    TUNNEL_CF_TOKEN=eyJh...  # Paste token here
    
  5. Start IronClaw:
    ironclaw run
    
    The tunnel URL will be displayed in logs:
    [INFO] Tunnel started: https://random-name-123.cfargotunnel.com
    
Features:
  • Zero Trust authentication
  • Automatic HTTPS
  • DDoS protection
  • Access control policies
  • No firewall configuration needed
Configuration:
pub struct CloudflareTunnelConfig {
    pub token: String,  // From Zero Trust dashboard
}

Tailscale

Private mesh VPN with optional public access.

Setup (Tailnet-Only)

  1. Install Tailscale:
    # macOS
    brew install tailscale
    
    # Linux
    curl -fsSL https://tailscale.com/install.sh | sh
    
  2. Authenticate:
    sudo tailscale up
    
  3. Configure IronClaw:
    TUNNEL_PROVIDER=tailscale
    TUNNEL_TS_FUNNEL=false  # Tailnet-only
    
  4. Start IronClaw:
    ironclaw run
    
    Access via: https://<machine-name>.<tailnet-name>.ts.net

Setup (Public Funnel)

  1. Enable Funnel (requires Tailscale account):
    TUNNEL_PROVIDER=tailscale
    TUNNEL_TS_FUNNEL=true  # Public access
    
  2. Start IronClaw:
    ironclaw run
    
    Public URL: https://<machine-name>.<tailnet-name>.ts.net
Features:
  • Private mesh network
  • Automatic HTTPS (via Let’s Encrypt)
  • ACLs for access control
  • Optional public access (Funnel)
  • Magic DNS
Configuration:
pub struct TailscaleTunnelConfig {
    pub funnel: bool,              // Use public funnel
    pub hostname: Option<String>,  // Override machine name
}

ngrok

Instant public URLs for testing and demos.

Setup

  1. Create account at ngrok.com
  2. Install ngrok:
    # macOS
    brew install ngrok
    
    # Linux
    wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
    tar xvzf ngrok-v3-stable-linux-amd64.tgz
    sudo mv ngrok /usr/local/bin/
    
  3. Get auth token from dashboard
  4. Configure IronClaw:
    TUNNEL_PROVIDER=ngrok
    TUNNEL_NGROK_TOKEN=...  # Your auth token
    
  5. Start IronClaw:
    ironclaw run
    
    URL: https://random-string.ngrok.app

Custom Domain (Paid)

TUNNEL_PROVIDER=ngrok
TUNNEL_NGROK_TOKEN=...
TUNNEL_NGROK_DOMAIN=my-app.ngrok.app  # Requires paid plan
Features:
  • Instant public URLs
  • Web inspection UI
  • Replay requests
  • Custom domains (paid)
  • Edge routing
Configuration:
pub struct NgrokTunnelConfig {
    pub auth_token: String,
    pub domain: Option<String>,  // Custom domain (paid plan)
}

Custom Tunnel

Use any tunnel binary with placeholders.

Example: bore.pub

TUNNEL_PROVIDER=custom
TUNNEL_CUSTOM_COMMAND="bore local {port} --to bore.pub"
TUNNEL_CUSTOM_URL_PATTERN="bore.pub"

Example: localtunnel

TUNNEL_PROVIDER=custom
TUNNEL_CUSTOM_COMMAND="lt --port {port}"
TUNNEL_CUSTOM_URL_PATTERN="https://"

Example: Custom cloudflared

TUNNEL_PROVIDER=custom
TUNNEL_CUSTOM_COMMAND="cloudflared tunnel --url http://{host}:{port}"
TUNNEL_CUSTOM_HEALTH_URL=http://localhost:8080/health
TUNNEL_CUSTOM_URL_PATTERN="https://"
Placeholders:
  • {host} - Local bind address (e.g., 127.0.0.1)
  • {port} - Local bind port (e.g., 3000)
Configuration:
pub struct CustomTunnelConfig {
    pub start_command: String,          // Command with placeholders
    pub health_url: Option<String>,     // Health check endpoint
    pub url_pattern: Option<String>,    // Substring to match in stdout
}

Tunnel Lifecycle

Startup

1. Gateway binds local port (default: 3000)
2. Tunnel provider starts (if configured)
3. Public URL extracted from stdout
4. Gateway becomes ready

Shutdown

1. Gateway receives shutdown signal
2. Tunnel provider stopped gracefully
3. Container cleanup (if any)
4. Process exits

Health Checks

pub async fn health_check(&self) -> bool {
    // Check if tunnel process is still alive
    if let Some(proc) = self.process.lock().await.as_mut() {
        match proc.child.try_wait() {
            Ok(None) => true,  // Still running
            _ => false,        // Exited
        }
    } else {
        false  // Not started
    }
}

URL Extraction

Cloudflare

Parses cloudflared output:
2025-03-03T12:34:56Z INF +--------------------------------------------------------------------------------------------+
2025-03-03T12:34:56Z INF |  Your quick Tunnel has been created! Visit it at (it may take some time to be reachable): |
2025-03-03T12:34:56Z INF |  https://random-name-123.cfargotunnel.com                                                 |
2025-03-03T12:34:56Z INF +--------------------------------------------------------------------------------------------+
Extraction: Look for https://.*cfargotunnel.com

Tailscale

Queries tailscale status --json:
{
  "Self": {
    "DNSName": "machine-name.tailnet-name.ts.net.",
    "HostName": "machine-name"
  }
}
URL: https://<DNSName> (strips trailing dot)

ngrok

Parses ngrok output:
Forwarding   https://random-string.ngrok.app -> http://localhost:3000
Extraction: Look for https://.*ngrok.app

Custom

Matches url_pattern in stdout:
TUNNEL_CUSTOM_URL_PATTERN="https://"
First line containing pattern is used as URL.

Programmatic API

Creating Tunnels

use ironclaw::tunnel::{
    create_tunnel,
    TunnelProviderConfig,
    CloudflareTunnelConfig,
};

let config = TunnelProviderConfig {
    provider: "cloudflare".to_string(),
    cloudflare: Some(CloudflareTunnelConfig {
        token: "eyJh...".to_string(),
    }),
    ..Default::default()
};

let tunnel = create_tunnel(&config)?.unwrap();

// Start tunnel
let url = tunnel.start("127.0.0.1", 3000).await?;
println!("Tunnel URL: {}", url);

// Health check
if tunnel.health_check().await {
    println!("Tunnel is healthy");
}

// Get URL
if let Some(url) = tunnel.public_url() {
    println!("Public URL: {}", url);
}

// Stop tunnel
tunnel.stop().await?;

Tunnel Trait

#[async_trait]
pub trait Tunnel: Send + Sync {
    fn name(&self) -> &str;
    
    async fn start(&self, local_host: &str, local_port: u16) -> Result<String>;
    
    async fn stop(&self) -> Result<()>;
    
    async fn health_check(&self) -> bool;
    
    fn public_url(&self) -> Option<String>;
}

Security Considerations

Tunnel Authentication

  • Cloudflare: Zero Trust policies (IP restrictions, auth providers)
  • Tailscale: ACLs, MagicDNS, device authorization
  • ngrok: Basic auth, OAuth, SAML (paid plans)
  • Custom: Depends on provider

Gateway Authentication

In addition to tunnel auth, IronClaw gateway has:
  • API key authentication (GATEWAY_API_KEY)
  • Rate limiting
  • Request validation
  • CORS configuration
Production:
TUNNEL_PROVIDER=cloudflare
TUNNEL_CF_TOKEN=...
GATEWAY_API_KEY=...  # Strong random key
Development:
TUNNEL_PROVIDER=none
# Access via localhost:3000 only
Team Access:
TUNNEL_PROVIDER=tailscale
TUNNEL_TS_FUNNEL=false  # Tailnet-only

Troubleshooting

Tunnel Process Dies

Error: Tunnel health check failed
Solutions:
  1. Check binary is installed: which cloudflared
  2. Check logs for tunnel errors
  3. Verify credentials are valid
  4. Ensure port is not already in use

URL Not Extracted

Warning: Could not extract tunnel URL from output
Solutions:
  1. Check tunnel binary output format hasn’t changed
  2. For custom tunnels, verify url_pattern matches
  3. Increase startup timeout
  4. Check logs for tunnel startup errors

Connection Refused

Error: Connection refused at https://random.cfargotunnel.com
Causes:
  1. Tunnel not fully started (takes 10-30s)
  2. Gateway not listening on correct port
  3. Firewall blocking outbound tunnel connection
Solutions:
  1. Wait 30 seconds and retry
  2. Check gateway is running: lsof -i :3000
  3. Verify GATEWAY_PORT matches tunnel forwarding

Cloudflare 502 Bad Gateway

Error 502: Bad Gateway
Causes:
  1. Gateway crashed after tunnel started
  2. Port mismatch between gateway and tunnel
  3. Network connectivity issues
Solutions:
  1. Restart IronClaw
  2. Verify GATEWAY_PORT configuration
  3. Check gateway health: curl localhost:3000/health

Performance

Latency

ProviderTypical LatencyNotes
None0msLocal only
Tailscale10-50msDirect peer-to-peer when possible
Cloudflare20-100msGlobal CDN, varies by region
ngrok50-200msDepends on edge location

Throughput

  • Cloudflare: Unlimited bandwidth
  • Tailscale: Full network speed (P2P)
  • ngrok: Rate limited on free plan

Recommendations

  • Low latency: Tailscale (P2P) or None (local)
  • High throughput: Cloudflare or Tailscale
  • Quick setup: ngrok
  • Production: Cloudflare (Zero Trust) or Tailscale (team)

Source Code

Key files:
  • src/tunnel/mod.rs - Tunnel trait and factory
  • src/tunnel/cloudflare.rs - Cloudflare implementation
  • src/tunnel/tailscale.rs - Tailscale implementation
  • src/tunnel/ngrok.rs - ngrok implementation
  • src/tunnel/custom.rs - Custom tunnel implementation
  • src/tunnel/none.rs - No-tunnel implementation

Build docs developers (and LLMs) love