Skip to content

North9-Labs/Seamless

Repository files navigation

Seamless

Self-hosted reverse tunnels — post-quantum encrypted, any protocol, no accounts required.

HTTP · HTTPS · Raw TCP · UDP · WebSocket · Hybrid X25519 + ML-KEM-768

CI Security Audit License: AGPL v3 Rust 1.88+ Seam v0.1.39


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.


Why Seamless instead of ngrok / Cloudflare Tunnel

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

Install

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-relay

Requires Rust 1.88+. Seam must be cloned alongside Seamless (path dependency).


Quickstart — 2 minutes to a live tunnel

1. Start the relay on your VPS

# 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-signed

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

2. Save the relay keys (one time)

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

3. Expose a service

# 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 25565

The tunnel reconnects automatically with exponential backoff (1 s → 30 s) if the connection drops.


Tunnel types

HTTP / HTTPS tunnels

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)

TCP tunnels

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     # SSH

Connect with: ssh -p 2222 user@relay.example.com or psql -h relay.example.com -p 5432.

UDP tunnels

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    # SIP

TCP passthrough (relay-side)

Configured 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:6379

Config reference

seamless 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 values

Config file example (~/.config/seamless/config.toml):

relay  = "relay.example.com:4443"
x25519 = "a9d493..."
kem    = "..."
token  = "mytoken"

Auth tokens

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 3000

Admin UI

The 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.com10.0.0.5:8080 without 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

Securing the admin UI

# 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.crt

Webhooks

Fire signed JSON events to external endpoints on tunnel lifecycle events:

./seamless-relay \
  --webhook-url https://hooks.example.com/seamless \
  --webhook-secret mysigningsecret

Events: 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).


Geo-IP country blocking

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,IR

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


Fail2ban

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.


Deployment

TOML config file

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   # 24h

CLI flags always override config file values.

systemd (production)

See systemd/README.md and systemd/install.sh.

Docker

# 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.com

Docker Compose:

docker compose up relay

Identity persistence

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


Full relay flag reference

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)

Security model

What Seam protects

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.

What Seamless adds on the public side

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.

Trust boundary

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.

Supply chain security

Seamless runs cargo audit in CI on every push. As of v0.1.35, zero known vulnerabilities in the dependency tree.

Admin security

  • 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

Architecture

See docs/ for protocol specification, CLI reference, and self-hosting guide.

Layer model

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.


Building from source

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 --help

Tests:

cargo test --workspace

License

Seamless is dual-licensed:

See LICENSE-COMMERCIAL for details.

About

Post-quantum reverse tunnels — expose any service through a relay you control, secured by Seam (ML-KEM-768 + X25519)

Topics

Resources

License

AGPL-3.0, Unknown licenses found

Licenses found

AGPL-3.0
LICENSE
Unknown
LICENSE-COMMERCIAL

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages