Learn how to install, configure, and use Tunels to expose your local services to the internet securely. From your first tunnel to advanced configurations.
Tunels is a secure tunneling service that exposes your local development servers, APIs, and services to the public internet. Whether you are testing webhooks from Stripe or GitHub, demoing an application to a client, or debugging a mobile app that needs to reach your local backend, Tunels creates an encrypted tunnel from a public URL directly to a port on your machine.
When you start a tunnel, Tunels assigns you a public URL such as https://myapp.tunnels.solutions. Any HTTP, TCP, or UDP traffic sent to that URL is forwarded through an encrypted TLS connection to your local service. You do not need to configure firewalls, set up port forwarding on your router, or deploy anything to a remote server.
Tunels runs on all major operating systems and architectures. The client is a single static binary with no external dependencies.
| Platform | Architecture | Minimum OS Version |
|---|---|---|
| Linux | amd64, arm64, 386, arm | Kernel 3.10+ |
| macOS | amd64 (Intel), arm64 (Apple Silicon) | macOS 11 (Big Sur) |
| Windows | amd64, 386, arm64 | Windows 10 |
| FreeBSD | amd64, arm64 | FreeBSD 12+ |
Install Tunels with a single command. The installer detects your OS and architecture automatically, downloads the correct binary, verifies the checksum, and installs it.
# Linux, macOS, FreeBSD
curl -fsSL https://tunels.io/install.sh | bash
# Or with wget
wget -qO- https://tunels.io/install.sh | bash
# Windows (Git Bash)
curl -fsSL https://tunels.io/install.sh | bash
# Windows (PowerShell)
Invoke-WebRequest -Uri https://download.tunels.io/v0.0.1/windows/amd64/tunels.exe -OutFile tunels.exe
On Linux, macOS, and FreeBSD, the installer places the binary in /usr/local/bin/tunels (or ~/.local/bin/tunels if sudo is not available). On Windows (Git Bash), it saves tunels.exe to the current directory.
If you prefer to install manually, download the binary for your platform from the Downloads page or use the command line:
# Linux (amd64)
curl -Lo tunels https://download.tunels.io/v0.0.1/linux/amd64/tunels
# macOS (Apple Silicon)
curl -Lo tunels https://download.tunels.io/v0.0.1/darwin/arm64/tunels
# macOS (Intel)
curl -Lo tunels https://download.tunels.io/v0.0.1/darwin/amd64/tunels
# Make executable
chmod +x tunels
# Move to PATH
sudo mv tunels /usr/local/bin/
Verify the installation:
tunels version
# tunels v0.0.1 (linux/amd64)
Suppose you have a local web server running on port 8000. To expose it to the internet, simply run:
./tunels localhost:8000
Tunels will connect to the tunnel server, allocate a public URL, and begin forwarding traffic:
tunels
Tunnel Status: online
Version: 0.0.1
Web Interface: http://127.0.0.1:4040
Forwarding: https://a1b2c3d4.tunnels.solutions -> http://localhost:8000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
http://localhost:4040 where you can see every request that passes through the tunnel, inspect headers, replay requests, and view response bodies. This is invaluable for debugging webhooks and API integrations.
While you can use Tunels without an account on the free Basic plan (with a random subdomain), creating an account unlocks static subdomains, multiple tunnels, TCP/UDP protocols, and higher limits. To sign up:
tun_live_...) is shown in your dashboard. Copy it.Pass your authentication token when starting a tunnel to access your plan features:
./tunels -authtoken=tun_live_xxxxxxxxxxxxxxxxxx localhost:8000
Or save it in a configuration file so you do not have to pass it every time (see Configuration):
# Save to tunels.yml
echo "auth_token: tun_live_xxxxxxxxxxxxxxxxxx" > tunels.yml
# Now just run without the flag
./tunels localhost:8000
Tunels is distributed as a single static binary. There are no dependencies, runtimes, or package managers required. The fastest way to install is with our install script:
# Automatic install (detects your OS and architecture)
curl -fsSL https://tunels.io/install.sh | bash
The script downloads the correct binary, verifies the SHA-256 checksum, and installs it to your PATH. If you prefer to install manually, see the platform-specific instructions below.
Available for amd64, arm64, 386, and arm architectures.
# Automatic install
curl -fsSL https://tunels.io/install.sh | bash
# Or manual download (amd64)
curl -Lo tunels https://download.tunels.io/v0.0.1/linux/amd64/tunels
chmod +x tunels
sudo mv tunels /usr/local/bin/
tunels version
On Linux systems with systemd, you can also set up Tunels as a service for persistent tunnels. See the Deployment section for details.
Available for both Intel (amd64) and Apple Silicon (arm64) Macs.
# Automatic install (recommended)
curl -fsSL https://tunels.io/install.sh | bash
# Or manual download
# Apple Silicon (M1/M2/M3/M4)
curl -Lo tunels https://download.tunels.io/v0.0.1/darwin/arm64/tunels
# Intel
curl -Lo tunels https://download.tunels.io/v0.0.1/darwin/amd64/tunels
chmod +x tunels
xattr -d com.apple.quarantine ./tunels
xattr -d com.apple.quarantine ./tunels
Available for amd64, 386, and arm64. Download the .exe binary.
# PowerShell
Invoke-WebRequest -Uri https://download.tunels.io/v0.0.1/windows/amd64/tunels.exe -OutFile tunels.exe
# Verify
.\tunels.exe version
To add Tunels to your system PATH on Windows, move tunels.exe to a directory that is already in your PATH, or add a new directory:
# Create a directory and add to PATH (PowerShell, run as Administrator)
New-Item -ItemType Directory -Path "C:\Program Files\Tunels" -Force
Move-Item tunels.exe "C:\Program Files\Tunels\"
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Tunels", "Machine")
Available for amd64 and arm64.
# Automatic install
curl -fsSL https://tunels.io/install.sh | bash
# Or manual download
curl -Lo tunels https://download.tunels.io/v0.0.1/freebsd/amd64/tunels
chmod +x tunels
sudo mv tunels /usr/local/bin/
Every release includes SHA-256 checksums to verify the integrity of your download. The install script verifies checksums automatically. For manual verification:
# Download the checksums
curl -fsSL https://download.tunels.io/checksums.json
# Verify on Linux
sha256sum tunels
# Verify on macOS
shasum -a 256 tunels
# Compare the output with the sha256 value in checksums.json for your platform
For convenience, move the Tunels binary to a directory in your system PATH so you can invoke it from anywhere without specifying the full path.
| Platform | Recommended Location | Command |
|---|---|---|
| Linux | /usr/local/bin/ |
sudo mv tunels /usr/local/bin/ |
| macOS | /usr/local/bin/ |
sudo mv tunels /usr/local/bin/ |
| Windows | C:\Program Files\Tunels\ |
Add directory to system PATH |
| FreeBSD | /usr/local/bin/ |
sudo mv tunels /usr/local/bin/ |
The general syntax for the Tunels CLI is:
tunels [options] <local-address>
The <local-address> is the address of the local service you want to expose. It can be a port number (8080), a host and port (localhost:8080), or an IP and port (192.168.1.100:3000). If you specify only a port number, Tunels assumes localhost as the host.
The following flags control tunnel behavior. All flags are optional except the local address.
| Flag | Default | Description |
|---|---|---|
-authtoken=TOKEN |
none | Your Tunels authentication token. Tokens begin with tun_live_ and are 64+ characters long. Required for static subdomains, multiple tunnels, and paid plan features. You can also set this in the config file or via the TUNELS_AUTH_TOKEN environment variable. |
-subdomain=NAME |
random | Request a specific subdomain for your tunnel URL (e.g., -subdomain=myapp gives you https://myapp.tunnels.solutions). Requires a Pro plan or higher. Names must be 4-63 characters, lowercase alphanumeric and hyphens only. |
-proto=PROTOCOL |
http |
The tunnel protocol. Valid values are http, tcp, and udp. HTTP tunnels get a public HTTPS URL. TCP tunnels receive a host:port pair. UDP tunnels receive a host:port pair. TCP and UDP require a Pro plan or higher. |
-config=FILE |
tunels.yml |
Path to the YAML configuration file. If the file exists in the current directory, it is loaded automatically. Use this flag to specify an alternate location. |
-host-header=HOST |
original | Rewrite the Host header to the specified value before forwarding to the local service. Useful when your local server uses virtual hosts or name-based routing. Example: -host-header=myapp.local. |
-inspect |
true |
Enable or disable the local web inspection interface at http://localhost:4040. The inspector shows all requests and responses passing through the tunnel. Set -inspect=false to disable. |
-log=DEST |
none |
Where to write log output. Valid values: stdout, stderr, none, or a file path (e.g., -log=/var/log/tunels.log). Logging is disabled by default. |
-log-level=LEVEL |
info |
Minimum log level. Valid values: debug, info, warn, error. The debug level includes TLS handshake details, connection lifecycle events, and raw message frames. |
start-all |
N/A | Instead of specifying a single local address, use start-all to start every tunnel defined in your configuration file. Example: tunels start-all. |
# Expose a local web server on port 3000
tunels localhost:3000
# Requires Pro plan or higher
tunels -authtoken=tun_live_abc123... -subdomain=myapi localhost:8080
# Result: https://myapi.tunnels.solutions -> http://localhost:8080
# Expose a local PostgreSQL database
tunels -proto=tcp -authtoken=tun_live_abc123... localhost:5432
# Result: tcp://0.tcp.tunnels.solutions:12345 -> localhost:5432
# Expose a local DNS server
tunels -proto=udp -authtoken=tun_live_abc123... localhost:5353
# Result: udp://0.udp.tunnels.solutions:54321 -> localhost:5353
# Useful for virtual-host-based local setups
tunels -host-header=myapp.dev localhost:80
# Full debug output saved to a log file
tunels -log=/tmp/tunels-debug.log -log-level=debug localhost:8000
# Define multiple tunnels in tunels.yml, then start them all
tunels -config=./tunels.yml start-all
# Run without the local inspection UI
tunels -inspect=false localhost:8000
Instead of passing flags on every invocation, you can define your settings in a tunels.yml configuration file. Tunels automatically looks for tunels.yml in the current working directory. You can specify a different path with -config=path/to/file.yml.
The configuration file uses YAML format. Here is a complete example with all available fields:
# tunels.yml - Full configuration reference
server_addr: tunnels.solutions:4443
trust_host_root_certs: true
auth_token: tun_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
inspect: true
log: stdout
log_level: info
tunnels:
webapp:
subdomain: myapp
proto: http
addr: localhost:3000
host_header: myapp.local
api:
subdomain: myapi
proto: http
addr: localhost:8080
database:
proto: tcp
addr: localhost:5432
| Field | Type | Description |
|---|---|---|
server_addr |
string | Address of the Tunels tunnel server. Defaults to tunnels.solutions:4443. Only change this if you are running a self-hosted Tunels server or connecting to a regional endpoint. |
trust_host_root_certs |
bool | Whether to trust the host system's root CA certificates when verifying the tunnel server's TLS certificate. Defaults to true. Set to false only in specialized environments. |
auth_token |
string | Your authentication token. Begins with tun_live_. Equivalent to the -authtoken flag. You can also set this via the TUNELS_AUTH_TOKEN environment variable. |
inspect |
bool | Enable or disable the local web inspection UI. Defaults to true. |
log |
string | Log destination: stdout, stderr, none, or a file path. |
log_level |
string | Minimum log level: debug, info, warn, or error. |
tunnels |
map | A map of named tunnel definitions. Each key is a friendly name used in logs and the inspector. See tunnel definition fields below. |
Each entry under tunnels supports the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
addr |
string | Yes | Local address to forward traffic to (e.g., localhost:3000, 192.168.1.5:8080). |
proto |
string | No | Protocol: http (default), tcp, or udp. |
subdomain |
string | No | Requested subdomain. Requires Pro plan or higher. |
host_header |
string | No | Rewrite the Host header before forwarding. HTTP tunnels only. |
On Gold and Premium plans, you can use your own domain name instead of a *.tunnels.solutions subdomain. To set up a custom domain:
tunnels.solutions:
myapp.example.com CNAME tunnels.solutions.
-hostname flag:
tunels -hostname=myapp.example.com -authtoken=tun_live_xxx localhost:3000
Tunels automatically provisions and renews TLS certificates for custom domains using Let's Encrypt.
You can set configuration values through environment variables. This is especially useful in CI/CD pipelines and containerized environments where you do not want to store tokens in files.
| Environment Variable | Equivalent Config Field |
|---|---|
TUNELS_AUTH_TOKEN | auth_token |
TUNELS_SERVER_ADDR | server_addr |
TUNELS_LOG | log |
TUNELS_LOG_LEVEL | log_level |
Environment variables take precedence over the config file, which takes precedence over command-line flag defaults. Explicit command-line flags always have the highest priority.
The Tunels REST API allows you to manage your account, tokens, subscriptions, and view usage metrics programmatically. All API endpoints return JSON responses.
https://tunels.io/api/v1
API requests are authenticated using either a JWT Bearer token (obtained from the login endpoint) or a CLI token (for machine-to-machine access). Include the token in the Authorization header:
# Using JWT token
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
# Using CLI token
Authorization: Bearer tun_live_xxxxxxxxxxxxxxxx
JWT access tokens expire after 15 minutes. Use the refresh endpoint to obtain a new access token. CLI tokens do not expire unless explicitly revoked.
All responses follow a consistent JSON envelope:
// Success
{
"data": { ... },
"error": null
}
// Error
{
"data": null,
"error": "Description of what went wrong"
}
/api/v1/auth/register
Create a new Tunels account. No authentication required.
{
"email": "[email protected]",
"password": "MySecureP@ss1",
"first_name": "Jane",
"last_name": "Doe"
}
{
"data": {
"user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "[email protected]",
"cli_token": "tun_live_abc123...",
"message": "Please verify your email address"
}
}
/api/v1/auth/login
Authenticate and receive JWT token pair. No authentication required.
{
"email": "[email protected]",
"password": "MySecureP@ss1"
}
{
"data": {
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"expires_at": "2026-03-09T15:30:00Z",
"token_type": "Bearer"
}
}
/api/v1/auth/me
Auth Required
Retrieve the current authenticated user's profile information.
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe",
"current_plan": "pro",
"email_verified": true,
"status": "active",
"created_at": "2026-01-15T10:30:00Z"
}
}
/api/v1/plans
List all available subscription plans. No authentication required.
{
"data": [
{
"code": "basic",
"name": "Basic",
"price_monthly": 0,
"max_tunnels": 1,
"bandwidth_gb_monthly": 1,
"rps_limit": 10
},
...
]
}
/api/v1/subscription
Auth Required
Get the current user's active subscription and plan details.
{
"data": {
"plan_code": "pro",
"plan_name": "Pro",
"status": "active",
"billing_cycle": "yearly",
"current_period_start": "2026-03-01T00:00:00Z",
"current_period_end": "2027-03-01T00:00:00Z",
"bandwidth_used_gb": 12.5
}
}
/api/v1/tokens
Auth Required
Create a new CLI authentication token. The full token value is only returned once in the response.
{
"name": "ci-pipeline-token"
}
{
"data": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"name": "ci-pipeline-token",
"token": "tun_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"created_at": "2026-03-09T12:00:00Z"
}
}
/api/v1/tokens
Auth Required
List all CLI tokens for the authenticated user. Token values are masked; only the last 4 characters are visible.
{
"data": [
{
"id": "b2c3d4e5-...",
"name": "ci-pipeline-token",
"last_used_at": "2026-03-09T11:45:00Z",
"created_at": "2026-03-01T10:00:00Z"
}
]
}
/api/v1/tokens/{id}
Auth Required
Revoke a CLI token. Any active tunnels using this token will be terminated immediately.
{
"data": {
"message": "Token revoked successfully"
}
}
/api/v1/metrics/current
Auth Required
Retrieve current billing period usage metrics including bandwidth consumption, active tunnel count, and request statistics.
{
"data": {
"bandwidth_used_gb": 12.5,
"bandwidth_limit_gb": 50,
"active_tunnels": 3,
"max_tunnels": 10,
"total_requests": 145230,
"period_start": "2026-03-01T00:00:00Z",
"period_end": "2026-04-01T00:00:00Z"
}
}
| Code | Meaning |
|---|---|
200 | Request succeeded |
201 | Resource created successfully |
400 | Bad request (invalid parameters, validation error) |
401 | Unauthorized (missing or invalid authentication) |
403 | Forbidden (insufficient permissions or plan level) |
404 | Resource not found |
409 | Conflict (e.g., email already registered) |
429 | Rate limited (too many requests) |
500 | Internal server error |
429 Too Many Requests response with a Retry-After header indicating how many seconds to wait before retrying.
Tunels supports three transport protocols for tunneling: HTTP/HTTPS, TCP, and UDP. Each is suited to different use cases and availability depends on your plan.
HTTP is the default and most common protocol. When you create an HTTP tunnel, Tunels assigns a public HTTPS URL (e.g., https://myapp.tunnels.solutions). All traffic between the public internet and the Tunels server is encrypted with TLS. The Tunels server terminates TLS and forwards plain HTTP to your local service.
Key features of HTTP tunnels:
localhost:4040 shows all HTTP requests and responses with full headers and bodies.wss://) work automatically over HTTP tunnels. No special configuration is required.# Basic HTTP tunnel
tunels localhost:3000
# HTTP tunnel with static subdomain
tunels -subdomain=myapp localhost:3000
# HTTP tunnel with Host header rewriting
tunels -host-header=app.local localhost:80
TCP tunnels forward raw TCP connections to your local service. This is essential for non-HTTP protocols such as databases, SSH, game servers, or any custom TCP-based application. TCP tunnels require a Pro plan or higher.
When you create a TCP tunnel, Tunels assigns a public host:port pair (e.g., 0.tcp.tunnels.solutions:12345). Remote clients connect to this address and their TCP connections are forwarded directly to your local service.
# Expose a local PostgreSQL database
tunels -proto=tcp -authtoken=tun_live_xxx localhost:5432
# Connect remotely: psql -h 0.tcp.tunnels.solutions -p 12345 -U myuser mydb
# Expose a local SSH server
tunels -proto=tcp -authtoken=tun_live_xxx localhost:22
# Connect remotely: ssh -p 12345 [email protected]
# Expose a Redis instance
tunels -proto=tcp -authtoken=tun_live_xxx localhost:6379
UDP tunnels forward UDP datagrams to your local service. This is useful for DNS servers, gaming servers, VoIP applications, and IoT devices that use UDP-based protocols. UDP tunnels require a Gold plan or higher.
# Expose a local DNS server
tunels -proto=udp -authtoken=tun_live_xxx localhost:5353
# Expose a game server
tunels -proto=udp -authtoken=tun_live_xxx localhost:27015
WebSocket connections work automatically over HTTP tunnels. When a client sends an HTTP Upgrade: websocket request to your tunnel URL, Tunels correctly handles the protocol upgrade and maintains the persistent bidirectional connection between the remote client and your local WebSocket server.
No special flags or configuration are needed. If your local server supports WebSocket, it works through the tunnel:
# Your local WebSocket server is at ws://localhost:8080/ws
tunels localhost:8080
# Remote clients connect to:
# wss://yoursubdomain.tunnels.solutions/ws
| Protocol | Basic | Student | Pro | Gold | Premium |
|---|---|---|---|---|---|
| HTTP/HTTPS | |||||
| TCP | |||||
| UDP | |||||
| WebSocket |
Security is fundamental to Tunels. Every tunnel connection is encrypted, tokens are securely stored, and multiple layers of access control are available to protect your exposed services.
All traffic between the internet and the Tunels server is encrypted with TLS 1.2+. The control channel between the Tunels client on your machine and the server uses a dedicated TLS connection on port 4443. HTTP tunnels automatically receive a valid HTTPS certificate, so visitors to your tunnel URL always connect over HTTPS.
The data channel between the Tunels client and server is also encrypted, meaning your local traffic is never transmitted in plaintext over the internet even for TCP and UDP tunnels.
In addition to the base tunnel URL, you can add authentication layers to restrict who can access your tunnels. These are particularly valuable when exposing staging environments or sensitive internal services.
Add HTTP Basic Authentication to your tunnel so visitors must enter a username and password before accessing your service. Available on Pro plans and above.
# Protect tunnel with Basic Auth
tunels -auth="user:password" localhost:8000
On Pro plans and above, you can restrict tunnel access to users who authenticate with their Google account. This is ideal for sharing staging environments with teammates who all have company Google accounts.
On Gold and Premium plans, you can restrict tunnel access to specific IP addresses or CIDR ranges. This is the most restrictive form of access control and is recommended for production-like environments.
# Allow only specific IPs (Gold/Premium plans)
tunels -cidr-allow="203.0.113.0/24,198.51.100.42/32" localhost:8000
Your CLI tokens (prefixed with tun_live_) are the keys to your Tunels account. They are 64+ characters of cryptographically random data and are used to authenticate tunnel connections and identify your plan.
chmod 600).gitignore if it contains your auth tokenDELETE /api/v1/tokens/{id}. All active tunnels using that token will be terminated instantly. Then create a new token.
Tunels offers five plans designed for different needs, from hobbyists exploring the tool to enterprises running production infrastructure. All paid plans include a 14-day free trial.
| Feature | Basic | Student | Pro | Gold | Premium |
|---|---|---|---|---|---|
| Price | Free | Free* | $5/mo | $15/mo | $50/mo |
| Tunnels | 1 | 1 | 5 | 25 | Unlimited |
| Subdomain Type | Random | Random | Static | Custom domain | Custom + Dedicated IP |
| Bandwidth | 3 GB/mo | 15 GB/mo | 75 GB/mo | 200 GB/mo | Unlimited |
| RPS Limit | 10 | 50 | 200 | 500 | Unlimited |
| Protocols | HTTP | HTTP, TCP | HTTP, TCP | HTTP, TCP, UDP | HTTP, TCP, UDP, SMTP |
| Auth Options | — | — | Basic Auth | Basic, OAuth, IP Allow | Basic, OAuth, Google, IP, Custom |
| Request Inspection | |||||
| Request Replay | |||||
| Support | Community | Student forum | Priority email | Priority + Slack + Phone |
* Student plan requires verification with a valid university email and proof of enrollment.
Each plan has a maximum requests-per-second (RPS) limit. This is the sustained rate of HTTP requests your tunnel can handle. Tunels uses a token bucket algorithm with a burst multiplier, meaning short spikes above the limit are allowed:
429 Too Many Requests response from the Tunels server. Your local service never sees these requests.Bandwidth usage is tracked per billing period (monthly). Both inbound and outbound traffic through your tunnels counts toward your limit. When you approach your limit:
You can monitor your current bandwidth usage at any time from the account billing page or via the GET /api/v1/metrics/current API endpoint.
This section covers the most commonly encountered issues and their solutions. If you do not find your issue here, enable debug logging (see below) and contact support.
Cause: The tunnel is running but your local service is not listening on the specified address and port.
Solutions:
curl http://localhost:8000127.0.0.1 specifically, use tunels 127.0.0.1:8000 instead of tunels localhost:8000.Cause: The auth token is invalid, expired, or has been revoked.
Solutions:
tun_live_ and is complete (no truncation from copy-paste).Cause: You have reached the maximum number of concurrent tunnels allowed by your plan.
Solutions:
Ctrl+C in the terminal where they are running).Cause: Another user currently has an active tunnel on the requested subdomain.
Solutions:
Cause: macOS Gatekeeper blocks unsigned binaries downloaded from the internet.
Solution: Remove the quarantine attribute:
xattr -d com.apple.quarantine ./tunels
Cause: Your corporate network or firewall blocks outbound connections to port 4443 (the Tunels control channel).
Solutions:
tunnels.solutions:4443.HTTPS_PROXY environment variable before running Tunels.Cause: WebSocket upgrade is not reaching your local server.
Solutions:
new WebSocket("ws://localhost:8080/ws").-host-header flag.Cause: Several factors can affect tunnel throughput.
Solutions:
localhost:4040 for 429 responses).tunels -inspect=false localhost:8000.When troubleshooting, enable full debug logging to capture detailed information about the tunnel lifecycle, TLS handshakes, and message frames:
# Full debug output to stdout
tunels -log=stdout -log-level=debug localhost:8000
# Save debug logs to a file for sharing with support
tunels -log=/tmp/tunels-debug.log -log-level=debug localhost:8000
When contacting support, include the debug log file and the output of tunels version. This dramatically speeds up diagnosis.
Tunels is not just a development tool. You can integrate it into CI/CD pipelines, Docker workflows, and team collaboration setups for staging environments and deploy previews.
Use Tunels in your CI/CD pipeline to create temporary public URLs for integration testing, end-to-end tests, or deploy previews. The tunnel starts before your tests run and stops when the job finishes.
# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start application
run: |
npm start &
sleep 5
- name: Install Tunels
run: curl -fsSL https://tunels.io/install.sh | bash
- name: Start tunnel
env:
TUNELS_AUTH_TOKEN: ${{ secrets.TUNELS_TOKEN }}
run: |
./tunels -log=stdout -subdomain=pr-${{ github.event.number }} localhost:3000 &
sleep 3
- name: Run E2E tests
env:
BASE_URL: https://pr-${{ github.event.number }}.tunnels.solutions
run: npm run test:e2e
Give every pull request its own public URL so reviewers can interact with the changes without checking out the branch locally. Use the PR number as the subdomain for predictable URLs:
https://pr-42.tunnels.solutions for PR #42https://pr-123.tunnels.solutions for PR #123Add a bot comment to the PR with the preview URL so reviewers can access it with one click.
Run Tunels alongside your application in a Docker Compose setup. This is useful for development environments that already use Docker.
# docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
tunnel:
image: alpine:latest
depends_on:
- app
environment:
- TUNELS_AUTH_TOKEN=${TUNELS_AUTH_TOKEN}
command: |
sh -c "wget -qO- https://tunels.io/install.sh | bash &&
tunels -subdomain=myapp -inspect=false app:3000"
Use environment variables to configure Tunels differently across environments. This avoids storing tokens in configuration files and makes it easy to use different settings in development, CI, and staging.
# Development (.env.development)
TUNELS_AUTH_TOKEN=tun_live_dev_token_xxx
TUNELS_LOG_LEVEL=debug
# CI/CD (.env.ci)
TUNELS_AUTH_TOKEN=tun_live_ci_token_xxx
TUNELS_LOG=stdout
TUNELS_LOG_LEVEL=warn
# Staging (.env.staging)
TUNELS_AUTH_TOKEN=tun_live_staging_token_xxx
TUNELS_LOG_LEVEL=error
For teams, each developer should have their own Tunels account and token. This ensures proper attribution, separate tunnel limits, and the ability to revoke access individually. Recommended setup:
{project}-{developer} (e.g., api-jane, api-john) so team members know which tunnel belongs to whom.TUNELS_AUTH_TOKEN in your shell profile (~/.bashrc, ~/.zshrc) so it is automatically available in every terminal session without passing it as a flag each time.