v0.1 · Multi-tenant dashboard + Stripe billing shipped

Self-host ingress in one binary.

21tunnel is an open-source tunnel service. A Rust agent, a Rust server, a Postgres-backed multi-tenant dashboard with JWT auth, MFA, RBAC, and Stripe billing — dual MIT / Apache-2.0. Run it on your own VM, keep your users' traffic in your own control plane.

$ git clone https://github.com/vikasswaminh/21tunnel && cd 21tunnel && cargo build --release copy
Rust · TLS 1.3 + yamux argon2id + JWT + TOTP MIT / Apache-2.0
~ $ tunnel21 http 3000
$ tunnel21 http 3000 --domain bob.21tunnel.com
[0.012s] dial edge.21tunnel.com:7443 ok
[0.041s] tls 1.3 · ALPN=yamux · cert=21tunnel R1 ok
[0.058s] auth capability-token · scope=bob.* · ttl=24h ok
[0.071s] yamux session up · max_streams=100

Tunnel live
   https://bob.21tunnel.com → 127.0.0.1:3000
  region: local · retention: 24h · inspector: http://127.0.0.1:9090/inspector

Ctrl-C to close the tunnel.
$
MIGRATIONS
5
applied
CLIPPY DENY
unwrap · panic
+ 20 more
UNSAFE CODE
0 lines
#![forbid(unsafe_code)]
Rust Postgres 15 axum tokio yamux sqlx argon2id jsonwebtoken Next.js Astro Zustand Stripe Rust Postgres 15 axum tokio yamux sqlx argon2id jsonwebtoken Next.js Astro Zustand Stripe
What ships today

The multi-tenant plumbing between your laptop and the internet.

Every feature below is live in the current build. No roadmap asterisks — if it's on this page, you can pull it from master and run it tonight.

Self-hosted, one binary

Rust server + Postgres + nginx. Run it on a single VM. No control plane you don't own, no vendor lock-in, no egress fees.

$ cargo build --release

TLS 1.3 + yamux transport

Agents connect over plain TCP with TLS 1.3 and yamux multiplexing. Replaced QUIC for smaller binaries and simpler NAT traversal.

$ tokio-rustls · yamux 0.13

Capability-token agent auth

HMAC-signed tokens carry subject, allowed subdomains, bandwidth, quota, and TTL. Scoped per machine, revoke-one, replay-resistant.

$ bincode + HMAC-SHA256

Multi-tenant dashboard

Hybrid self-serve signup → superadmin approval → org with members. Owner / admin / member / viewer RBAC enforced at middleware + handler.

$ Postgres · axum · JWT

argon2id + rotating refresh

Passwords hashed with argon2id (OWASP defaults). 15-min JWT access + opaque refresh cookie, rotated on every use. Reuse trips whole-session revoke.

$ jsonwebtoken 9 · argon2 0.5

Optional TOTP MFA

Enroll from the profile page, get ten one-shot recovery codes once. Login flow short-circuits to an mfa_challenge JWT on second factor.

$ RFC 6238 · sealed at rest

Stripe Checkout + metered

Customer Portal for the tenant owner, Checkout for plan upgrades, HMAC-verified webhooks with idempotent billing_events replay.

$ Checkout · Portal · webhooks

Request inspector + audit log

Captured requests retained for replay. Audit log partitioned by month with a retention worker that prunes on a 90-day window.

$ audit_logs_YYYY_MM

dual_auth middleware

Every tenant-scoped route accepts either a user JWT or the static admin bearer — ops scripts keep working, browser sessions get per-org filtering.

$ 1 middleware · 7 routes
Inside the binary

One control plane, one DB, one container — no sidecar tax.

The whole system fits in five processes: qnt-server, nginx, Postgres, the dashboard (static Next.js), and your agent. Everything else is someone else's problem.

LIVE TRAFFIC

Inspect every request. Replay any of them.

The inspector captures method, path, headers, body, timing, and origin IP. Admins can replay from the dashboard; auditors can filter to their org's rows only (row-level filter via dual_auth).

