Skip to content

Repository files navigation

T3

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.

Prerequisites

Quick start

From the repository root:

go run ./cmd/t3

Or build and run a binary:

go build -o bin/t3 ./cmd/t3
./bin/t3

On 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.

Expose a local app

  1. 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
  2. Start a local web server (in another terminal):

    python3 -m http.server 8080
  3. Open an SSH tunnel to the T3 server:

    ssh -t -R 80:localhost:8080 -p 2222 localhost
    • Connect to localhost when running T3 locally — t3.dev is 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 default SSH_ADDR)
    • No username or password is required
  4. The server prints a public URL in the SSH session, for example:

    https://happy-tiger-a1b2c3d4.t3.dev
    
  5. 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.dev
    

    Use the exact subdomain printed by the SSH session. Then open the HTTPS URL in your browser (https://<subdomain>.t3.dev:8443 if using non-standard ports).

Configuration

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/t3

For production, set DOMAIN to your real domain and provide valid TLS certificates via TLS_CERT and TLS_KEY.

SSH host 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).

Auto-generation

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.

Generate a key manually

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_key

Use a custom key path

Point T3 at your key file:

HOST_KEY_PATH=/path/to/host_key go run ./cmd/t3

Or with the binary:

./bin/t3
# with env:
export HOST_KEY_PATH=/etc/t3/host_key

Docker

Mount 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" \
  t3

Or 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 volume

To 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

Security notes

  • Never commit host_key to git (it is listed in .gitignore)
  • Use chmod 600 on 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_hosts to skip host verification prompts

Development

Run tests:

go test ./...

Run tests with race detection (same as CI):

go test -race ./...

Build all packages:

go build ./...

Project layout

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.

About

A simple SSH tunneling service written in Go

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages