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.
Quickstart
Install via Homebrew or curl, then start sharing immediately.
brew install ahmetvural79/tap/tunr
tunr share --port 3000
CLI Reference
tunr share -p [port]: Create a public HTTPS tunnel.tunr share -p [port] -s [subdomain]: Use a custom.tunr.shsubdomain (Pro feature).tunr logs: Stream HTTP request logs in your terminal.tunr open: Open the embedded local HTTP inspector dashboard in your browser.
❄️ 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.
- Visual Comments: Clients can click anywhere on your webpage to drop a pin and leave a comment about the design or functionality.
- JS Error Catching: Whenever the client's browser throws a JavaScript error
(e.g.
Uncaught TypeError), it is silently caught and streamed back to the server. - Dashboard Viewer: You can read all aggregated feedback, complete with timestamps and browser info, in the Tunr Dashboard Feedback page.
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:
| Code | Location | Region |
|---|---|---|
ams | Amsterdam | Europe (EU) |
sea | Seattle | US West (Americas) |
sin | Singapore | Asia-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()