NexaPipe 0.3.0
Release

NexaPipe 0.3.0

A NexaPipe instance can now answer the question its logs could not: how is this instance doing. Health, metrics and live state sit behind a loopback listener you switch on.

29 September 2026 · one tag ships the server archives, the desktop bundles and the Android APK.

Download for your platform

Pick a build below if the guess is wrong.

Downloads

Client app first, then the server. Nothing needs an account, and nothing is gated.

Desktop app — nexa

Desktop bundles are signed with the Tauri updater key; the in-app updater reads latest.json in the same release.

Android — nexa

Only arm64-v8a is shipped. iOS is not a supported platform — see the roadmap.

Server & client library — nexapipe

Each archive holds the nexapipe binary, the client cdylib for embedding, config.toml.2fa.example, the README and the licence.

Install from a terminal

No package manager yet — these are the release assets, straight from the tag.

# Linux x86_64, static binary
curl -LO https://github.com/open-nexa/nexapipe/releases/download/v0.3.0/nexapipe-0.3.0-x86_64-unknown-linux-musl.tar.gz
tar -xzf nexapipe-0.3.0-x86_64-unknown-linux-musl.tar.gz
cd nexapipe-0.3.0-x86_64-unknown-linux-musl && ./nexapipe --version
# macOS, Apple silicon
curl -LO https://github.com/open-nexa/nexapipe/releases/download/v0.3.0/nexapipe-0.3.0-aarch64-apple-darwin.tar.gz
tar -xzf nexapipe-0.3.0-aarch64-apple-darwin.tar.gz
cd nexapipe-0.3.0-aarch64-apple-darwin && ./nexapipe --version
# Verify a download against the release checksums
curl -LO https://github.com/open-nexa/nexapipe/releases/download/v0.3.0/SHA256SUMS.txt
sha256sum -c --ignore-missing SHA256SUMS.txt

All assets: SHA256SUMS.txt.

What's new

0.3.0 follows 0.2.0, released 28 September 2026. Nothing in it breaks a working configuration: with no [admin] section the server binds nothing extra and behaves exactly as 0.2.0 did.

Instance health, metrics and state

MetricMeaning
nexapipe_connections_total
nexapipe_connections_active
Connections opened since start, and open right now.
nexapipe_connections_by_pathOpen connections by path kind: direct (hole punching succeeded), relayed, unknown. Read from iroh rather than guessed from a socket address, so "nothing to rent" finally becomes a number.
nexapipe_requests_totalRequests answered, by status class.
nexapipe_request_duration_ms_totalMilliseconds answering requests, summed; divide by requests_total for a mean.
nexapipe_l4_flows_totalFinished TCP/UDP flows by protocol and status. Flows are counted separately from requests on purpose: a flow is a tunnel a client holds open as long as it likes.
nexapipe_backends_up
nexapipe_backends_down
Backends in rotation, and backends health checks took out. Read from the live pools at scrape time.
nexapipe_in_flight
nexapipe_build_info
Connections a shutdown waits for; one series identifying the build.

Turning it on

# config.toml
[admin]
listen_addr = "127.0.0.1:9090"

[metrics]
enabled = true

# then
curl -s http://127.0.0.1:9090/healthz
curl -s http://127.0.0.1:9090/metrics | grep connections_by_path
curl -s -H "Authorization: Bearer $(cat config.toml.admin-token)" \
     http://127.0.0.1:9090/v1/status | jq
nexapipe status --json
Loopback only, and there is no escape hatch. A non-loopback bind is refused at startup rather than warned about: the answers name your routes, clients and backends. Reach it from another host over an SSH tunnel, not by widening the address.

Android: the app asks before it hands over a secret

Not yet confirmed on a device. Everything past BiometricPrompt itself has been compiled, unit-tested and linted, but not watched running. If a prompt misbehaves on your device, that is the part to look at first — and the desktop app has no such gate yet; it is planned for 0.4.

Changelog

Added

Changed

Fixed

Before upgrading

Coming from 0.1.x rather than 0.2.0? default_backend was removed in 0.2.0 and the server refuses to start on a config that still names it — refusing is the point, since ignoring it would quietly turn forwarding into 404s. Replace it with a catch-all route, host_pattern = "*".