Self-Hosting GoTunnel¶
GoTunnel ships as a single binary that is both the client and the server, so you can run your own tunnel server instead of using the hosted server. Self-hosting needs no database, Redis, or email — that machinery only exists for the hosted server's device enrollment and usage limits. All you need is the binary, a machine with a public IP, and a shared token.
There are two roles:
- Server — runs on a machine reachable from the internet (a VPS).
gotunnel server - Client — runs next to the local service you want to expose.
gotunnel/gotunnel client
1. Install the binary on both machines¶
Install gotunnel on the server with any method from the install guide; on Linux the one-liner is curl -fsSL https://gotunnel.site/install.sh | sh.
2. Start the server¶
Pick a token your clients will use to authenticate (any long random string):
# simplest: plaintext control channel, public ports start at 10000
gotunnel server --addr :9000 --start-port 10000 --auth-tokens "your-strong-token"
With TLS on the control channel (recommended over the public internet):
gotunnel server \
--addr :9000 --tls \
--tls-cert /path/server-cert.pem \
--tls-key /path/server-key.pem \
--auth-tokens "your-strong-token"
--auth-tokenstakes one or more comma-separated tokens (or setGOTUNNEL_AUTH_TOKENS). There is no default token — the server won't start without one.- Open the tunnel port (
:9000) and the public port range (from--start-portupward) in your firewall. - Add
--max-connections,--max-auth-failures, and--auth-lockoutto taste. - To require client certificates, add
--tls-client-ca /path/ca.pemwith--tlsand--ws-addr=. The WebSocket listener can't check certificates, so the server won't start with it enabled. - The server allows 32 unauthenticated connections per IP. A stream proxy in front of the tunnel port makes every client look like one IP, so prefer direct connections or the WebSocket route for busy servers.
3. Connect a client¶
export GOTUNNEL_TOKEN="your-strong-token"
gotunnel http 3000 --server your-vps-host:9000
You'll get a public your-vps-host:<assigned-port> that forwards to localhost:3000, with the request inspector at the link it prints, like on the hosted server. With TLS on the server, add --tls --tls-ca /path/ca-cert.pem, and --tls-servername if your certificate name differs from the host you dial.
The token goes in GOTUNNEL_TOKEN rather than --token so it doesn't show up in the process list. gotunnel audit warns if you pass it with --token. Set it even if this machine is registered with the hosted server: a saved token is never sent to another server, so without GOTUNNEL_TOKEN the client stops with an error.
4. (Optional) Subdomain mode¶
To hand out https://<random>.tunnel.example.com URLs instead of ports, run the server with --base-domain tunnel.example.com --http-addr 127.0.0.1:8080, point a wildcard DNS record at the box, and put a TLS-terminating reverse proxy (nginx/Caddy) in front of the HTTP router. This is how the hosted server runs.
Your proxy must follow these rules, or one tunnel's visitors can reach another tunnel:
- Never reuse upstream connections to the HTTP router. It binds each connection to the first request's
Host. In nginx, useproxy_passdirectly with noupstreamblock usingkeepalive. In Caddy, settransport http { keepalive off }on thereverse_proxy. - Overwrite
X-Real-IPwith the real client address on every route to the WebSocket and API ports. The server trusts this header from loopback peers. - Ignore
X-Accel-*response headers from tunnels. In nginx, useproxy_ignore_headersandproxy_hide_headerfor them. - Rewrite tunnel cookies to their own host, for example
proxy_cookie_domain ~^.*$ $host;, and serve tunnels from a domain on the Public Suffix List where you can, so tunnels can't set cookies for each other.
5. (Optional) TCP tunnels¶
Let clients share databases and other TCP services with gotunnel tcp <port>. Each tunnel gets its own public port from a range you open in your firewall, and is locked to approved IPs.
GOTUNNEL_TCP_ENABLED=true
GOTUNNEL_TCP_BASE_DOMAIN=tcp.example.com
GOTUNNEL_TCP_PORT_START=20000
GOTUNNEL_TCP_PORT_END=20099
- Open the range in your firewall, e.g.
ufw allow 20000:20099/tcp, and point a DNS-only wildcard*.tcp.example.comat the server (A record only). - Without anything else, only
--allowlistIPs can connect. - Approval links: set
GOTUNNEL_TCP_APPROVAL_URL=https://approve.tcp.example.com, run the server with--api-addr, configure Redis and SMTP for the one-time codes, and proxy/a/and/approve/on that host to the API address. The approval host must be IPv4-only and DNS-only, because the browser's IP is the one that gets approved. - Audit log and bans: set
GOTUNNEL_DATABASE_URL. - Admin commands: set
GOTUNNEL_ADMIN_SOCKET=/run/gotunnel/admin.sock, thensudo -u <service-user> gotunnel admin sessions. - Kernel gating: load an nftables table named
gotunnelwith the setstcp_allowed,tcp_allowed_netsandtcp_banned, grant the serviceCAP_NET_ADMIN, and setGOTUNNEL_TCP_NFT_ENABLED=true.
Run gotunnel help for every TCP limit (session length, bandwidth, connections per tunnel, rate limits, approved IPs per tunnel).
Running as a service¶
Run the server under systemd so it restarts on boot/failure:
[Unit]
Description=gotunnel
After=network.target
[Service]
User=gotunnel
EnvironmentFile=/etc/gotunnel/env
ExecStart=/usr/local/bin/gotunnel server --addr :9000
NoNewPrivileges=true
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Put GOTUNNEL_AUTH_TOKENS=your-strong-token in /etc/gotunnel/env, owned by root with mode 600, and create a gotunnel system user so the server doesn't run as root.
Updating¶
gotunnel update
Hosted vs self-hosted at a glance¶
| Hosted server | Self-hosted | |
|---|---|---|
| Setup | one command, email verification once | run your own server + token |
| Public URL | https://<id>.t.gotunnel.site |
your host/port (or your own subdomain) |
| TLS | handled for you | your certs or a reverse proxy |
| Data path | through the hosted server | entirely your infrastructure |
| Cost | free, with fair-use limits, no guarantees | free, your VPS, your limits |
Both modes speak the same binary protocol with the same stream multiplexing and TLS support — the only difference is who owns the server.