Tunr Documentation

Tunr exposes your local development server to the internet in < 3 seconds with automatic HTTPS, WebSocket support, and zero configuration. It's a developer-first alternative to ngrok and Cloudflare Tunnel.

๐Ÿ’ก

Note: tunr is a single static Go binary. It doesn't rely on Node.js or Python to run, and the CLI is fully open-source.

๐Ÿš€ Deploy an app preview

Tunnels preview your localhost. The cloud is where your app lives on โ€” deployed from any project directory, it sleeps when idle, wakes on request, and keeps running after you close your laptop.

tunr deploy --name my-app
# โ–ฒ Building (nixpacks ยท node 20 detected)
# ๐Ÿš€ Live: https://my-app.tunr.sh  (sleeps when idle, wakes on request)

No Dockerfile required โ€” tunr auto-detects Node, Next.js, and Python (FastAPI/Flask). Drop a Dockerfile in the repo to take full control. Secrets go through --env KEY=VALUE (never committed).

โ—ˆ Manage apps

Or manage everything from the Apps dashboard โ€” deploy status, versions, logs and settings.

๐Ÿค– Agent control plane (MCP)

tunr ships an MCP server, so Claude Code, Cursor and Windsurf can drive it directly. Everything you can do in the dashboard, your agent can do over MCP.

tunr mcp   # start the MCP server (stdio transport)

Point your agent at it and say โ€œship this to tunrโ€ โ€” it calls deploy_app for you. Tools include deploy_app, list_apps, get_logs, set_share_policy, get_feedback and rollback. See MCP / Cursor setup below for the config snippet.

Quickstart โ€” tunnels

Install via Homebrew or curl, then start sharing immediately.

brew install ahmetvural79/tap/tunr
tunr share --port 3000

CLI Reference

โ„๏ธ Freeze / Snapshot Mode

If your app crashes (e.g., Node throws an exception, Database disconnects) while demoing to a client, the freeze mode will intercept the 500 error and serve the last working HTML/CSS/JS response from memory.

tunr share --port 3000 --freeze

๐Ÿ›ก๏ธ Read-Only Demo Mode

Prevents destructive actions. Whenever a client submits a form (POST) or clicks a delete button (DELETE), the tunr proxy safely intercepts the request, blocks it from hitting your actual local backend, and returns a simulated success response.

tunr share --port 3000 --demo

๐Ÿ’ฌ Feedback Widget Injection

Every HTML page that passes through the tunnel gets a transparent feedback widget injected via proxy middleware (no code changes required on your end). Both visual UI feedback and background JavaScript exceptions are captured.

tunr share --port 3000 --inject-widget

๐Ÿ“ฑ QR Code Tunnel Sharing

Generate a scannable QR code for your tunnel URL directly in the terminal. Perfect for mobile testing and quickly sharing URLs with clients or for scanning into mobile browsers.

tunr share -p 3000 --qr

๐Ÿ”‘ Bearer Token Authentication

Protect your tunnel with a simple API key. Requests must include Authorization: Bearer <token> in the header or ?token=<value> in the query string to be served.

tunr share -p 3000 --auth-token "my-super-secret-key"

๐Ÿ›ก๏ธ IP Whitelisting

Restrict tunnel access to specific CIDR ranges. Only whitelisted IP addresses can reach your local server โ€” ideal for staging environments, team access, and production testing.

# Single network
tunr share -p 3000 --allow-ip "203.0.113.0/24"

# Multiple networks
tunr share -p 3000 --allow-ip "10.0.0.0/8,172.16.0.0/12"

๐Ÿ”ง Live Header Modification

Add, replace, or remove HTTP headers on the fly before they reach your local server. Useful for debugging, internal routing, or stripping fingerprinting headers.

# Add headers
tunr share -p 3000 --header-add "X-Env: staging"

# Replace a header
tunr share -p 3000 --header-replace "Host: internal.local"

# Remove headers
tunr share -p 3000 --header-remove "X-Powered-By"

๐Ÿ”’ Password Protection

Add Basic Authentication to your public URL instantly without writing any code. Keep your development environments secure from unauthorized access while sharing with clients or third parties.

tunr share -p 8080 --password "secret"

โณ Auto-Expiring Tunnels

Forget to stop a tunnel exposing your local machine? Use a Time-To-Live (TTL). Once the duration expires, the tunnel daemon safely terminates the connection and shuts down the proxy.

tunr share -p 3000 --ttl 1h30m

๐Ÿ”€ Path Routing

Map different incoming URL paths to different upstream ports on your machine. This is perfect for testing microservices or serving your frontend and API from a single public proxy domain.

tunr share --route /=3000 --route /api=8080

๐Ÿค– Model Context Protocol

Tunr supports MCP out of the box. Add it to Claude Desktop or Cursor so your AI can manage tunnels locally.

// .cursor/mcp.json
{
  "mcpServers": {
    "tunr": {
      "command": "tunr",
      "args": ["mcp"]
    }
  }
}

๐Ÿ”Œ TCP Tunnels

Expose raw TCP services โ€” databases, SSH servers, Redis, game servers โ€” through secure tunnels without any HTTP parsing overhead. Perfect for PostgreSQL, MySQL, MongoDB, Redis, or any TCP-based protocol.

# Basic TCP tunnel
tunr tcp --port 5432

# TCP tunnel with QR code
tunr tcp --port 22 --qr

# TCP tunnel with IP restriction
tunr tcp --port 6379 --allow-ip 10.0.0.0/8

๐ŸŒ Multi-Region Routing

Select a preferred relay region for lower latency to specific geographic areas. Currently available regions:

CodeLocationRegion
amsAmsterdamEurope (EU)
seaSeattleUS West (Americas)
sinSingaporeAsia-Pacific
# HTTP tunnel to European clients
tunr share --port 3000 --region ams

# TCP tunnel for Asia-Pacific users
tunr tcp --port 5432 --region sin

๐Ÿ Python SDK

Official Python SDK: pip install tunr

from tunr import TunrClient, TunnelOptions

client = TunrClient()

# Simple tunnel
tunnel = client.share(port=3000)
print(tunnel.public_url)

# With options
opts = TunnelOptions(
    subdomain="myapp",
    password="demo123",
    allow_ips=["10.0.0.0/8"],
)
tunnel = client.share(port=8080, opts=opts)

# Inspect requests
requests = client.get_requests(tunnel.id)

# Replay a request
client.replay_request(requests[0].id)

# Clean up
tunnel.close()

โšก Node.js SDK

Official Node.js SDK: npm install @tunr/cli

import { TunrClient } from '@tunr/cli'

const client = new TunrClient()

// Simple tunnel
const tunnel = await client.share(3000)
console.log(tunnel.publicUrl)

// With options
const tunnel = await client.share(8080, {
  subdomain: 'myapp',
  password: 'demo123',
  allowIps: ['10.0.0.0/8'],
})

// Event-based lifecycle
tunnel.on('ready', () => console.log('Tunnel live'))
tunnel.on('error', (err) => console.error(err))
tunnel.on('exit', () => console.log('Tunnel closed'))

// Inspect & replay
const requests = await client.getRequests(tunnel.id)
await client.replayRequest(requests[0].id)

// Clean up
await tunnel.close()