T3 is an SSH tunneling server that exposes local applications to the internet through memorable subdomains (for example, happy-tiger-a1b2c3d4.t3.dev). Clients connect over SSH with remote port forwarding; the server assigns a subdomain and reverse-proxies HTTPS traffic to the local app.
- Go 1.26 or later
- An SSH client (
sshon macOS/Linux, or OpenSSH for Windows)
From the repository root:
go run ./cmd/t3Or build and run a binary:
go build -o bin/t3 ./cmd/t3
./bin/t3On startup the server listens on:
| Service | Default address |
|---|---|
| SSH | :2222 |
| HTTP | :8080 (redirects to HTTPS) |
| HTTPS | :8443 |
A host key is created automatically at host_key if it does not exist.
Press Ctrl+C to shut down gracefully.
-
Start the T3 server (in one terminal):
go run ./cmd/t3
If your local app also uses port 8080, move the T3 HTTP redirect to another port:
HTTP_ADDR=:8081 go run ./cmd/t3
-
Start a local web server (in another terminal):
python3 -m http.server 8080
-
Open an SSH tunnel to the T3 server:
ssh -t -R 80:localhost:8080 -p 2222 localhost
- Connect to
localhostwhen running T3 locally —t3.devis only the domain used in tunnel URLs, not the SSH host unless you have deployed T3 there -R 80:localhost:8080— forward remote port 80 to your local port 8080-p 2222— SSH port (matches defaultSSH_ADDR)- No username or password is required
- Connect to
-
The server prints a public URL in the SSH session, for example:
https://happy-tiger-a1b2c3d4.t3.dev -
For local development, point tunnel subdomains at your machine. Add lines like this to
/etc/hosts:127.0.0.1 t3.dev 127.0.0.1 happy-tiger-a1b2c3d4.t3.devUse the exact subdomain printed by the SSH session. Then open the HTTPS URL in your browser (
https://<subdomain>.t3.dev:8443if using non-standard ports).
All settings are read from environment variables. Defaults are suitable for local development.
| Variable | Default | Description |
|---|---|---|
DOMAIN |
t3.dev |
Base domain for tunnel URLs |
SSH_ADDR |
:2222 |
SSH listener address |
HTTP_ADDR |
:8080 |
HTTP redirect listener |
HTTPS_ADDR |
:8443 |
HTTPS reverse proxy listener |
STATS_ADDR |
127.0.0.1:9090 |
Stats endpoint (reserved) |
HOST_KEY_PATH |
host_key |
Path to the SSH host private key |
TLS_CERT |
cert.pem |
TLS certificate path |
TLS_KEY |
key.pem |
TLS private key path |
Example with custom ports and domain:
DOMAIN=t3.dev SSH_ADDR=:2222 HTTP_ADDR=:8080 HTTPS_ADDR=:8443 go run ./cmd/t3For production, set DOMAIN to your real domain and provide valid TLS certificates via TLS_CERT and TLS_KEY.
T3 uses an SSH host key to identify the server when clients connect. The key is stored as a private key file (default: host_key in the project root).
If no key exists at HOST_KEY_PATH, T3 creates an Ed25519 key automatically on first startup:
Host key file not found at host_key, generating new key...
This is fine for local development. For production or Docker, generate and persist a key yourself so it stays stable across restarts.
Use OpenSSH ssh-keygen (Ed25519, no passphrase):
ssh-keygen -t ed25519 -f host_key -N ""This creates:
| File | Purpose |
|---|---|
host_key |
Private key — give this to T3 (HOST_KEY_PATH) |
host_key.pub |
Public key — optional, for client known_hosts verification |
Set restrictive permissions:
chmod 600 host_keyPoint T3 at your key file:
HOST_KEY_PATH=/path/to/host_key go run ./cmd/t3Or with the binary:
./bin/t3
# with env:
export HOST_KEY_PATH=/etc/t3/host_keyMount a volume so the host key persists across container restarts:
# Generate on the host first
ssh-keygen -t ed25519 -f host_key -N ""
chmod 600 host_key
docker run --rm --name t3 \
-p 2222:2222 \
-p 8081:8080 \
-p 8443:8443 \
-e DOMAIN=localhost \
-v t3-data:/data \
-v "$(pwd)/host_key:/data/host_key:ro" \
t3Or let the container generate and store the key in a named volume (first run only):
docker volume create t3-data
docker run ... -v t3-data:/data t3
# Key is written to /data/host_key inside the volumeTo copy a generated key out of a volume:
docker run --rm -v t3-data:/data alpine cat /data/host_key > host_key
chmod 600 host_key- Never commit
host_keyto git (it is listed in.gitignore) - Use
chmod 600on the private key file - Back up the key in production — changing it causes SSH clients to warn about a changed host key
- The public key (
host_key.pub) is safe to share; clients can add it to~/.ssh/known_hoststo skip host verification prompts
Run tests:
go test ./...Run tests with race detection (same as CI):
go test -race ./...Build all packages:
go build ./...cmd/t3/ Entry point
internal/config/ Environment-based configuration
internal/server/ SSH, HTTP/HTTPS, tunnel registry, abuse protection
internal/subdomain/ Subdomain generation and validation
internal/tunnel/ Tunnel lifecycle and rate limiting
See ARCHITECTURE.md for a deeper overview of how the components fit together.