# https://tunnels.io/docs

Documentation

# Everything you need to know.

Install, configure, and use tunnels.io to expose your local services to the internet securely. From your first tunnel to advanced configurations.

On this page

Getting started Installation CLI reference Configuration API reference Protocols Security Plans & limits Troubleshooting Deployment

---

Quick links

[Download client](https://tunnels.io/downloads) [View plans](https://tunnels.io/pricing) [Create account](https://tunnels.io/signup)

## Getting started

### What is tunnels.io?

tunnels.io 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, tunnels.io creates an encrypted tunnel from a public URL directly to a port on your machine.

When you start a tunnel, you get a public URL such as `https://myapp.tunnels.host``. Any HTTP or TCP traffic sent to that URL is forwarded through an encrypted TLS connection to your local service. No firewall config, no router port-forwarding, no remote deploy.

### System requirements

The client is a single static binary with no external dependencies.

| Platform | Architecture | Minimum OS |
| --- | --- | --- |
| 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+ |

### Quick install (recommended)

One command. The installer detects your OS and architecture, downloads the correct binary, verifies the checksum, and installs it.

```
# Linux, macOS, FreeBSD
curl -fsSL https://tunnels.io/install.sh | bash
# Or with wget
wget -qO- https://tunnels.io/install.sh | bash
# Windows (PowerShell)
Invoke-WebRequest -Uri https://download.tunnels.io/0.0.70/windows/amd64/tunnels.exe -OutFile tunnels.exe
```

On Linux, macOS, and FreeBSD the installer places the binary in `/usr/local/bin/tunnels`` (or `~/.local/bin/tunnels`` without sudo).

### Manual installation

Prefer to install by hand? Download from the [Downloads page](https://tunnels.io/downloads) or:

```
# Linux (amd64)
curl -Lo tunnels https://download.tunnels.io/0.0.70/linux/amd64/tunnels
# macOS (Apple Silicon)
curl -Lo tunnels https://download.tunnels.io/0.0.70/darwin/arm64/tunnels
chmod +x tunnels
sudo mv tunnels /usr/local/bin/
```

**Verify the installation:**

```
tunnels version
# 0.0.70
```

### Creating your first tunnel

Have a local server on port 8000? Expose it:

`tunnels http 8000`

tunnels.io connects, allocates a public URL, and forwards traffic:

```
Tunnel Status: online
Version:       0.0.70
Web Interface: http://127.0.0.1:4040
Forwarding:    https://a1b2c3d4.tunnels.host -> http://localhost:8000
```

**Web Interface:** a local inspection UI at `http://localhost:4040`` shows every request through the tunnel with its method, path, status, duration, and full request and response headers. Invaluable for debugging webhooks.

### Signing up and getting an auth token

An account unlocks static subdomains, multiple tunnels, TCP, and higher limits:

1. Visit [tunnels.io/signup](https://tunnels.io/signup) and create your account.
2. Verify your email via the link sent to your inbox.
3. Your initial CLI token (`tnl_…``) is shown in your dashboard. Copy it.
4. Create additional tokens from **Account > API Tokens**.

### Using your auth token

```
tunnels config add-authtoken tnl_xxxxxxxxxxxxxxxxxx
tunnels http 8000
```

---

## Installation

tunnels.io ships as a single static binary, no dependencies, runtimes, or package managers. Fastest path:

```
# Automatic install (detects OS + architecture)
curl -fsSL https://tunnels.io/install.sh | bash
```

### Linux

amd64, arm64, 386, and arm.

```
curl -fsSL https://tunnels.io/install.sh | bash
# Or manual (amd64)
curl -Lo tunnels https://download.tunnels.io/0.0.70/linux/amd64/tunnels
chmod +x tunnels && sudo mv tunnels /usr/local/bin/ && tunnels version
```

### macOS

Intel (amd64) and Apple Silicon (arm64).

```
# Apple Silicon
curl -Lo tunnels https://download.tunnels.io/0.0.70/darwin/arm64/tunnels
chmod +x tunnels
```

**macOS Gatekeeper:** the macOS binary is signed with a Developer ID certificate and notarized by Apple, so it runs without a Gatekeeper prompt. If you moved or rewrote the file after downloading and macOS still blocks it, clear the quarantine flag once with `xattr -d com.apple.quarantine ./tunnels``.

### Windows

```
# PowerShell
Invoke-WebRequest -Uri https://download.tunnels.io/0.0.70/windows/amd64/tunnels.exe -OutFile tunnels.exe
.\tunnels.exe version
```

### FreeBSD

```
curl -Lo tunnels https://download.tunnels.io/0.0.70/freebsd/amd64/tunnels
chmod +x tunnels && sudo mv tunnels /usr/local/bin/
```

### Verifying with SHA-256 checksums

Every release includes checksums; the install script verifies automatically. Manually:

```
curl -fsSL https://download.tunnels.io/checksums.json
sha256sum tunnels          # Linux
shasum -a 256 tunnels      # macOS
```

### Adding to PATH

| Platform | Location | Command |
| --- | --- | --- |

```
| Linux | /usr/local/bin/ | sudo mv tunnels /usr/local/bin/ |
| macOS | /usr/local/bin/ | sudo mv tunnels /usr/local/bin/ |
| Windows | C:\Program Files\Tunnels\ | Add directory to PATH |
| FreeBSD | /usr/local/bin/ | sudo mv tunnels /usr/local/bin/ |
```

---

## CLI reference

### Basic syntax

`tunnels [options] <local-address>`

The `<local-address>`` is the local service to expose: a port (`8080``), host:port (`localhost:8080``), or IP:port. A bare port assumes `localhost``.

### Command-line flags

| Flag | Default | Description |
| --- | --- | --- |
|`--authtoken TOKEN` | none | Your auth token (begins `tnl_``). Required for static subdomains, multiple tunnels, and paid features. Also settable via config or `tunnels config add-authtoken``. |
|`--subdomain NAME` | random | Request a specific subdomain (Pro+). 4-63 chars, lowercase alphanumeric and hyphens. |
|`--proto PROTOCOL` | https | `http`` or `tcp``. TCP requires Pro or higher. |
|`--config FILE` | ~/.tunnels | Path to the YAML config file. Auto-loaded from the current directory if present. |
|`--log DEST` | none | `stdout``, `stderr``, `none``, or a file path. |
|`--log-level LEVEL` | info | `debug``, `info``, `warn``, `error``. |
|`start-all` | N/A | Start every tunnel defined in your config file. |

### Usage examples

```
# Basic HTTP tunnel
tunnels localhost:3000
# Authenticated, static subdomain (Pro+)
tunnels --authtoken tnl_abc123 --subdomain myapi localhost:8080
# -> https://myapi.tunnels.host
# TCP tunnel for a database
tunnels --proto tcp --authtoken tnl_abc123 localhost:5432
# Debug logging to a file
tunnels --log /tmp/tunnels-debug.log --log-level debug localhost:8000
# Start all tunnels from config
tunnels --config ./my-tunnels.yaml start-all
```

---

## Configuration

Settings live in `~/.tunnels``, a YAML file the client reads on every run. Point at a different file with `--config path/to/file``. The easiest way to create it is `tunnels config add-authtoken TOKEN``, which writes the file for you.

### File format

Note the shape of `proto``: it is a map of protocol to local address, not a string. There is no separate `addr`` field.

```
# ~/.tunnels
server_addr: tunnels.host:443
auth_token: tnl_xxxxxxxxxxxxxxxxxxxxxxxx
inspect_addr: 127.0.0.1:4040
tunnels:
webapp:
subdomain: myapp
proto:
http: localhost:3000
database:
proto:
tcp: localhost:5432
```

Start them with `tunnels start webapp``, or `tunnels start-all``.

### Configuration fields

| Field | Type | Description |
| --- | --- | --- |
|`server_addr` | string | Tunnel server address. Default `tunnels.host:443``. |
|`auth_token` | string | Auth token (begins `tnl_``). Same as `--authtoken``. Set it with `tunnels config add-authtoken``. |
|`inspect_addr` | string | Address for the local request inspector. Default `127.0.0.1:4040``. Set it to `disabled`` to turn the inspector off. Left at the default, a second client automatically takes the next free port (4041, 4042, and so on) and prints the one it used. Set it explicitly and the client binds that address or reports that it could not. |
|`http_proxy` | string | Outbound proxy for the connection to the tunnel server. |
|`trust_host_root_certs` | bool | Use the operating system trust store for the server connection. |
|`tunnels` | map | Named tunnel definitions. Each entry takes `proto`` (a map of protocol to local address), and optionally `subdomain``, `domain``, `hostname``, `auth`` and `remote_port``. |

### Custom domains

On Gold and Premium plans, use your own domain instead of a `*.tunnels.host`` subdomain:

1. Add a CNAME pointing your domain to `tunnels.host``: `myapp.example.com    CNAME    tunnels.host.`

2. Wait for DNS propagation (5-30 min typical).
3. Start with the `-hostname`` flag: `tunnels --hostname myapp.example.com --authtoken tnl_xxx localhost:3000`

TLS certificates for custom domains are provisioned and renewed automatically via Let's Encrypt.

### Environment variables

The client reads the variables below. Note the prefix is `TUNELS_`` with a single N, which does not match the product name. There is no environment override for the auth token: set it in the config file with `tunnels config add-authtoken``, or pass `--authtoken``.

| Environment Variable | Effect |
| --- | --- |
|`TUNELS_NO_UPDATE_CHECK` | Set to `1`` to skip the once-a-day update check and its banner. Recommended in CI. |
|`TUNELS_NO_TELEMETRY` | Set to `1`` to disable telemetry. |
|`TUNELS_API_BASE_URL` | Override the platform API base the client talks to. Rarely needed. |
|`http_proxy` | Standard outbound proxy for the connection to the tunnel server. |

**Priority (highest to lowest):** command-line flags, then the config file, then built-in defaults.

---

## API reference

The REST API manages your account, tokens, subscriptions, and usage metrics. All responses are JSON.

### Base URL

`https://tunnels.io/api/v1`

### Authentication

Authenticate with a JWT Bearer token (from login) or a CLI token, in the `Authorization`` header. JWT access tokens expire after 15 minutes; CLI tokens don't expire unless revoked.

```
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...     # JWT
Authorization: Bearer tnl_xxxxxxxxxxxxxxxx     # CLI token
```

### Response format

```
// Success
{ "data": { ... }, "error": null }
// Error
{ "data": null, "error": "Description of what went wrong" }
```

### Endpoints

`POST``/api/v1/auth/register``

Create a new account. No auth required.

`{ "email": "dev@example.com", "password": "MySecureP@ss1", "first_name": "Jane" }`

Password: 8-72 chars; ≥1 upper, lower, digit, special.

`POST``/api/v1/auth/login``

Authenticate and receive a JWT token pair.

`{ "access_token": "eyJ…", "refresh_token": "eyJ…", "token_type": "Bearer" }`

`GET``/api/v1/auth/me``Auth required

The current user's profile (email, plan, status, verified).

`GET``/api/v1/plans``

List all subscription plans (code, name, price, limits). No auth.

`GET``/api/v1/subscription``Auth required

Active subscription, plan, billing cycle, and bandwidth used.

`POST``/api/v1/tokens``Auth required

Create a CLI token. The full `tnl_…`` value is returned only once.

`GET``/api/v1/tokens``Auth required

List CLI tokens (values masked to last 4 chars).

`DELETE``/api/v1/tokens/{id}``Auth required

Revoke a token. Active tunnels using it are terminated immediately.

`GET``/api/v1/metrics/current``Auth required

Current period usage: bandwidth, active tunnels, total requests.

### HTTP status codes

|`200` | Request succeeded |
| --- | --- |
|`201` | Resource created |
|`400` | Bad request (validation error) |
|`401` | Unauthorized |
|`403` | Forbidden (plan/permission) |
|`404` | Not found |
|`409` | Conflict (e.g. email already registered) |
|`429` | Rate limited |
|`500` | Internal server error |

**Rate limiting:** the API limits per IP. Exceeding it returns `429 Too Many Requests`` with a `Retry-After`` header.

---

## Protocols

Two transport protocols: HTTP/HTTPS and TCP. TCP requires a paid plan.

### HTTP/HTTPS tunneling

The default. You get a public HTTPS URL (e.g. `https://myapp.tunnels.host``); the server terminates TLS and forwards plain HTTP to your local service.

- **Automatic HTTPS** — every tunnel gets a valid TLS cert, no config.
- **Request inspection** — the local UI at `localhost:4040``.
- **Host header rewriting** — for virtual-host local setups.
- **WebSocket support** — `wss://`` works automatically.

### TCP tunneling

Forward raw TCP for databases, SSH, game servers (Pro+). You get a public `host:port`` like `0.tcp.tunnels.host:12345``.

```
# PostgreSQL
tunnels --proto tcp --authtoken tnl_xxx localhost:5432
# psql -h 0.tcp.tunnels.host -p 12345 -U user db
# SSH
tunnels --proto tcp --authtoken tnl_xxx localhost:22
```

**Security warning:** TCP tunnels expose raw access. Ensure your service has authentication (DB passwords, SSH keys) and use IP restrictions on Gold/Premium.

### Protocol availability by plan

| Protocol | Basic | Student | Pro | Gold | Premium |
| --- | --- | --- | --- | --- | --- |
| HTTP/HTTPS | | | | | |
| TCP | | | | | |
| WebSocket | | | | | |

---

## Security

### TLS encryption

All traffic between the internet and the server is TLS 1.2+. The control channel between client and server uses a dedicated TLS connection. HTTP tunnels automatically receive a valid HTTPS certificate; the data channel is encrypted for TCP tunnels too.

### Authentication methods

- **Basic Auth (Pro+)** — `tunnels -auth="user:password" localhost:8000``
- **OAuth / Google (Pro+)** — restrict access to Google-authenticated users.
- **IP allowlisting (Gold/Premium)** — `tunnels -cidr-allow="203.0.113.0/24" localhost:8000``

### Token security

CLI tokens (`tnl_``) are 64+ chars of cryptographically random data, the keys to your account.

### Best practices

#### Do

- Store tokens in env vars or `chmod 600`` config files
- Use separate tokens per environment
- Rotate tokens periodically
- Use IP restrictions for sensitive services
- Add config files containing tokens to `.gitignore``

#### Do not

- Commit auth tokens to version control
- Share tokens in chat, email, or forums
- Expose production databases without IP restrictions
- Leave tunnels running unauthenticated
- Reuse one token across team members

**If a token is compromised:** revoke it immediately from **Account > API Tokens** or `DELETE /api/v1/tokens/{id}``. Active tunnels are terminated instantly. Then create a new one.

---

## Plans & limits

### Plan comparison

| Feature | Basic | Student | Pro | Gold | Premium |
| --- | --- | --- | --- | --- | --- |
| Protocols | HTTP/HTTPS | HTTP/HTTPS | + TCP | + TCP | + TCP |
| Subdomain | Random | Random | Static | Custom domain | Custom domain |
| Support | Community | Forum | Email | Priority email | Priority + Slack |

Current prices, bandwidth allowances, request-rate limits and tunnel counts are on the [pricing page](https://tunnels.io/pricing), which reads them live from the plan catalog.

The Student plan requires verification with a valid university email address.

### Rate limiting (RPS)

Each plan has a max requests-per-second. A token-bucket algorithm with a burst multiplier allows short spikes at 2-5x. Requests over the limit get a `429`` from the server, your local service never sees them.

### Bandwidth enforcement

- **80% usage** — email notification.
- **100% usage** — a 24-hour grace period; traffic keeps flowing.
- **Grace expired** — new connections denied until next period or upgrade.

**Need more?** Upgrade anytime from the [Pricing page](https://tunnels.io/pricing). Changes take effect immediately and are prorated.

---

## Troubleshooting

"Connection refused" or "502 Bad Gateway"

**Cause:** the tunnel is up but your local service isn't listening on that address/port. **Fix:** verify with `curl http://localhost:8000``; match the port; try `127.0.0.1:8000`` if your service binds there specifically.

"Authentication failed" / "Invalid auth token"

**Cause:** token invalid, revoked, or account unverified. **Fix:** confirm it starts with `tnl_`` and isn't truncated; check it isn't revoked; verify your email; create a new token.

"Tunnel limit exceeded"

**Cause:** at your plan's concurrent-tunnel cap. **Fix:** close unused tunnels (Ctrl+C); orphaned ones auto-clean within minutes; [upgrade](https://tunnels.io/pricing) for more.

macOS: "developer cannot be verified"

The macOS binary is Developer ID signed and notarized, so this should not happen on a fresh download. It can occur if the file was moved, rewritten or unpacked by a tool that dropped the signature. **Fix:** re-download it, or clear the quarantine flag with `xattr -d com.apple.quarantine ./tunnels``

Firewall / corporate proxy blocking connection

**Fix:** allow outbound TCP to `tunnels.host:443``; behind an HTTP proxy set `HTTPS_PROXY``; test from a mobile hotspot to confirm it's the network.

Slow tunnel performance

**Fix:** throughput can't exceed your upload bandwidth; check for 429s in the inspector; disable inspection for high throughput (`inspect_addr: disabled``); a slow backend looks like a slow tunnel.

Inspector is on a different port, or shows another tunnel's traffic

Each running client needs its own inspector port. When you start a second client, it takes the next free port after `4040`` and prints the address it actually bound, so read the **Web Interface** line rather than assuming `4040``. Any inspector page also lists the other clients running on your machine, so you can move between them. **Fix for older clients:** versions before this behaviour shipped kept printing `127.0.0.1:4040`` even when the port was already taken, which sent you to the first client's traffic; upgrade with `tunnels update``. To put several tunnels in one inspector instead, run them from one process with `tunnels start-all``.

### Enabling debug logs

```
tunnels --log stdout --log-level debug localhost:8000
# Or to a file for support
tunnels --log /tmp/tunnels-debug.log --log-level debug localhost:8000
```

---

## Deployment

Integrate tunnels.io into CI/CD pipelines, Docker workflows, and team setups for staging and deploy previews.

### GitHub Actions example

```
# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Install tunnels
run: curl -fsSL https://tunnels.io/install.sh | bash
- name: Start tunnel
env: { TUNELS_NO_UPDATE_CHECK: "1" }
run: |
tunnels config add-authtoken ${{ secrets.TUNNELS_TOKEN }}
tunnels --subdomain pr-${{ github.event.number }} http 3000 &
```

### Deploy previews for pull requests

Give every PR its own public URL using the PR number as the subdomain: `https://pr-42.tunnels.host``. Post the URL as a bot comment so reviewers click once.

### Docker integration

```
# docker-compose.yml
services:
app:
build: .
ports: ["3000:3000"]
tunnel:
image: alpine:latest
depends_on: [app]
environment: ["TUNNELS_TOKEN=${TUNNELS_TOKEN}"]
command: sh -c "wget -qO- https://tunnels.io/install.sh | bash && tunnels config add-authtoken $TUNNELS_TOKEN && tunnels --subdomain myapp http app:3000"
```

### Team collaboration

1. **Each developer** has a personal account + token.
2. **CI/CD** uses a dedicated service-account token stored as a repo secret.
3. **Subdomain convention**: `{project}-{developer}`` (e.g. `api-jane``).
4. **Shared staging**: a team service account for always-on tunnels.

**Pro tip:** run `tunnels config add-authtoken TOKEN`` once. It saves the token to `~/.tunnels`` so every later command picks it up automatically.

↑ Back to top
