Server reference
The server is the part of tunnel-me that runs on a public machine — a VPS, a small cloud instance, or any always-on host with a reachable IP address. It owns your domain: visitors open name.your-domain.com, and the server forwards that request through the tunnel to the machine where your service actually runs.
The server is one command:
tunnel-me serve
(run does the same thing.)
Command line and environment
Every setting is a command-line flag and every flag has a matching environment variable with the TUNNEL_ prefix. Environment variables are convenient in Docker and systemd. For example, --domain can also be set as TUNNEL_DOMAIN.
| Flag | Environment variable | Default | What it does |
|---|---|---|---|
--bind |
TUNNEL_BIND |
:8080 |
Address and port to listen on |
--domain |
TUNNEL_DOMAIN |
localhost |
Your base domain. Requests to this domain (or to anything that is not one of its subdomains) show the admin panel |
--public-url |
TUNNEL_PUBLIC_URL |
http://localhost:8080 |
The address visitors actually use. It appears in the connect commands shown in the panel and in sign-in redirects, so it must be reachable from the outside |
--db |
TUNNEL_DB |
tunnel.db |
Path to the database file. Created on first start |
--tls.enabled |
TUNNEL_TLS_ENABLED |
off | Let the server handle HTTPS itself |
--tls.cert |
TUNNEL_TLS_CERT |
/etc/tls/tls.crt |
TLS certificate file |
--tls.key |
TUNNEL_TLS_KEY |
/etc/tls/tls.key |
TLS private key file |
--binaries-dir |
TUNNEL_BINARIES_DIR |
/opt/tunnel-me/binaries |
Where the server finds client binaries for the one-line installer |
--stats-interval |
TUNNEL_STATS_INTERVAL |
10s |
How often live connections are measured for the charts |
--stats-buffer |
TUNNEL_STATS_BUFFER |
8192 |
Queue size for measurements before they are written to the database |
--auth |
TUNNEL_AUTH |
static |
Who can open the admin panel. Values: basic, file, static, oidc, webauth — see Authentication |
--graceful-timeout |
GRACEFUL_TIMEOUT |
3s |
How long running requests may finish when the server shuts down |
--cors |
TUNNEL_CORS |
off | Allow browser scripts from any website to call the API. Dangerous — only useful for development |
The sub-flags of --auth.* are listed in Authentication.
How the server routes requests
One port answers everything. Routing is decided by the hostname in the request:
| Visitor opens | What they get |
|---|---|
tunnels.example.com (the base domain) or any name that is not a subdomain of it |
The admin panel, plus the HTTP API at /api/v1 |
myapp.tunnels.example.com (a subdomain) |
The tunnel whose sub-domain is myapp |
/boot/... |
The one-line installer and client binaries |
/agent |
The connection endpoint agents use (you never open this in a browser) |
This is why the wildcard DNS record and (if you terminate TLS yourself) a wildcard certificate are needed — every tunnel is just another subdomain. See Getting started for the DNS setup and Reverse proxy for HTTPS.
TLS
You have two options for HTTPS:
- Terminate TLS in front of tunnel-me (Caddy, nginx, traefik, or a cloud load balancer). This is the most common setup — the proxy owns the certificates and forwards plain HTTP to port 8080. See Reverse proxy.
- Let tunnel-me handle HTTPS with
--tls.enabled. The certificate must cover both the wildcard and the base domain — for example one certificate listing*.tunnels.example.comandtunnels.example.com. A certificate for the base domain alone will produce browser warnings on every tunnel.
If TLS terminates in front of the server and you use OIDC sign-in, also pass --auth.oidc.trust-proxy so sign-ins work with the proxy in the middle.
The database
Everything you configure — tunnels, tokens, users, groups, providers, statistics — lives in a single SQLite database file (--db, default tunnel.db in the working directory). It is created on first start and upgraded automatically when you run a newer version.
To back up, stop the server (or use any SQLite-aware backup tool) and copy the file. To move the server, copy the file.
Usage statistics
While a connection is live, the server measures its ping and traffic every --stats-interval (default 10 seconds). The panel's charts show speed (in/out), ping, and the number of connected agents over the last hour, 24 hours, 7 days, or 30 days.
Charts always group measurements in one-minute-or-wider buckets, so keep --stats-interval at one minute or below — sampling less often than that adds nothing. Old measurements are cleaned up automatically: a tunnel keeps its last month, or its 1000 newest samples, whichever holds.
One-line installer support
The installer script that the panel offers (Agents → your tunnel → Overview) downloads the client binary from the server itself:
/boot/connect/<token>returns the script tailored to your server address and token./boot/instance/<os>/<arch>returns the binary. The server serves its own binary when the requesting platform matches what the server runs on; for other platforms it serves files namedtunnel-me_<os>-<arch>from--binaries-dir.
Supported platforms for the installer: Linux and macOS on x86_64 and arm64. The official Docker image already ships with client binaries for all of them, so the installer works out of the box there.
Protecting the admin panel
The default (
--auth static) performs no credential check at all. Out of the box anyone who reaches the server can open the panel. Before exposing the port, pick a real method — Authentication walks through all five.
Quick summary:
--auth value |
Who gets in |
|---|---|
static (default) |
Everyone — no check performed |
basic |
Whoever knows the username/password from --auth.basic.* (browser login prompt) |
file |
Users from an htpasswd file (--auth.file.filename) |
oidc |
Anyone your identity provider signs in (--auth.oidc.*) |
webauth |
Requests carrying the header named by --auth.web.header — set by an authenticating proxy in front |
Protecting the panel never protects tunnels — each tunnel has its own sign-in setting, configured in the panel.
Running with Docker
The official image contains both the server and the client binaries:
docker run -d --name tunnel-me \
-p 8080:8080 \
-v ./data:/data \
--restart unless-stopped \
ghcr.io/reddec/tunnel-me serve \
--domain tunnels.example.com \
--public-url https://tunnels.example.com \
--db /data/tunnel.db \
--auth basic \
--auth.basic.username me \
--auth.basic.password change-me-now
The same thing as a compose file:
services:
tunnel-me:
image: ghcr.io/reddec/tunnel-me
command: >-
serve
--domain tunnels.example.com
--public-url https://tunnels.example.com
--db /data/tunnel.db
--auth basic
--auth.basic.username me
--auth.basic.password change-me-now
ports:
- "8080:8080"
volumes:
- ./data:/data
restart: unless-stopped
The database lives in the mounted ./data directory — back that up.