Self-hosted device enrollment, management, and experimental private networking.
Quick Start · Downloads · Documentation · Security · Roadmap
TyxNet is an open-source control plane for enrolling and managing devices behind NAT or CGNAT. Devices initiate outbound connections to a central server, receive private IPv4 identities, and appear in a role-aware web console.
Important
Encrypted central UDP forwarding is implemented for experimental virtual-IP traffic. TyxNet is still an experimental development release, not a production-ready or independently audited VPN replacement.
Warning
The protocol has not undergone an independent security audit. Do not expose a plaintext deployment to the public internet. Use TLS or a trusted HTTPS reverse proxy and obtain an independent review before production use.
This is the easiest server installation. One Compose service and one container run TyxNet and Caddy together, provide trusted-LAN access on TCP 8443, and enable public HTTPS with either a domain or a static public IP. Raspberry Pi does not compile the Go project locally.
Requirements: 64-bit Linux, Docker Engine, Docker Compose v2 or legacy Compose
v1.29, and /dev/net/tun. Raspberry Pi OS should report aarch64 from
uname -m.
Download the Compose file and environment template:
mkdir -p tyxnet && cd tyxnet
curl -fLO https://raw.githubusercontent.com/fbeser/tyxnet/main/docker-compose.yml
curl -fLo .env https://raw.githubusercontent.com/fbeser/tyxnet/main/.env.example
nano .envFor HTTPS, set exactly one public address in .env:
# Public IP without a domain
TYXNET_PUBLIC_IP=203.0.113.10
TYXNET_DOMAIN=
# Or a domain whose A/AAAA record points to this server
TYXNET_PUBLIC_IP=
TYXNET_DOMAIN=vpn.example.comFor a trusted-LAN-only installation without HTTPS, leave both values empty:
TYXNET_PUBLIC_IP=
TYXNET_DOMAIN=The server remains available at http://RASPBERRY_PI_OR_SERVER_IP:8443 and
Caddy starts with a private no-op configuration instead of entering a restart
loop. Do not forward TCP 8443, 18080, or 18443 from the modem in this mode.
Leave the remaining port values unchanged unless they conflict with another
service. TYXNET_VERSION=latest follows new releases; use a version such as
0.3.16 to pin the deployment.
Start everything with current Compose:
sudo docker compose pull
sudo docker compose up -d
sudo docker compose psOn older Raspberry Pi installations, the same file works with legacy Compose:
sudo docker-compose pull
sudo docker-compose up -d
sudo docker-compose psIf docker compose version fails but docker-compose --version succeeds, use
the second command form consistently. Compose stores the database and Caddy
certificates in persistent Docker volumes and starts the container after a host
reboot through restart: unless-stopped.
For initial setup on the trusted LAN, open:
http://RASPBERRY_PI_OR_SERVER_IP:8443
The first page creates the initial administrator. The dashboard hides Run at startup inside containers because Compose already handles startup; systemd is neither installed nor required there.
The container entrypoint supervises both processes. It forwards Docker stop
signals to TyxNet and Caddy and exits unsuccessfully if either process stops
unexpectedly, allowing restart: unless-stopped to recover the complete stack.
Caddy automatically obtains and renews the certificate. For a domain, forward
the standard public ports to the configured host ports. With the default .env:
WAN TCP 80 -> RASPBERRY_PI_LAN_IP TCP 18080
WAN TCP 443 -> RASPBERRY_PI_LAN_IP TCP 18443
WAN TCP 18443 -> RASPBERRY_PI_LAN_IP TCP 18443
WAN UDP 51830 -> RASPBERRY_PI_LAN_IP UDP 51830
Public-IP certificates are short-lived Let's Encrypt certificates. At least one standard ACME validation route, WAN TCP 443 or WAN TCP 80, must reach Caddy. The TCP 18443 rule is the user-facing HTTPS service and UDP 51830 carries the encrypted TyxNet tunnel.
Use one of these URLs for the dashboard and every client:
https://PUBLIC_IP:18443
https://vpn.example.com:18443
If the domain is served directly on host TCP 443, set TYXNET_HTTPS_PORT=443
and use https://vpn.example.com without a port suffix. Public-IP HTTPS was
verified on Raspberry Pi 5 with Debian 12, Docker 20.10, and legacy Compose
1.29. Its certificate is valid for about six days and Caddy renews it
automatically.
Check the server, proxy, certificate, and tunnel:
sudo docker-compose ps
sudo docker-compose logs -f tyxnet-server
openssl s_client -connect 127.0.0.1:18443 -servername PUBLIC_IP -verify_return_error </dev/null
curl --resolve PUBLIC_IP:18443:127.0.0.1 https://PUBLIC_IP:18443/If these checks pass but the public URL does not open, test from mobile data
with Wi-Fi disabled. Many home routers do not support NAT loopback. Confirm that
the modem forwards TCP 18443 to the Raspberry Pi's active Ethernet address,
not a second Wi-Fi address. During the external test,
sudo tcpdump -ni any 'tcp port 18443' shows whether the connection reaches the
host: no incoming SYN means the problem is still in the modem, ISP firewall or
CGNAT path.
CasaOS must receive the complete docker-compose.yml in one import. Open
App Store → Custom Install → Import, paste or upload the whole Compose file,
and verify that the preview contains only the tyxnet-server service before
installing. The single container starts TyxNet first, waits for its local HTTP
listener, and then starts Caddy.
In the imported service environment, set one address for HTTPS:
TYXNET_PUBLIC_IP=203.0.113.10
TYXNET_DOMAIN=
For a domain, leave TYXNET_PUBLIC_IP empty and set TYXNET_DOMAIN instead.
For LAN-only HTTP, leave both values empty; the application remains healthy and
the dashboard is available on the configured TYXNET_LAN_PORT.
The supervisor entrypoint is stored inside the TyxNet image, so CasaOS cannot
expand its shell variables while parsing Compose. Caddy proxies to
127.0.0.1:8443 inside the same container; Docker DNS and container names are
not involved.
CasaOS can customize TYXNET_VERSION, LAN/tunnel/HTTPS host ports, and volume
names while importing. Only TYXNET_DOMAIN and TYXNET_PUBLIC_IP are passed to
the container. Unrelated host variables such as OPENAI_API_KEY are not copied
into it.
The default volume names deliberately match earlier installs created with
project name tyxnet:
tyxnet_tyxnet-data
tyxnet_caddy-data
tyxnet_caddy-config
Before migration, identify the actual names with
docker volume ls --format '{{.Name}}'. If they differ, set
TYXNET_DATA_VOLUME, TYXNET_CADDY_DATA_VOLUME, and
TYXNET_CADDY_CONFIG_VOLUME to those existing names during import. Stop the old
application only after confirming the names. Never use down -v, delete a
volume, or create an empty replacement volume during migration.
When upgrading from the previous two-container Compose layout, use the old Compose file once to stop its containers without deleting volumes, then replace the file and start the new layout:
sudo docker-compose -p tyxnet down
curl -fLo docker-compose.yml \
https://raw.githubusercontent.com/fbeser/tyxnet/main/docker-compose.yml
sudo docker-compose -p tyxnet pull
sudo docker-compose -p tyxnet up -dDo not add -v. For a CasaOS migration, stop the old application without its
Delete data option, confirm the three named volumes still exist, and import
the new Compose file as one application. After startup, docker-compose ps
must show one container and docker-compose logs -f tyxnet-server must show both
TyxNet and Caddy startup messages.
Already enrolled clients keep their device identity; only change server: to
the new HTTPS URL and restart TyxNet. Windows stores this file at
C:\ProgramData\TyxNet\client.yaml; macOS stores it under
~/Library/Application Support/TyxNet/client.yaml. No new enrollment token is
required.
Update or stop the stack without deleting its data:
sudo docker-compose pull && sudo docker-compose up -d
sudo docker-compose downNever add -v to down during updates or HTTPS migration. It deletes the
TyxNet database and Caddy certificate state. Compose v2 users can replace
docker-compose with docker compose. The published multiarch image is
ghcr.io/fbeser/tyxnet:latest for linux/amd64 and linux/arm64.
- Open the latest release.
- Download
TyxNet-<version>-macos-universal.dmg. - Drag TyxNet Client or TyxNet Server into Applications.
- On first launch, right-click the app and select Open.
- Approve the administrator prompt required for the native tunnel process.
The universal DMG supports Intel and Apple Silicon. Client and Server run as
menu-bar apps; configuration is stored under
~/Library/Application Support/TyxNet and logs under ~/Library/Logs/TyxNet.
The current apps are ad-hoc signed but not Apple-notarized. Gatekeeper may show an unsigned developer warning. Disable Run at startup before deleting the app to remove its LaunchDaemon and LaunchAgent registrations.
- Open the latest release.
- Download
TyxNet-<version>-windows-amd64.msi. - Run the installer and approve the UAC prompt.
- Launch TyxNet Client or TyxNet Server from the Start menu.
The x64 MSI installs the server, client, tray companions, tyxnetctl, and the
verified Wintun 0.14.1 runtime under Program Files\TyxNet. Mutable data and logs
are stored under ProgramData\TyxNet.
The MSI is not Authenticode-signed, so Windows SmartScreen may warn on first
launch. Windows ARM64 binaries are available as standalone .exe files in the
release; an ARM64 MSI is not available yet.
The one-line installer detects Linux amd64 or arm64, downloads the matching
release binaries and checksum, installs tyxnet-server plus tyxnetctl, and
enables the systemd service:
curl -fsSL https://github.com/fbeser/tyxnet/releases/latest/download/install-server.sh | sudo shChoose non-default listener ports during the first installation:
curl -fsSL https://github.com/fbeser/tyxnet/releases/latest/download/install-server.sh | \
sudo sh -s -- --api-port 9443 --tunnel-port 51999The management console can persist later TCP management-port and UDP tunnel-port changes under Overview → Listener ports. Restart the service and update firewall, router, and client-facing URLs after saving. Container host ports remain deployment settings and must be changed in the Compose environment.
sudo systemctl status tyxnet-server
sudo journalctl -u tyxnet-server -fThe client-only installer verifies the matching release binary and enables it
at startup. With no arguments, enroll through the local/LAN setup page on port
9070:
curl -fsSL https://github.com/fbeser/tyxnet/releases/latest/download/install-client.sh | sudo shFor unattended enrollment, provide all three values together:
curl -fsSL https://github.com/fbeser/tyxnet/releases/latest/download/install-client.sh | \
sudo sh -s -- --server https://vpn.example.com --token 'TYX-...' --name raspberry-piRe-running either installer upgrades the binaries and preserves existing configuration, database, and client identity files.
| Area | Status |
|---|---|
| First-admin setup and authenticated web console | Working |
| Users, roles, sessions, enrollment tokens, devices, and audit events | Working |
| Ed25519 enrollment and challenge-authenticated control connection | Working |
| Reconnect, heartbeat, device presence, and role-scoped views | Working |
| Network-flow topology and 60-second traffic dashboard | Working |
| Linux TUN, Windows Wintun, and macOS utun adapter creation | Experimental |
| Windows tray and macOS menu-bar companions | Experimental |
| Native Linux systemd installation | Working |
| Multiarch Docker image and Compose deployment | Working |
| Encrypted UDP packet data plane and virtual-IP traffic | Experimental |
| Authenticated remote reconnect, restart, shutdown, and result reporting | Working |
| Signed/notarized desktop distribution | Not complete |
| Mobile clients, P2P, STUN, and hole punching | Planned |
With HTTPS control and UDP 51830 reachable, traffic such as
ping 10.90.0.1 or ssh user@10.90.0.4 crosses the central encrypted data
plane. Host firewalls and the destination service must still allow that traffic.
- Central self-hosted server with SQLite persistence and append-only migrations
- Browser-based server management and client enrollment/status consoles
- Users, RBAC roles, enrollment tokens, sessions, commands, and audit records
- Ed25519 device identity and challenge authentication
- HKDF-SHA256, ChaCha20-Poly1305, directional keys, and replay protection
- Argon2id passwords and one-way hashes for random tokens
- Virtual source-IP validation and central routing policy
- Live network topology, stable/filterable protocol and port flows, per-flow Mbps, packet totals, and 60-second throughput charts
- Windows Wintun, Linux TUN, and macOS utun platform adapters
- Windows notification-area and macOS menu-bar companions
- Strict enum-based remote command allowlist with no server-provided shell text
- Linux systemd, Docker Compose, Windows MSI, and universal macOS DMG packaging
- Cross-builds and release checksums for Linux, Windows, and macOS
flowchart LR
LC[Linux client] -->|TLS control| S[TyxNet Server]
WC[Windows client] -->|TLS control| S
MC[macOS client] -->|TLS control| S
UI[Web console / tyxnetctl] -->|HTTPS API| S
S --> DB[(SQLite)]
LC -->|Encrypted UDP packets| S
WC -->|Encrypted UDP packets| S
MC -->|Encrypted UDP packets| S
TyxNet uses a central star topology. Clients connect outbound through NAT or CGNAT; no inbound client port forwarding is required. TyxNet does not use WireGuard and does not route general internet traffic through the tunnel by default. See Architecture and Protocol.
| Platform | Architectures | Server adapter | Client adapter | Desktop UI | Package |
|---|---|---|---|---|---|
| Linux | amd64, arm64 | TUN | TUN | Web | Binary / Docker |
| macOS | Intel, Apple Silicon | utunN |
utunN |
Web + menu bar | Universal DMG |
| Windows | amd64, arm64 | Wintun | Wintun | Web + tray | x64 MSI / binaries |
| Android / iOS | Planned | — | — | — | — |
For trusted-LAN installs, open http://SERVER_IP:8443 and complete the setup
form. To create the administrator from the CLI without placing the password in
process arguments:
The setup card, login card, and management sidebar display the running TyxNet version so operators can confirm the active build before creating the first administrator.
printf '%s\n' 'a-strong-password' | sudo tyxnet-server admin create \
--config /etc/tyxnet/server.yaml \
--username admin \
--password-stdinsudo tyxnet-server token create \
--config /etc/tyxnet/server.yaml \
--user admin \
--expires 24h \
--max-uses 1The web console can create and revoke enrollment tokens without using the CLI.
Use the client-only release installer and enroll from the web page on port
9070:
curl -fsSL https://github.com/fbeser/tyxnet/releases/latest/download/install-client.sh | sudo shFor unattended enrollment, pass --server, --token, and --name as shown in
the Native Linux client section above. The installer stores the identity with
owner-only permissions and creates a systemd service.
Install TyxNet Client.app from the universal DMG, open the menu-bar icon, and
select Open Web Console. Enter the server URL, device name, and enrollment
token. The local UI is available at http://127.0.0.1:9070. Leave server
removes the saved server URL, enrollment identity, active tunnel, and local
management session so the client can enroll again. It is restricted to a
loopback browser request and does not delete the server-side device record;
revoke that old record separately from the server console.
macOS packet routing remains experimental and a production Network Extension is not implemented. See macOS client notes.
Install the x64 MSI, launch TyxNet Client, and use its tray menu to open the
local enrollment console. The launcher requests administrator access and uses
the bundled Wintun runtime. The local UI is available at
http://127.0.0.1:9070 and provides the same local-only Leave server reset.
See Windows client notes.
The server dashboard provides setup, login, overview counters, device rename and revoke, persistent virtual-IP assignment, command actions, user and role management, administrator password resets, token management, command history, server settings, network-flow telemetry, and audit logs. The flow panel shows device-to-device direction, TCP/UDP ports or ICMP metadata, stable sorting, filters, expandable details, current Mbps, packet totals, and a 60-second chart from successfully routed virtual-IP packets. Administrators can optionally enable persistent flow history and set a 1–10240 MB logical metadata budget; recording is off by default, the setting survives restart, and the oldest saved rows are removed when the selected budget is reached. Saved history has endpoint, protocol, date/time, transfer, and packet filters plus an administrator-only delete-all action. For example, the additive live flow API metadata includes:
{"source":"10.90.0.2","destination":"10.90.0.3","protocol":"tcp","protocol_number":6,"source_port":52000,"destination_port":22}The authenticated API can enable a 100 MB history budget and query a UTC time range without exposing packet contents:
curl -X PATCH https://vpn.example.com/api/v1/server/settings \
-H "Authorization: Bearer $TYXNET_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"flow_history_enabled":true,"flow_history_limit_mb":100}'
curl -G https://vpn.example.com/api/v1/network/flows/history \
-H "Authorization: Bearer $TYXNET_ACCESS_TOKEN" \
--data-urlencode 'protocol=tcp' \
--data-urlencode 'from=2026-08-09T09:00:00Z' \
--data-urlencode 'to=2026-08-09T10:00:00Z'Resetting a password signs out every existing session for that user; the new password must contain at least 12 characters. Remember me creates a 30-day session only over HTTPS.
| Role | Devices visible to clients | Client controls |
|---|---|---|
| Admin | All devices | Reconnect, restart, shutdown |
| Operator | All devices | Reconnect, restart, shutdown |
| Viewer | All devices | View only |
| Member | Devices owned by that user | View only |
Admins, operators, and viewers can view live and saved flow metadata. Members cannot access the cross-device flow panel. Traffic payloads, DNS names, and application-protocol contents are not stored. Live IP protocol and port/ICMP metadata remains in memory for 60 seconds. When an administrator enables flow history, one-second aggregates of that same metadata are persisted to SQLite until deleted or aged out by the configured logical metadata budget.
Reconnect, restart, and shutdown commands are delivered over the authenticated
control stream and tracked as queued, delivered, accepted, succeeded, failed, or
expired. Clients accept only fixed allowlisted actions: Windows uses
shutdown.exe, Linux uses systemctl, and macOS uses /sbin/shutdown. The
client service must run with the operating-system privileges required by those
commands. No server-provided text is passed to a shell.
tyxnetctl --server https://vpn.example.com login \
--username admin --password '...'
export TYXNET_SERVER=https://vpn.example.com
export TYXNET_ACCESS_TOKEN=TYX-...
tyxnetctl devices list
tyxnetctl users list
tyxnetctl tokens listA password supplied on the command line may be visible in the process list. Use a secure secret source or the API for automation.
Example YAML files are under configs/. The server validates
addresses, ports, CIDRs, TLS paths, and plaintext-bind opt-in at startup.
Important locations:
| Installation | Configuration | Data / identity | Logs |
|---|---|---|---|
| Docker server | Image default or mounted YAML | tyxnet-data volume |
docker compose logs |
| Native Linux server | /etc/tyxnet/server.yaml |
/var/lib/tyxnet |
systemd journal |
| Windows MSI | ProgramData\TyxNet |
ProgramData\TyxNet |
ProgramData\TyxNet\logs |
| macOS app | ~/Library/Application Support/TyxNet |
Same directory | ~/Library/Logs/TyxNet |
Private keys and tokens are never written into server configuration YAML.
| Port | Protocol | Purpose |
|---|---|---|
| 443 | TCP/UDP | Public HTTPS or ACME validation through Caddy |
| 18443 | TCP | Optional public-IP HTTPS endpoint mapped to Caddy |
| 8443 | TCP | Direct/LAN dashboard, enrollment, and control APIs |
| 51830 | UDP | Encrypted virtual-IP packet tunnel |
| 9070 | TCP | Local client enrollment and status UI |
Allow only the required ports from intended networks. Never expose TCP 9070 to the public internet. The default Docker Compose file publishes TCP 8443 to the LAN for first setup and should be used only on a trusted network until TLS is configured.
Clients initiate UDP keepalives, so no client-side port forwarding is needed.
The server-side modem/firewall must forward UDP 51830 to the TyxNet server.
sudo docker compose ps
sudo docker compose logs -f tyxnet-server
sudo docker compose restart tyxnet-server
sudo docker compose pull && sudo docker compose up -dsudo systemctl status tyxnet-server
sudo systemctl restart tyxnet-server
sudo journalctl -u tyxnet-server -fUninstall removes the native binary, unit, and configuration but intentionally preserves server data for recovery.
Requirements: Go 1.25 or newer. Desktop tray builds also require the native Windows or macOS toolchain.
git clone https://github.com/fbeser/tyxnet.git
cd tyxnet
make build
make testUseful targets:
| Command | Purpose |
|---|---|
make build |
Build server, client, trays, and CLI for the host |
make test |
Run unit and integration tests |
make test-race |
Run the Go race detector |
make vet |
Run go vet |
make lint |
Run golangci-lint |
make release |
Cross-build release binaries and checksums |
make release-full |
Build all release binaries plus macOS DMG and Windows MSI |
make package-macos |
Build the universal DMG on macOS |
make package-windows |
Build the x64 MSI with wixl |
Release targets accept VERSION, for example:
make release-full VERSION=0.3.16Every GitHub release includes checksums.txt:
grep 'tyxnet-server-linux-arm64$' checksums.txt | sha256sum -c -Release binaries and GHCR images are built and published from the local release
machine; GitHub Actions is not used for release publishing. Versioned and
latest container tags are published for linux/amd64 and linux/arm64.
| Topic | Document |
|---|---|
| Architecture | docs/architecture.md |
| Protocol | docs/protocol.md |
| Security model | docs/security-model.md |
| Server installation | docs/server-installation.md |
| Linux client | docs/linux-client.md |
| macOS client | docs/macos-client.md |
| Windows client | docs/windows-client.md |
| OpenAPI | docs/openapi.yaml |
| Troubleshooting | docs/troubleshooting.md |
| Roadmap | docs/roadmap.md |
| Development | docs/development.md |
Read CONTRIBUTING.md and AGENTS.md. Security issues must be reported privately according to SECURITY.md.
Apache License 2.0. See LICENSE and THIRD_PARTY_NOTICES.md.