Connect your servers, sites, and devices into one private, encrypted network — shipped as a single tiny binary that runs anywhere, from a cloud VM to a MikroTik router.
English · 简体中文
Subnetra stitches machines in different places — offices, a data center, roaming laptops, home labs, containers, routers — into a single flat private subnet.
It uses a hub-and-spoke design: a reachable hub relays traffic between spokes, so any node can reach any other by a stable overlay IP even when most of them sit behind NAT. Every packet travels fully encrypted over an ordinary UDP tunnel, and the whole thing is one self-contained binary — nothing else to install, no kernel modules, no daemon zoo.
- 🏢 Link branch offices to a data center — route whole subnets site-to-site over one private overlay.
- 💻 Give roaming laptops a stable private IP that follows them across Wi-Fi, LTE, and home networks.
- 🧪 Reach home-lab / IoT / container services as if they were on the same LAN.
- 🛰 Publish a LAN from behind NAT — e.g. a MikroTik router exposing
192.168.88.0/24to the mesh. - 📦 Run where heavy VPN stacks won't fit — constrained containers, BusyBox, small ARM boxes, edge routers.
- Runs anywhere, installs as one file — a single static binary (under 512 KB on
Linux) with zero external dependencies. Drops onto cloud VMs, containers, BusyBox,
Raspberry Pi, and MikroTik RouterOS. Builds for
amd64/arm64/armv7/armv5, plus a native macOS spoke. - Encrypted by default, invisible on the wire — every packet is ChaCha20-Poly1305 encrypted with per-link keys and replay protection. There are no magic bytes, and unauthenticated packets are dropped silently — to a port scanner the tunnel looks like nothing is listening.
- A flat private subnet with policy routing — give every node an overlay IP, route whole subnets site-to-site, and let the hub relay spoke-to-spoke so nodes behind NAT still reach each other.
- Just works behind NAT — spokes keep their own NAT pinhole open with a built-in keepalive, and the hub automatically relearns a spoke that roams to a new address — no external pinger, no manual reconnect.
- Change routes live — inject or update forwarding rules at runtime with zero downtime: no restart, no dropped packets.
- Built to be operated — human-readable and JSON status, per-reason drop counters
that tell you why traffic isn't flowing, per-peer health/
onlineflags, and a Prometheus exporter for alerting. - Simple, declarative config — describe a node as a
hubor aspokeand the forwarding table is derived for you. Name your peers sostatusreadsbj-office-gw, notid=2.
The fastest path is the container image (the hub is typically a public cloud host):
# 1. Create a config — one hub, one or more spokes.
cp config.example.json config.json
# Set a UNIQUE 64-hex key on every peer link: openssl rand -hex 32
# 2. Run it (the tunnel needs the TUN device + NET_ADMIN).
docker run -d --name subnetra \
--cap-add=NET_ADMIN --device=/dev/net/tun \
-v "$PWD/config.json":/etc/subnetra/config.json:ro \
ghcr.io/jamiesun/subnetra:latest
# 3. Check it.
docker exec subnetra subnetra statusPrefer a bare binary? Grab the static build for your architecture from the
latest release, drop a
config.json next to it, and run ./subnetrad. A full hub + two-spoke production
walkthrough lives in the deployment guide.
Set a role and Subnetra derives the routing table for you. A home/office spoke
that exposes its own overlay IP and sends everything else through the hub needs only:
{
"role": "spoke",
"virtual_subnet": "10.0.0.0/24",
"local_id": 2,
"local_tun_ip": "10.0.0.2/24",
"local_routes": ["10.0.0.2/32"],
"peers": [
{ "id": 1, "name": "cloud-hub", "endpoint": "203.0.113.1:51820", "allowed_src": "10.0.0.0/24", "psk": "…64 hex…" }
]
}The matching hub just lists its spokes — each peer becomes a route to that node:
{
"role": "hub",
"virtual_subnet": "10.0.0.0/24",
"local_id": 1,
"peers": [
{ "id": 2, "name": "bj-office-gw", "endpoint": "203.0.113.2:51820", "allowed_src": "10.0.0.2/32", "psk": "…64 hex…" },
{ "id": 3, "name": "alice-laptop", "endpoint": "203.0.113.3:51820", "allowed_src": "10.0.0.3/32", "psk": "…64 hex…" }
]
}Each peer link uses its own private pre-shared key (generate with openssl rand -hex 32);
sharing one key across links is rejected. Spokes enable the NAT keepalive automatically.
See config.example.json and the
deployment guide for every field, and subnetrad --check to
validate a config offline.
subnetra status turns the silent, by-design packet drops into countable signals so
you can tell why traffic is or isn't flowing:
subnetra v0.5.1 [running]
mode=raw_direct local_id=1 udp_port=51820 tun=snr0 peers=2
peers:
id=2 name=bj-office-gw endpoint=203.0.113.2:51820 allowed_src=10.0.0.2/32
id=3 name=alice-laptop endpoint=203.0.113.3:51820 allowed_src=10.0.0.3/32
traffic: tun_rx / udp_tx / udp_rx / tun_tx / relay / keepalive …
drops: unknown_peer / auth_or_invalid / spoof / no_route …
subnetra status --jsonemits the same data as a stable, versioned JSON object — including a per-peerlast_seen_age_secondsandonlineflag — for monitoring and automation (secrets are never serialized).- A drop-in Prometheus textfile exporter turns that into scrapeable metrics and alerts.
See deployment guide §6–§7 for the full status schema, the drop taxonomy, and alerting examples.
| Target | How |
|---|---|
| Container (amd64 / arm64 / armv7 / armv5) | docker pull ghcr.io/jamiesun/subnetra:latest — ships a HEALTHCHECK so orchestrators report healthy/unhealthy. |
| Static binary | Download subnetra-<version>-<arch>.tar.gz from Releases — no runtime to install. |
| Air-gapped / offline | Load the per-arch docker load-able image tarball attached to each release; verify against SHA256SUMS.txt. |
| macOS spoke | subnetra-<version>-macos-arm64.tar.gz (Apple Silicon) / …-amd64 (Intel) — runbook-certified, see docs/macos-spoke-acceptance.md. |
| MikroTik RouterOS | Scripted container bring-up/teardown in deploy/routeros/ — see docs/routeros-container.md. |
| systemd / launchd | Ready-to-edit units and hub/spoke configs in deploy/. |
- 📘 Deployment guide — hub + two-spoke production walkthrough: secrets, host networking, upgrades, HA/failover, key rotation, monitoring.
- 📐 Wire-protocol spec — the normative v1 on-wire contract (for interoperability and review).
- 🍏 macOS spoke runbook · 🛰 RouterOS container guide
- 🏗 Design & architecture — the PRD and system design.
- 📦 Releases · 🐳 Container image · ⚙️ Example config
Requires the Zig 0.16.0 toolchain — nothing else.
zig build # native build
zig build -Dtarget=aarch64-linux-musl # static cross-compile (also: x86_64 / arm-*)
zig build test # run the test suiteArtifacts land in zig-out/bin/ (subnetrad daemon + subnetra control tool). A
Linux dev container and integration/benchmark harness live under
.devcontainer/ and test/integration/;
releases are cut by bumping .version in build.zig.zon and pushing
a matching vX.Y.Z tag.
MIT © 2026 jettwang
🔁 A Chinese mirror of this README lives at
README.zh-CN.md. The two must be kept in sync: when you edit one, update the other in the same change.
