flexaccess.dev / flextunnel
flextunnel
The rootless split tunnel for private TCP services.
- proxy layer
- no root
- CLI
- Linux
- macOS
- Windows
- iOS
- Access level
- TCP via local SOCKS5 / HTTP proxy listeners or forwarded ports
- Privileges
- None — no root or admin on either end
- DNS
- Resolved on the server's side of the network
- Authentication
- Per-client Ed25519 keys, authorized-keys style
- VPN profile
- None — it isn't a VPN, so it sits beside one
- Routing policy
- Set once on the server, pushed to every client
how it works
One encrypted connection, dialled by identity
flextunnel gives you proxy-level access to hosts behind a server — without a VPN. The client runs local SOCKS5 and HTTP proxy listeners, and can bind local ports straight to server-side addresses; targets on the server-pushed tunnel list are carried over an encrypted QUIC connection to the server, which resolves DNS and makes the outbound TCP connection from its own network. Everything else connects directly from your device.
Because it uses ordinary userspace sockets — no TUN device — neither the client nor the server needs root or admin rights. And because it isn't a VPN, it needs no VPN profile or network extension, so nothing stops you running one next to it, even on iOS.
Every connection is gated by a per-client Ed25519 keypair in the shared flexaccess-keys format; the server keeps an ssh-style authorized-keys file of the clients it admits. For a throwaway session, --quick skips key files and config entirely — each side just types in the other's endpoint ID.
features
What flextunnel gives you
Access
SOCKS5 & HTTP proxy
The client runs local SOCKS5 and HTTP proxy listeners on 127.0.0.1 — point any proxy-aware app at them and its connections to private targets ride the tunnel. The HTTP front-end is there for tools that only speak HTTP proxies or whose SOCKS5 resolves DNS on the client: wget, Docker pulls, npm, JDBC.
Local port forwarding
Bind a local port to any host:port on the server's network, so tools that can't use a proxy — databases, RDP, most GUIs — reach private services as if they were local. Per profile in the desktop and iOS apps; from config on the CLI.
Zero privileges
Ordinary userspace sockets on both ends — no TUN device, so no root, no admin, no elevation prompts. The installer puts the binary in ~/.local/bin.
Routing
Split by default, enforced twice
Only targets on the server-pushed tunnel list go through the tunnel; everything else connects directly. The server enforces the same list as a whitelist on its side, so a misconfigured client can't reach past it.
Server-side DNS, aliases, and forwarders
Reach names that only resolve on the server's network — or the server's own localhost. Map friendly names like server.internal, or wildcards like *.svc.internal, to addresses on its network, and forward chosen DNS suffixes to per-suffix upstream resolvers with failover and TCP fallback.
Server-to-server bridges
Chain servers together: one server forwards matching targets over a persistent, authenticated connection to another, which resolves and dials them from its own network — split-tunnel routing that reaches networks a single server can't. Single hop, so mutual bridges can't loop.
Quick ephemeral tunnels
flextunnel server start --quick and client start --quick: no key files, no config. Each side enters the other's endpoint ID in the terminal, the session full-tunnels for as long as you need it, and nothing is persisted. The server gives up if no client shows within five minutes.
Security
Per-client keys
Every client authenticates with its own Ed25519 keypair, signed into the handshake so a captured handshake can't be replayed; whoever runs the server decides which public keys get in.
No inbound ports
The same dial-by-identity transport: the server needs no public IP and no port forwarding.
Not a VPN
No VPN profile, no network extension, no TUN device. Nothing stops you running it next to a corporate or personal VPN — including on iOS, where the app is a plain app.
Resilience & visibility
Survives outages
Auto-reconnect with backoff from the very first attempt. Local listeners stay bound, off-list targets keep connecting directly, and on-list requests are held for up to 45 s while the link comes back instead of failing.
Bring your own relays
Point both ends at two or more custom relays and public infrastructure is out of the picture. A server that loses its home relay re-homes on another in place, so clients off the LAN aren't stranded.
Live control panel
flextunnel client control attaches a read-only terminal panel to a running client: connection phase, direct or relay path, the pushed routing breakdown, and every port forward's live state.
Built-in status page
From any connected client, http://flextunnel.internal shows the server's live status as a page or JSON — always tunneled, regardless of the routed set.
quick start
Up and running in four steps
The CLI flow, condensed from the README. You'll need flextunnel on both ends and flexaccess-keys on each client.
Generate keys
The server gets an identity key, which is what clients dial. Each client gets its own Ed25519 auth key.
# on the server flextunnel generate-iroh-key -o server.key # on each client flexaccess-keys generate-auth-key "alice laptop" -o client.key flexaccess-keys show-auth-key --private-key-file client.key # → ed25519-pub:… alice laptopWrite the server config
Paste each client's ed25519-pub line into ./authorized_keys, then decide what goes through the tunnel. The routed set is set once here and pushed to every client.
server.tomlsecret_file = "./server.key" authorized_keys_file = "./authorized_keys" # what clients may reach through the tunnel routed_domains = ["*.internal", "*.corp.example.com"] routed_cidrs = ["10.0.0.0/8"] [host_aliases] "server.internal" = "127.0.0.1"Start the server
It prints its endpoint ID on startup. No port to open, no root needed.
flextunnel server start -c server.tomlConnect a client and use it
Dial the server by its endpoint ID, then point tools at the local proxy. Use socks5h so names resolve on the server's side.
flextunnel client start \ --server-node-id <SERVER_ENDPOINT_ID> \ --auth-key-file client.key \ --socks-port 1080 curl -x socks5h://127.0.0.1:1080 http://server.internal:8000/ ssh -o ProxyCommand='nc -X 5 -x 127.0.0.1:1080 %h %p' user@db.internalJust need a tunnel to a box you're sitting at? Run --quick on both ends instead: no keys, no config, each side types in the other's endpoint ID.
install
Get the CLI
curl -sSL https://flexaccessdev.github.io/flextunnel/install.sh | bashirm https://flexaccessdev.github.io/flextunnel/install.ps1 | iexElevation is only for the installer, which writes to Program Files. Running flextunnel needs none.
No root needed to run: on Linux/macOS the installer places the binary in ~/.local/bin. Prebuilt binaries cover Linux amd64/arm64, Apple Silicon macOS, and Windows amd64; a container image is published at ghcr.io/flexaccessdev/flextunnel. Client keys are generated with flexaccess-keys. Set up flexaccess-keys
apps & repos
Pick your platform
- CLI · client and serverflextunnel
The core — command-line client, server, and read-only control panel for Linux / macOS / Windows.
one-line installer, container image, or cargo build - macOS / Windows appflextunnel-desktop
A menu-bar / system-tray app for macOS and Windows: any number of profiles connected at once, each with its own SOCKS5 / HTTP listeners and port forwards; keys generated in-app, connection-path view, live per-profile logs, profile export and import, and secrets kept in the macOS Keychain or Windows Credential Manager.
unsigned .dmg / .msi with every stable release - iOS appflextunnel-ios
Browse private networks in a built-in browser — split by hostname when the tunnel set is name-based, everything through the tunnel otherwise — or forward localhost ports so other apps on the phone (SSH, RDP, databases) can reach them, with an opt-in, time-limited background keep-alive. Saved profiles, keys generated on-device and kept in the Keychain, no VPN profile required. Each release publishes an unsigned .ipa to sign under your own Apple team.
unsigned .ipa, self-signed
good for
Where it fits
- Web UIs, SSH, RDP, and databases that are only reachable from the server's network
- Shared or locked-down machines where you can't get admin rights
- Sitting next to a corporate or personal VPN — it isn't one, so there's nothing to switch off
- A five-minute throwaway tunnel to a box you're sitting at — with --quick
keep in mind
Where it doesn't
Trusted clients, trusted host
flextunnel lets clients you trust reach a server you also run; it is not a multi-tenant service. An authorized client can reach whatever the server's network can — including the server's own localhost — so authorize keys accordingly and scope the server's network reach if that is too broad. The local proxy listeners are unauthenticated and bind 127.0.0.1 only, so the client host is assumed to be single-user.
Need UDP forwarding?
flextunnel carries TCP only. If you need to forward UDP — WireGuard or OpenVPN over P2P, game servers, VoIP, or UDP services in Kubernetes that kubectl port-forward can't reach — use tunnel-rs: peer-to-peer TCP and UDP port forwarding for Linux, macOS, and Windows over the same encrypted QUIC transport, with the same no-open-port NAT traversal, the same flexaccess-keys Ed25519 authentication, and no root on either end. It's built for development and homelab use rather than production. tunnel-rs on GitHub
Pre-1.0: keep both ends on the same release
While flextunnel is in the 0.0.x series there is no backward compatibility between versions. Upgrade clients and servers together, and refresh configs from the current examples when you do. If an upgraded deployment still can't connect, regenerate the server identity key as a fallback — that changes the server's endpoint ID, so update every client profile with the new one.