inspector GET /inspector /api/inspector
POST 201 /v1/tunnels 91ms 412 B
GET 200 /orgs/current/members 17ms 2.1 kB
POST 200 /auth/refresh 8ms 614 B
POST 401 /admin/organizations 4ms 96 B
POST 200 /webhooks/stripe 23ms 38 B
SCHEMA

5 migrations.

Postgres 15, monthly-partitioned audit_logs, idempotent webhook events, rotating refresh tokens.

-- migrations/
0001_initial_schema.sql
0002_bootstrap_default_org.sql
0003_api_keys_token_metadata.sql
0004_auth_and_billing.sql
0005_invitation_token.sql
COMPILE-TIME SAFETY

Strict Rust.

Every unwrap, panic, todo!, and unreachable is a compile-time deny. #![forbid(unsafe_code)].

unwrap_used   = "deny"
panic         = "deny"
todo          = "deny"
unimplemented = "deny"
dbg_macro     = "deny"
AUTH

Zero-trust by default.

argon2id, JWT+refresh rotation, optional TOTP, 10 recovery codes, session revocation on password reset.

argon2idJWTRefresh rotationTOTPRecovery codesSession revoke
ROLES

Four levels + superadmin.

JWT carries the role; middleware enforces. Superadmin bit bypasses org checks + unlocks the Admin Console.

owner    = 4 (billing, transfer, delete)
admin    = 3 (invite, manage members)
member   = 2 (create tunnels, tokens)
viewer   = 1 (read-only)
superadmin = 255 (bypass)
Use cases

The same binary, every environment.

Works on your laptop, works on a VM, works on your customer's hardware. No per-deployment SaaS fee — you host it, you run it.

01 / DEMOS

Share localhost

Paste a URL into Slack — stakeholders see the feature, not a screenshare.

$ tunnel21 http 3000
02 / WEBHOOKS

Test webhooks locally

Stripe, GitHub, Shopify delivered to your dev box; inspector records each one.

capture → replay
03 / IOT

Reach devices behind NAT

SSH, HTTP, or TCP into printers, pis, and PLCs on residential lines.

tcp · ssh · http
04 / LLM TOOLS

Expose MCP servers

Publish agent tools and retrieval endpoints with capability-token auth.

MCP · scoped tokens
05 / SELF-HOST

Bring your own control plane

Your Postgres, your VM, your audit log. No vendor reads your traffic.

BYOC · SOC 2 scoped
06 / SaaS

Embed in your product

Give your customers tunnels under your brand. Dashboard is multi-tenant out of the box.

white-label
07 / CI / CD

Preview URLs per PR

Spin an ephemeral tunnel in CI; tear it down on merge. One CLI, zero YAML.

ephemeral
08 / TEAM

Invite + role-gate

Hybrid signup + superadmin approval + scoped invitations — schema already there.

owner/admin/member/viewer
Deploy it

Run it how you actually run things.

The runbook walks you through these three. Everything reads from TOML, all secrets from on-disk key files with chmod 600.

/etc/qnt/server.toml
[server]
bind     = "0.0.0.0:7443"   # agent transport (TLS 1.3 + yamux)
api_bind = "127.0.0.1:9090" # dashboard API
proxy_bind = "0.0.0.0:8080" # public HTTP frontend
domain   = "21tunnel.com"

[database]
enabled = true
url     = "postgres://qnt@127.0.0.1/qnt"
auto_migrate = true

[web_auth]
jwt_secret_path    = "/etc/qnt/jwt.key"
dashboard_base_url = "https://app.21tunnel.com"
secure_cookies     = true

[resend]
api_key_path = "/etc/qnt/resend.key"
from_address = "noreply@21tunnel.com"

