nostr-vpn

July 21, 2026 ยท View on GitHub

nostr-vpn logo

Canonical repository: git.iris.to (htree://npub1xdhnr9mrv47kkrn95k6cwecearydeh8e895990n3acntwvmgk2dsdeeycm/nostr-vpn). GitHub is a mirror.

nostr-vpn is a Tailscale-style private mesh VPN built around a FIPS-backed data plane. It includes the nvpn CLI/daemon, a shared native app core, and native shells for desktop and mobile platforms.

Downloads

Release artifacts currently cover native Apple Silicon macOS, Linux x64, Windows x64, Android arm64, StartOS x86_64/aarch64 service packages, and headless CLI archives for Apple Silicon macOS, Windows x64, Linux x86_64, and Linux arm64. Intel macOS is source-only for now.

Quick Start

cargo install nvpn --force
nvpn init
MY_NPUB='<paste nostr_pubkey from nvpn init>'
nvpn set --participant "$MY_NPUB"
nvpn start --daemon --connect

On a new device, generate its signed join request and show it to an admin in a Nostr VPN app:

nvpn init
nvpn join-request

The admin scans or pastes that request. Approval returns an admin-signed roster to the joining device. This signed request is the only join flow.

For the background daemon flow used by desktop apps:

nvpn start --daemon --connect
nvpn status
nvpn stop

For persistent startup:

sudo nvpn service install
nvpn service status

On Windows, run nvpn service install from an elevated shell instead of using sudo.

Native Apps

The native apps share the Rust app-core state/action contract and use platform shells for macOS, Linux, Windows, Android, and iOS.

just build
just run

Use just run-macos or just run-linux when you want a specific desktop target.

Nostr VPN desktop app showing a connected Home Mesh network, device status badges, and join request controls.

What Works Today

  • Generates Nostr identity keys automatically
  • Enrolls devices through signed join requests and admin-signed roster delivery
  • Stores multiple named networks with one active network at a time
  • Brings up FIPS private mesh tunnels for private network traffic
  • Routes private traffic directly when possible and through FIPS neighbors when direct UDP is blocked
  • Supports MagicDNS, authenticated DNS-over-HTTPS for exit routes, route advertisement, exit-node selection, and WireGuard upstream egress
  • Exposes native desktop apps, JSON status, network diagnostics, doctor bundles, desktop updates, and Linux-focused Docker e2e coverage

Platform Status

PlatformStatus
Apple Silicon macOSNative SwiftUI/AppKit app, CLI tarball, signed/notarized release artifacts when credentials are configured
Linux x64Native GTK/libadwaita app, .deb, CLI tarballs, Docker e2e coverage
Windows x64Native WPF app, installer, CLI zip, WinTun tunnel path
Android arm64Native app-core UI, signed APK/AAB artifacts when signing is configured, VPN runtime still being hardened
iOSNative SwiftUI app and NetworkExtension target available on the App Store; public TestFlight beta remains available
Umbrel / StartOSWeb control panels and service packages
Intel macOSSource-only

Further Reading

Maintainer Notes

This section is intentionally compact and command-oriented. Keep user-facing product detail above; keep agent/operator reference material here.

Config Model

nvpn init creates the config and keys automatically. By default, config lives in the OS app config directory:

  • Linux: ~/.config/nvpn/config.toml
  • macOS: ~/Library/Application Support/nvpn/config.toml
  • Fallback when no config dir is available: ./nvpn.toml

The config contains global app settings, Nostr relay/identity settings, NAT settings, node settings, and a [[networks]] list. Each network has its own stable network_id; only the active network participates in the live runtime.

Validation

Normal Rust gate:

cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

Per-change core/contract verification:

just verify-fast

Managed native preflight and the nightly/release five-platform matrix:

just verify-health
just verify-full

See verification tiers and managed native lab.

Release gate before version bumps and tags:

just release-gate

The gate overlaps its resource-isolated Windows and Docker-build lanes with host validation, writes each lane to artifacts/release-gate-logs, and reuses one candidate Docker image across functional and performance checks. NAT and the kernel/userspace WireGuard fixtures also overlap on isolated subnets. Roaming, throughput, host-pair, physical-device, and idle-CPU measurements remain serial so concurrency cannot skew their results.

Useful focused checks:

pnpm --dir web/control-panel check
( cd linux && cargo check )
NVPN_E2E_ROAMING_SCENARIOS=latency ./scripts/e2e-fips-roaming-docker.sh

Run the Windows build from a checkout on the configured Windows dev VM:

dotnet build windows\NostrVpn.Windows\NostrVpn.Windows.csproj -p:EnableWindowsTargeting=true

Packages and E2E

  • StartOS package: startos
  • Umbrel package: umbrel
  • Umbrel local web check:
docker compose -f umbrel/docker-compose.local.yml up --build
just e2e-umbrel-web

Focused security regression kit:

just security-regressions

Docker e2e and desktop updater scripts live under scripts. The most common entrypoints are scripts/e2e-docker.sh, scripts/e2e-fips-routed-udp-docker.sh, scripts/e2e-fips-nat-safe-mtu-docker.sh, scripts/e2e-wireguard-exit-docker.sh, and scripts/e2e-update-desktop.sh.

Release

  1. Move CHANGELOG.md from ## Unreleased to ## X.Y.Z - YYYY-MM-DD.
  2. Bump the root [workspace.package].version in Cargo.toml.
  3. Run node scripts/sync-versions.mjs and verify with node scripts/sync-versions.mjs --check.
  4. Run just release-gate.
  5. Commit, create git tag vX.Y.Z, push the tag to github, and push master to both github and htree origin.
  6. Watch .github/workflows/release.yml.

For local artifact staging, use:

cp .env.release.example .env.release.local
$EDITOR .env.release.local
just release-publish

Workspace Layout