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.
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.
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 --releaseAgents connect over plain TCP with TLS 1.3 and yamux multiplexing. Replaced QUIC for smaller binaries and simpler NAT traversal.
$ tokio-rustls · yamux 0.13HMAC-signed tokens carry subject, allowed subdomains, bandwidth, quota, and TTL. Scoped per machine, revoke-one, replay-resistant.
$ bincode + HMAC-SHA256Hybrid self-serve signup → superadmin approval → org with members. Owner / admin / member / viewer RBAC enforced at middleware + handler.
$ Postgres · axum · JWTPasswords 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.5Enroll 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 restCustomer Portal for the tenant owner, Checkout for plan upgrades, HMAC-verified webhooks with idempotent billing_events replay.
$ Checkout · Portal · webhooksCaptured requests retained for replay. Audit log partitioned by month with a retention worker that prunes on a 90-day window.
$ audit_logs_YYYY_MMEvery 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 routesThe 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.
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).
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 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" argon2id, JWT+refresh rotation, optional TOTP, 10 recovery codes, session revocation on password reset.
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) 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.
Paste a URL into Slack — stakeholders see the feature, not a screenshare.
Stripe, GitHub, Shopify delivered to your dev box; inspector records each one.
SSH, HTTP, or TCP into printers, pis, and PLCs on residential lines.
Publish agent tools and retrieval endpoints with capability-token auth.
Your Postgres, your VM, your audit log. No vendor reads your traffic.
Give your customers tunnels under your brand. Dashboard is multi-tenant out of the box.
Spin an ephemeral tunnel in CI; tear it down on merge. One CLI, zero YAML.
Hybrid signup + superadmin approval + scoped invitations — schema already there.
The runbook walks you through these three. Everything reads from TOML, all secrets from on-disk key files with chmod 600.
[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_..." # /etc/systemd/system/qnt-server.service
[Unit]
Description=21tunnel server
After=network.target postgresql.service
[Service]
Type=simple
User=qnt
Group=qnt
ExecStart=/opt/21tunnel/target/release/qnt-server \
--config /etc/qnt/server.toml \
--log-level info
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/qnt
[Install]
WantedBy=multi-user.target # deploy/Dockerfile — already in the repo
FROM rust:1.75-slim AS build
WORKDIR /src
COPY . .
RUN cargo build --release --locked -p qnt-server
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates && \
rm -rf /var/lib/apt/lists/*
COPY --from=build /src/target/release/qnt-server /usr/local/bin/
EXPOSE 7443 8080 9090
ENTRYPOINT ["qnt-server", "--config", "/etc/qnt/server.toml"] -- 0004_auth_and_billing.sql (excerpt)
ALTER TABLE users
ADD COLUMN is_superadmin BOOLEAN NOT NULL DEFAULT FALSE;
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
revoked_at TIMESTAMPTZ
);
CREATE TABLE refresh_tokens (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
session_id UUID NOT NULL REFERENCES sessions(id),
token_hash BYTEA NOT NULL UNIQUE, -- SHA-256
rotated_to UUID REFERENCES refresh_tokens(id),
expires_at TIMESTAMPTZ NOT NULL
); git pull + systemctl restart.“ The JWT carries role; the middleware enforces it. A demotion takes effect on the next write without waiting for token expiry. ”
Dual-auth middleware: admin bearer or user JWT; both produce the same AuthContext. Handlers don't branch on auth path.
Refresh tokens rotate on every use; presenting an already-rotated token revokes the whole session. Free theft detection, no heartbeat needed.
MFA challenge is a stateless JWT, not a DB row. Login doesn't need sticky sessions on the ops side.
Plan quotas below are the actual defaults seeded in migration 0001. Self-host the whole thing and the only price is your VM bill.
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.
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.
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.
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.
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.
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.
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.