Self-hosted reverse tunnels — post-quantum encrypted, any protocol, no accounts required.
HTTP · HTTPS · Raw TCP · UDP · WebSocket · Hybrid X25519 + ML-KEM-768
Seamless exposes services behind NAT to the internet through a relay you control — no third-party accounts, no per-seat pricing, no traffic inspection. Every byte between client and relay is end-to-end encrypted by Seam, a post-quantum UDP transport with ML-KEM-768 in the handshake.
internet your relay (VPS) your machine (NAT)
┌──────────┐ TLS ┌──────────────────────┐ UDP ┌─────────────────┐
│ browser │─────────►│ seamless-relay │◄───────│ seamless │
│ webhook │ HTTPS │ │ Seam │ (client) │
│ ssh │ │ routes by subdomain │ (PQ) │ → local service │
└──────────┘ └──────────────────────┘ └─────────────────┘
The client dials out. No firewall rules. No port forwarding. No relay restart when the client reconnects.
| Seamless | ngrok free | Cloudflare Tunnel | |
|---|---|---|---|
| Self-hosted | ✅ your VPS | ❌ their servers | ❌ their edge |
| Post-quantum safe | ✅ ML-KEM-768 | ❌ | ❌ |
| Any protocol | ✅ HTTP, TCP, UDP | HTTP only | HTTP/TCP only |
| HTTPS out of the box | ✅ BYO or ACME/LE | ✅ (their cert) | ✅ (their cert) |
| Wildcard TLS (ACME DNS-01) | ✅ | ❌ | ❌ |
| Unlimited tunnels | ✅ | ❌ 1 on free | ✅ |
| Custom subdomains | ✅ | ❌ paid | ✅ |
| Raw TCP tunnels | ✅ | ❌ paid | ❌ |
| UDP tunnels | ✅ | ❌ | ❌ |
| Geo-IP blocking | ✅ MaxMind GeoLite2 | ❌ paid | ❌ |
| Signed webhooks | ✅ HMAC-SHA256 | ✅ | ❌ |
| Admin UI | ✅ built-in | ✅ | ✅ |
| Fail2ban | ✅ built-in | ❌ | ❌ |
| JSONL audit log | ✅ | ❌ | ❌ |
| Traffic visible to operator | only you | ngrok | Cloudflare |
| Works on lossy networks | ✅ UDP + FEC | limited | limited |
| Cost | VPS (~$5/mo) | free tier / paid | free |
git clone https://github.com/North9-Labs/Seam
git clone https://github.com/North9LLC/Seamless
cd Seamless
cargo build --release
# Binaries: target/release/seamless target/release/seamless-relayRequires Rust 1.88+. Seam must be cloned alongside Seamless (path dependency).
# HTTP only (simplest)
./seamless-relay \
--seam-addr 0.0.0.0:4443 \
--http-addr 0.0.0.0:80 \
--base-domain tunnel.example.com
# HTTP + HTTPS with your own cert
./seamless-relay \
--seam-addr 0.0.0.0:4443 \
--http-addr 0.0.0.0:80 \
--https-addr 0.0.0.0:443 \
--tls-cert /etc/ssl/certs/tunnel.example.com.pem \
--tls-key /etc/ssl/private/tunnel.example.com.key \
--base-domain tunnel.example.com
# HTTPS with automatic Let's Encrypt certificate (DNS-01 via Cloudflare)
./seamless-relay \
--seam-addr 0.0.0.0:4443 \
--https-addr 0.0.0.0:443 \
--base-domain tunnel.example.com \
--acme-email admin@example.com \
--acme-domains tunnel.example.com \
--cloudflare-api-token $CF_TOKEN \
--acme-production
# HTTPS with self-signed cert (local testing)
./seamless-relay --https-addr 0.0.0.0:8443 --tls-self-signedOn first boot the relay prints its public keys:
INFO seamless_relay: seam-pubkey-x25519 <64 hex chars>
INFO seamless_relay: seam-pubkey-kem <2336 hex chars>
INFO seamless_relay: connect: seamless http <port> --relay ... --x25519 ... --kem ...
./seamless config init \
--relay your-vps.example.com:4443 \
--x25519 <64 hex chars> \
--kem <2336 hex chars>From here on, no flags needed. The keys are saved to ~/.config/seamless/config.toml.
# HTTP dev server
./seamless http 3000
# → https://abc12345.tunnel.example.com
# Fixed subdomain
./seamless http 3000 --subdomain myapp
# → https://myapp.tunnel.example.com
# Raw TCP (e.g. SSH)
./seamless tcp 22 --remote-port 2222
# → ssh -p 2222 user@your-vps.example.com
# Raw UDP (e.g. game server, VoIP)
./seamless udp 25565 --remote-port 25565The tunnel reconnects automatically with exponential backoff (1 s → 30 s) if the connection drops.
Routes by Host header. The relay terminates TLS and forwards HTTP to the client, which forwards to your local service.
./seamless http 3000 # any random subdomain
./seamless http 3000 --subdomain myapp # fixed subdomain
./seamless http 3000 --subdomain myapp.custom.com # custom domain (relay must have --allow-custom-domains)Forwards raw TCP byte-for-byte. Works for SSH, PostgreSQL, Redis, SMTP, or any TCP-based protocol.
./seamless tcp 5432 --remote-port 5432 # PostgreSQL
./seamless tcp 22 --remote-port 2222 # SSHConnect with: ssh -p 2222 user@relay.example.com or psql -h relay.example.com -p 5432.
Forwards raw UDP datagrams. Works for game servers, VoIP, DNS, and other UDP protocols.
./seamless udp 25565 --remote-port 25565 # Minecraft server
./seamless udp 5060 --remote-port 5060 # SIPConfigured once on the relay, no client needed. The relay listens on a TCP port and forwards to a fixed backend:
./seamless-relay \
--tcp-passthrough 5432:db.internal:5432 \
--tcp-passthrough 6379:redis.internal:6379seamless config init --relay host:port --x25519 hex --kem hex [--token tok]
seamless config show # print saved values
seamless config clear # remove config
# Config file: ~/.config/seamless/config.toml
# CLI flags always override config file valuesConfig file example (~/.config/seamless/config.toml):
relay = "relay.example.com:4443"
x25519 = "a9d493..."
kem = "..."
token = "mytoken"Without an auth file, anyone who can reach the relay's UDP port can register tunnels. Lock it down:
# On the relay — one token per line, # for comments
echo "supersecrettoken" > /etc/seamless/tokens
./seamless-relay --auth-file /etc/seamless/tokens ...
# On the client — save once
./seamless config init --token supersecrettoken
# Or per-command
./seamless --token supersecrettoken http 3000The relay serves a web admin panel at http://your-relay:8088 (configurable with --admin-addr).
Tabs:
- Dashboard — active tunnel count, bytes forwarded, blocked requests
- Tunnels — live list of all active HTTP subdomains and TCP/UDP ports with client IP, uptime, and byte counters; pause or force-disconnect any tunnel
- Audit log — tamper-evident JSONL event chain (tunnel events, auth failures, admin actions, bans)
- Access log — every HTTP request: method, host, path, status code, latency
- Banned IPs — active fail2ban blocks with expiry time and reason
- Proxy routes — static upstreams with health checks (route
api.example.com→10.0.0.5:8080without a client) - Identity — relay's public keys with copy-ready connect command
Admin API (REST, same port):
GET /api/tunnels # list active tunnels
POST /api/tunnels/:id/pause
POST /api/tunnels/:id/disconnect
GET /api/audit # JSONL audit stream
GET /api/events # Server-Sent Events for live updates# JWT login (bcrypt password)
./seamless-relay hash-password mypassword # → $2b$...
./seamless-relay --admin-password '$2b$...'
# Bearer token for API access
./seamless-relay --admin-token mysecrettoken
# Restrict to specific CIDR ranges
./seamless-relay --admin-allow-cidr 10.0.0.0/8,192.168.1.0/24
# mTLS for government / enterprise deployments
./seamless-relay \
--admin-tls-cert /etc/seamless/admin.crt \
--admin-tls-key /etc/seamless/admin.key \
--admin-client-ca /etc/seamless/ca.crtFire signed JSON events to external endpoints on tunnel lifecycle events:
./seamless-relay \
--webhook-url https://hooks.example.com/seamless \
--webhook-secret mysigningsecretEvents: tunnel.connected, tunnel.disconnected, request.blocked, ban.created, ban.expired.
Payloads are signed with X-Seamless-Signature: sha256=<hmac-hex>. Verify:
import hmac, hashlib
sig = request.headers["X-Seamless-Signature"].removeprefix("sha256=")
expected = hmac.new(secret, request.body, hashlib.sha256).hexdigest()
assert hmac.compare_digest(sig, expected)Delivery is fire-and-forget with one retry on failure (500 ms delay).
Block tunnel registrations and ingress traffic from specific countries using a MaxMind GeoLite2-Country database:
./seamless-relay \
--geoip-db /var/lib/seamless/GeoLite2-Country.mmdb \
--block-countries CN,RU,KP,IRThe database is loaded once at startup and held in memory. Blocked IPs receive an immediate 403. Stats are tracked in the admin UI. Download GeoLite2-Country.mmdb free at maxmind.com.
The relay tracks failed admin login attempts and automatically blocks IPs that fail 5 times within 10 minutes for 15 minutes. Bans are visible in the admin UI and expire automatically. A ban.created webhook event fires on each new ban.
Avoid long flag lists by using a config file:
./seamless-relay --config /etc/seamless/relay.toml# /etc/seamless/relay.toml
seam_addr = "0.0.0.0:4443"
http_addr = "0.0.0.0:80"
https_addr = "0.0.0.0:443"
base_domain = "tunnel.example.com"
auth_file = "/etc/seamless/tokens"
audit_log = "/var/log/seamless/audit.jsonl"
acme_email = "admin@example.com"
acme_domains = "tunnel.example.com"
acme_production = true
[geoip]
db_path = "/var/lib/seamless/GeoLite2-Country.mmdb"
block_countries = ["CN", "RU", "KP", "IR"]
[limits]
max_tunnels_per_ip = 5
max_tunnels = 500
ingress_rate_limit = 50
tunnel_max_age = 86400 # 24hCLI flags always override config file values.
See systemd/README.md and systemd/install.sh.
# Build from parent directory (Seam + Seamless side by side)
docker build -f Seamless/Dockerfile -t seamless-relay .
docker run -p 4443:4443/udp -p 80:80/tcp -p 443:443/tcp seamless-relay \
--seam-addr 0.0.0.0:4443 \
--http-addr 0.0.0.0:80 \
--https-addr 0.0.0.0:443 \
--tls-self-signed \
--base-domain tunnel.example.comDocker Compose:
docker compose up relayThe relay saves its keypair to seamless-relay.json on first boot with 0600 permissions and reloads it on restart. Back this file up — losing it means existing clients must reconfigure with new keys.
| Flag | Default | Env var | Description |
|---|---|---|---|
--config |
auto | SEAMLESS_CONFIG |
TOML config file path |
--seam-addr |
0.0.0.0:4443 |
UDP address for client connections | |
--http-addr |
0.0.0.0:8080 |
TCP address for HTTP ingress | |
--https-addr |
(off) | TCP address for HTTPS ingress | |
--tls-cert |
— | TLS certificate PEM path | |
--tls-key |
— | TLS private key PEM path | |
--tls-self-signed |
false |
Generate self-signed cert at startup | |
--admin-addr |
0.0.0.0:8088 |
Admin UI and REST API | |
--admin-token |
— | SEAMLESS_ADMIN_TOKEN |
Bearer token for admin API |
--admin-password |
— | SEAMLESS_ADMIN_PASSWORD |
bcrypt hash for JWT login |
--admin-allow-cidr |
(all) | SEAMLESS_ADMIN_ALLOW_CIDR |
Restrict admin to CIDRs |
--admin-tls-cert |
— | SEAMLESS_ADMIN_TLS_CERT |
TLS cert for admin port |
--admin-tls-key |
— | SEAMLESS_ADMIN_TLS_KEY |
TLS key for admin port |
--admin-client-ca |
— | SEAMLESS_ADMIN_CLIENT_CA |
CA for admin mTLS |
--base-domain |
localhost |
Base domain for tunnel URLs | |
--auth-file |
(open) | Token allowlist (one per line) | |
--store |
seamless-relay.json |
Identity and proxy routes file | |
--max-tunnels-per-ip |
10 |
SEAMLESS_MAX_TUNNELS_PER_IP |
Per-IP tunnel cap |
--max-tunnels |
1000 |
SEAMLESS_MAX_TUNNELS |
Global tunnel cap |
--rate-limit |
10 |
SEAMLESS_RATE_LIMIT |
New tunnel registrations per minute per IP |
--ingress-rate-limit |
100 |
SEAMLESS_INGRESS_RATE_LIMIT |
HTTP requests/sec per source IP |
--tunnel-max-age |
0 |
SEAMLESS_TUNNEL_MAX_AGE |
Max tunnel lifetime (seconds, 0=unlimited) |
--max-body-size |
10485760 |
SEAMLESS_MAX_BODY_SIZE |
Max HTTP request body (bytes) |
--default-bandwidth-limit |
0 |
SEAMLESS_DEFAULT_BW_LIMIT |
Per-tunnel bandwidth cap (bytes/sec) |
--webhook-url |
— | SEAMLESS_WEBHOOK_URL |
Webhook endpoint (repeatable) |
--webhook-secret |
— | SEAMLESS_WEBHOOK_SECRET |
HMAC-SHA256 webhook signing key |
--geoip-db |
— | SEAMLESS_GEOIP_DB |
MaxMind GeoLite2-Country.mmdb path |
--block-countries |
— | SEAMLESS_BLOCK_COUNTRIES |
Comma-separated ISO country codes to block |
--acme-email |
— | SEAMLESS_ACME_EMAIL |
Let's Encrypt account email |
--acme-domains |
— | SEAMLESS_ACME_DOMAINS |
Comma-separated domains for ACME cert |
--acme-production |
false |
Use LE production CA (staging by default) | |
--acme-wildcard |
false |
SEAMLESS_ACME_WILDCARD |
Wildcard cert via DNS-01 |
--cloudflare-api-token |
— | CLOUDFLARE_API_TOKEN |
Cloudflare API token for DNS-01 |
--allow-custom-domains |
false |
SEAMLESS_ALLOW_CUSTOM_DOMAINS |
Allow clients to register custom domains |
--reserved-subdomains |
— | SEAMLESS_RESERVED_SUBDOMAINS |
Comma-separated blocked subdomain names |
--subdomain-prefix |
— | SEAMLESS_SUBDOMAIN_PREFIX |
Required prefix for client subdomains |
--subdomain-length |
8 |
SEAMLESS_SUBDOMAIN_LENGTH |
Length of random subdomains |
--blocklist-file |
— | SEAMLESS_BLOCKLIST_FILE |
Subdomain blocklist file (reloaded on SIGHUP) |
--ip-denylist-file |
— | SEAMLESS_IP_DENYLIST_FILE |
CIDR denylist file (reloaded on SIGHUP) |
--audit-log |
— | SEAMLESS_AUDIT_LOG |
Append-only JSONL audit log path |
--tunnel-keepalive |
30 |
SEAMLESS_TUNNEL_KEEPALIVE |
Keepalive interval in seconds |
--cipher |
chacha20poly1305 |
SEAMLESS_CIPHER |
chacha20poly1305 or aes256gcm |
--log-level |
info |
error, warn, info, debug, trace |
|
--log-format |
text |
SEAMLESS_LOG_FORMAT |
text or json |
--tcp-passthrough |
— | SEAMLESS_TCP_PASSTHROUGH |
PORT:HOST:PORT (repeatable) |
Every packet between client and relay is encrypted with ChaCha20-Poly1305 (256-bit key). The handshake uses Noise_XX + ML-KEM-768 — a hybrid construction secure against both classical and quantum adversaries.
The relay terminates TLS on the public-facing HTTP/HTTPS ingress using rustls (pure-Rust, no OpenSSL). Bring a real cert, use automatic Let's Encrypt provisioning, or use --tls-self-signed.
The relay operator sees all plaintext HTTP traffic that passes through. If you don't control the relay, don't forward sensitive data over HTTP — use HTTPS so the relay only sees TLS ciphertext.
Seamless runs cargo audit in CI on every push. As of v0.1.35, zero known vulnerabilities in the dependency tree.
- JWT authentication with bcrypt password hashing
- Algorithm-pinned token validation (HS256 only, audience claim prevents cross-relay replay)
- Constant-time auth token comparison (no timing attacks)
- Fail2ban auto-blocks repeated login failures (5 failures/10 min → 15 min ban)
- Optional mTLS on the admin port for government deployments
- Audit log with tamper-evident per-event HMAC chain
See docs/ for protocol specification, CLI reference, and self-hosting guide.
| Layer | Seam handles | Seamless adds |
|---|---|---|
| Crypto | Noise_XX + ML-KEM-768, ChaCha20-Poly1305, replay window | — |
| Transport | UDP, CUBIC/BBR CC, RACK-TLP loss detection, GF(2⁸) FEC, pacing | — |
| Multiplexing | SeamMux + SeamStream (AsyncRead + AsyncWrite) |
Designates stream 0 as control |
| TLS (public) | — | rustls HTTPS ingress (BYO cert, ACME, or self-signed) |
| Application | — | Register/Registered/NewConn protocol, subdomain registry, HTTP Host routing, TCP/UDP listeners, client config file |
Seamless is intentionally thin. All the hard work lives in Seam.
git clone https://github.com/North9-Labs/Seam
git clone https://github.com/North9LLC/Seamless
cd Seamless
cargo build --release
./target/release/seamless-relay --help
./target/release/seamless --helpTests:
cargo test --workspaceSeamless is dual-licensed:
- Open source: GNU Affero General Public License v3.0 — free for open source projects and personal use
- Commercial: contact licensing@north9.org for proprietary, SaaS tunnel hosting, government, or OEM use
See LICENSE-COMMERCIAL for details.