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
| Provider | Type | Use Case | Cost |
|---|
| None | Local-only | Development | Free |
| Cloudflare | Zero Trust tunnel | Production | Free (with Cloudflare account) |
| Tailscale | Private mesh VPN | Team access | Free (up to 3 users) |
| ngrok | Public URL | Testing, demos | Free tier available |
| Custom | Any binary | Custom setups | Varies |
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
-
Create Cloudflare account at cloudflare.com
-
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
-
Create tunnel in Zero Trust dashboard:
- Navigate to Networks > Tunnels
- Click “Create a tunnel”
- Choose “Cloudflared”
- Copy the tunnel token
-
Configure IronClaw:
TUNNEL_PROVIDER=cloudflare
TUNNEL_CF_TOKEN=eyJh... # Paste token here
-
Start IronClaw:
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)
-
Install Tailscale:
# macOS
brew install tailscale
# Linux
curl -fsSL https://tailscale.com/install.sh | sh
-
Authenticate:
-
Configure IronClaw:
TUNNEL_PROVIDER=tailscale
TUNNEL_TS_FUNNEL=false # Tailnet-only
-
Start IronClaw:
Access via:
https://<machine-name>.<tailnet-name>.ts.net
Setup (Public Funnel)
-
Enable Funnel (requires Tailscale account):
TUNNEL_PROVIDER=tailscale
TUNNEL_TS_FUNNEL=true # Public access
-
Start IronClaw:
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
-
Create account at ngrok.com
-
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/
-
Get auth token from dashboard
-
Configure IronClaw:
TUNNEL_PROVIDER=ngrok
TUNNEL_NGROK_TOKEN=... # Your auth token
-
Start IronClaw:
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
}
}
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
Recommended Setup
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:
- Check binary is installed:
which cloudflared
- Check logs for tunnel errors
- Verify credentials are valid
- Ensure port is not already in use
Warning: Could not extract tunnel URL from output
Solutions:
- Check tunnel binary output format hasn’t changed
- For custom tunnels, verify
url_pattern matches
- Increase startup timeout
- Check logs for tunnel startup errors
Connection Refused
Error: Connection refused at https://random.cfargotunnel.com
Causes:
- Tunnel not fully started (takes 10-30s)
- Gateway not listening on correct port
- Firewall blocking outbound tunnel connection
Solutions:
- Wait 30 seconds and retry
- Check gateway is running:
lsof -i :3000
- Verify
GATEWAY_PORT matches tunnel forwarding
Cloudflare 502 Bad Gateway
Causes:
- Gateway crashed after tunnel started
- Port mismatch between gateway and tunnel
- Network connectivity issues
Solutions:
- Restart IronClaw
- Verify
GATEWAY_PORT configuration
- Check gateway health:
curl localhost:3000/health
Latency
| Provider | Typical Latency | Notes |
|---|
| None | 0ms | Local only |
| Tailscale | 10-50ms | Direct peer-to-peer when possible |
| Cloudflare | 20-100ms | Global CDN, varies by region |
| ngrok | 50-200ms | Depends 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