[stripe]
secret_key_path     = "/etc/qnt/stripe.key"
webhook_secret_path = "/etc/qnt/stripe-webhook.key"
price_pro  = "price_..."
price_team = "price_..."
Single static binary
Linux x86_64 + ARM64. Build in 4 minutes on a modest VM. No runtime dependencies except libc + OpenSSL (via rustls).
Secrets on disk, chmod 600
JWT secret, Resend key, Stripe key all live as separate files read at startup. No env vars in process listings.
Observable by default
tracing + structured logs, Prometheus handle, OpenTelemetry OTLP export. Audit-log partitions on a 90-day retention worker.
Declarative + versioned
5 SQL migrations, one TOML config, one systemd unit. Your ops story is git pull + systemctl restart.
5
SQL migrations in the tree
0 lines
of unsafe Rust
4 weeks
design → shipped MVP
MIT
+ Apache-2.0 dual license
Design principles
The JWT carries role; the middleware enforces it. A demotion takes effect on the next write without waiting for token expiry.
Design doc · §5 RBAC matrix
docs/plans/2026-04-24-multitenant-dashboard-design.md

Dual-auth middleware: admin bearer or user JWT; both produce the same AuthContext. Handlers don't branch on auth path.

commit 5befc84 — Week 4a

Refresh tokens rotate on every use; presenting an already-rotated token revokes the whole session. Free theft detection, no heartbeat needed.

session.rs — Week 1

MFA challenge is a stateless JWT, not a DB row. Login doesn't need sticky sessions on the ops side.

web_auth.rs — Week 4b
Pricing

Free for tinkering. BYO VM for enterprise.

Plan quotas below are the actual defaults seeded in migration 0001. Self-host the whole thing and the only price is your VM bill.

Free

$0forever
  • 1 tunnel
  • 10 GB bandwidth / month
  • 1 user
  • Random subdomain
  • 24h traffic inspection
Start free

Pro

$10/ month
  • 10 tunnels
  • 100 GB / month included
  • Reserved subdomain
  • 7-day traffic retention
  • Request inspector + replay
Upgrade

Enterprise

BYOself-host
  • Run the whole thing on your VM
  • Postgres you already operate
  • SOC 2 / HIPAA — your scope
  • MIT + Apache-2.0 source
  • Priority commits + responsive maintainer
Talk
FAQ

Questions that come up in every demo.

How is this different from ngrok or Cloudflare Tunnel? +

21tunnel is open-source and self-hostable end-to-end — control plane, edge, dashboard, billing. You run it on one VM you own. ngrok-style SaaS convenience without routing your users' traffic through a third-party SaaS.

Is it production-ready? +

It's a working MVP, not a battle-tested service yet. The backend is ~5,000 lines of Rust with strict clippy lints (unwrap/panic/todo all deny, unsafe_code forbidden). The dashboard covers signup, MFA, billing, RBAC, and superadmin. There's no multi-region, no 99.99% uptime claim, no customer at scale — use it knowing it's MVP-stage.

What does self-hosting actually require? +

A Linux VM with Postgres 15+, nginx, a domain, and a Stripe account if you want billing. The runbook walks through Certbot, Resend (email), and Stripe setup end-to-end. Once bootstrapped, one `qnt-server` binary runs everything.

Is the code really open-source? +

Yes — dual MIT / Apache-2.0, the canonical Rust-ecosystem license. The repo is github.com/vikasswaminh/21tunnel. Agent, server, dashboard, migrations, and design docs are all in the tree.

Why Rust + yamux + TLS 1.3 instead of QUIC? +

The original stack was quinn-based QUIC. We swapped to tokio-rustls + yamux because TCP+TLS 1.3 has better NAT traversal, smaller binaries, and works through every network middlebox. yamux handles the multiplexing QUIC gave us for free.

Can I just kick the tires without signing up? +

The repo has a docker-compose / systemd setup in the runbook. Clone, build, seed a superadmin via SQL, and you're in. No account required to try it.

Clone it tonight. Ship a tenant by Friday.

The repo is production-adjacent, not hypothetical. Everything on this page exists in master. Four commands and a DNS record get you to first signup.

Clone the repo Read the build log
$ git clone https://github.com/vikasswaminh/21tunnel  ·  MIT + Apache-2.0  ·  Rust 1.75+