Run the Orange mesh on your own server.
orange-mesh-server is the free, self-hosted control and relay service behind an Orange private mesh. It authenticates devices, assigns stable virtual IPs, distributes the peer map and access policy, and forwards WireGuard-encrypted packets when two devices cannot reach each other directly. You run it on a Linux host you own; deployment takes about ten minutes. This guide covers the complete server lifecycle, from the first download to device enrollment, backup, migration, and recovery.
| At a glance | Value |
|---|---|
| Platform | Linux, x86-64. The binary also runs on macOS, but the systemd steps must be replaced with launchd. |
| Price | Free. No Orange account, no client license, no metered traffic. |
| Deployment time | About 10 minutes. |
| Recommended scale | Up to 100 devices per server. |
| Required ports | 8088/tcp enrollment and control, 51820/tcp relay data path, 51821/udp fast relay path (strongly recommended). |
| Admin API | 9898/tcp, loopback only — never expose it publicly. |
| Default address pool | 172.31.240.0/20, roughly 4,000 addresses. Override with --address-pool <CIDR> before the first device joins. |
| Mesh domain | orange.mesh |
| State directory | /var/lib/orange/ — config, admin credentials and TLS certificate. |
| Encryption | WireGuard end to end. The relay forwards sealed packets and never holds session keys. |
| Prerequisites | A public IPv4 address or a domain that resolves to the host, plus accurate NTP-synced system time. |
Before you begin
Every mesh networking tool needs a server: one that verifies device identity and relays traffic when two devices cannot connect directly. That means users must trust someone—not to log their activity, inspect their traffic, or interrupt the service at the moment it matters most.
We do not believe that trust should be the only option.
Orange traffic is end-to-end encrypted between devices. The server only coordinates connections and relays traffic; it cannot see the contents. That is enforced by the architecture and does not depend on who owns the server. But “we cannot see it” and “you can verify for yourself that nobody can see it” are ultimately two different things.
That is why we make the complete server available as a free download. You can deploy it on your own machine and keep the entire path under your control—privacy and control guaranteed by architecture, not by a promise.
We want you to choose Orange because it works well, not because you have no other choice.
Overview
The server has two responsibilities: managing enrollment and relaying data. It verifies device identity, assigns virtual IPs, distributes the device list and access policy, and forwards ciphertext when devices cannot connect directly.
Traffic starts on the server relay and moves to a direct peer-to-peer path once that path has proven itself, returning to the relay if it degrades. Seeing traffic on the relay is not a failure—it is the path that keeps the mesh usable in every network environment.
Devices on the same network can find each other without any help. For two devices on different networks to establish a direct path, the server has to help them discover their public addresses—see Improve direct-path success. Until you enable that, expect most cross-network traffic to stay on the relay.
Each enrolled device receives a stable virtual IP that remains unchanged across restarts, Wi-Fi changes, and travel. Devices can then reach one another as if they shared one local network.
Deployment takes about ten minutes and normally requires very little maintenance afterward. A single server is recommended for no more than 100 devices.
Requirements
Before install- A Linux host. The binary also runs on macOS, but the systemd steps in this guide must be replaced with launchd.
- A public IPv4 address, or a domain name that resolves to the host.
- Accurate system time through NTP or chrony. Clock drift causes certificate and credential validation failures that are otherwise difficult to diagnose.
Open the required ports
| Port | Protocol | Purpose |
|---|---|---|
8088 | TCP | Required. Device enrollment and control connection. |
51820 | TCP | Required. Relay data path. |
51821 | UDP | Strongly recommended. Fast relay path. Without it, traffic falls back to TCP and large transfers are noticeably slower. |
9898 | TCP | Admin API. Keep it on loopback only; never expose it to the public internet. |
Choose the virtual address pool
Initialization writes 172.31.240.0/20, which provides roughly 4,000 addresses and stays clear of the ranges most networks already use. Once a device has joined, do not change this range—it is part of the mesh's network identity, and changing it means enrolling every device again.
Before initialization, confirm that it does not overlap another VPN, a cloud VPC, Docker or Kubernetes, your office or home LAN, or the reserved 198.18.0.0/15 range. Tailscale, for example, commonly uses 100.64.0.0/10.
To use another range, pass --address-pool <CIDR> to the initialization command, for example 10.90.0.0/16. The host bits must be zero—use 10.90.0.0/16, not 10.90.0.5/16—and the prefix must not be narrower than /30.
Install and initialize
About 10 minutesDownload the archive, install the binary at a permanent path, initialize server state, and register the system service.
curl -fL https://orangenet.app/releases/mesh-server-x86-64-latest.tar.gz \
-o orange-mesh-server-latest.tar.gz
tar xzf orange-mesh-server-latest.tar.gz
sudo install -m 0755 ./orange-mesh-server /usr/local/bin/orange
sudo install -d -m 700 /var/lib/orange
sudo orange mesh init-server --public-endpoint mesh.example.com
sudo orange test
sudo orange mesh install-systemd
sudo systemctl daemon-reload
sudo systemctl enable --now orange
orange-mesh-server, replace orange in the remaining commands accordingly.Pass only a hostname or IP address to --public-endpoint—do not include a port or an http:// prefix.
Initialization creates the configuration, admin credentials, and TLS certificate under /var/lib/orange/. It also prints a bootstrap join credential.
Verify the server
sudo orange mesh status
Mesh: enabled
Node: mesh-server-1 (<node-id>)
Roles: control,relay
Domain: orange.mesh
Peers: 0
Ready: true
Control: store_open=true hub_available=true
Relay hub: sessions=0
Direct path: disabled
Warnings: 1
warn mesh.security.static_bootstrap_tokens_present - Static bootstrap join tokens are still configured on this control node.
The important field is Ready: true. A new server normally reports zero peers and zero relay sessions.
| Output | Why it is normal on a new server |
|---|---|
Peers: 0 | No device has enrolled yet. |
Relay hub: sessions=0 | No enrolled peer is using the relay. |
Direct path: disabled | The server is not an edge device and does not participate in peer-to-peer paths. |
static_bootstrap_tokens_present | The bootstrap credential still exists. Remove it in the next step. |
sudo ss -lntup | grep orange
Confirm that 8088/tcp, 51820/tcp, 51821/udp, and loopback-only 9898/tcp are listening.
Remove the bootstrap credential
[[mesh.control.join_tokens]] block as soon as the server passes its readiness check.sudo vi /var/lib/orange/config.toml
# Delete the complete [[mesh.control.join_tokens]] block.
sudo systemctl restart orange
sudo orange mesh status
The warning should disappear and Warnings should return to zero.
Enroll devices
sudo orange mesh issue-join --label my-laptop
The command writes a one-time .omesh file and prints its path. Transfer that file to the user and import it in the Orange client. The device receives a stable virtual IP when enrollment completes.
| Option | Default | Purpose |
|---|---|---|
--label <name> | — | Human-readable note used for identification. It has no effect on behavior. |
--expires-in-secs <n> | 3600 (1 hour) | Validity period. Maximum seven days. |
--max-uses <n> | 1 | Number of successful enrollments allowed. |
--reusable | No | Permits reuse, but does not raise the use count by itself. Also set --max-uses. |
--roles edge,gateway | Normal device (edge) | Grants gateway permission so a Linux device can publish a LAN behind it. |
sudo orange mesh issue-join \
--label team \
--expires-in-secs 86400 \
--reusable \
--max-uses 20
The permissions requested by a device must match the join credential. A normal edge credential cannot enroll a gateway.
The command output also includes a CLI enrollment example for enrolling from the command line on a server. Orange app users only need to import the generated file and do not need to run that command.
sudo orange mesh nodes
The node list includes device ID, virtual IP, name, roles, and online state. A device with no heartbeat for more than 90 seconds is shown as offline.
Daily operations
| Operation | Command |
|---|---|
| Server status | sudo orange mesh status |
| Enrolled devices | sudo orange mesh nodes |
| Issue a join file | sudo orange mesh issue-join --label <note> |
| Issued join credentials | sudo orange mesh tokens |
| Revoke an unused credential | sudo orange mesh token revoke <ID> |
| Disable a lost device | sudo orange mesh node disable <device ID> |
| Remove a retired device | sudo orange mesh node remove <device ID> |
| Rename a device | sudo orange mesh node rename <device ID> <new name> |
| Restart after configuration changes | sudo systemctl restart orange |
| Follow logs | sudo journalctl -u orange -f |
A unique prefix of a device ID is sufficient, such as a3f2.
Disable versus remove
| Action | Use it when | Effect | Reversible |
|---|---|---|---|
disable | A device is lost or its credential may be exposed. | Disconnects it immediately and persistently rejects reconnection. | No enable command exists. |
remove | A device is permanently retired. | Deletes its record and releases its name and virtual IP. | No. |
- Revoking a join credential only affects unused enrollments. Disable a device that has already joined.
- Removing a device releases its name. If ACL rules reference that name, another device could inherit those permissions. Update and restart the policy before removal.
Configuration file
The generated configuration lives at /var/lib/orange/config.toml. Unknown or misspelled fields are startup errors; they are never silently ignored.
| Section | Guidance |
|---|---|
[general], [dns], [proxy], [listeners.*] | Do not delete these sections even if they appear unused. The configuration will not parse without them. |
[admin], [admin.auth] | Leave unchanged unless rotating admin credentials. |
[mesh], [mesh.control], [mesh.relay], [mesh.wireguard] | Ports and address pool can be planned before enrollment. Keep other generated values unchanged. |
[[mesh.control.join_tokens]] | Bootstrap credential. Delete it after the initial readiness check. |
[[mesh.acl]], [mesh.groups] | Add only when access control is required. |
[mesh.direct.stun], [mesh.relay_udp_sidecar_pacing] | Optional advanced features. These sections are absent by default. |
sudo systemctl restart orange after editing the configuration.Backup
sudo systemctl stop orange
sudo tar czf orange-backup-$(date +%F).tar.gz -C /var/lib orange
sudo systemctl start orange
Create the first backup immediately after enrolling your first group of devices.
Migrate to a new server
# Old server
sudo systemctl stop orange
sudo tar czf orange-migrate.tar.gz -C /var/lib orange
# New server
sudo install -m 0755 ./orange-mesh-server /usr/local/bin/orange
sudo tar xzf orange-migrate.tar.gz -C /var/lib
sudo orange test
sudo orange mesh install-systemd
sudo systemctl daemon-reload
sudo systemctl enable --now orange
/var/lib/orange—because generated configuration contains absolute paths. A path mismatch causes startup to fail with an error such as managed path ... must remain inside state_dir .... If another location is unavoidable, update every stored path together.| Original public endpoint | After migration |
|---|---|
| Domain name | Point DNS to the new server. Devices reconnect automatically. |
| Raw IP address | Devices retain the old IP and lose connectivity. Update the server configuration, restart, then issue new join credentials and enroll every device again. |
The restored certificate remains valid and does not need to be regenerated.
Troubleshooting
sudo orange mesh status
sudo journalctl -u orange -n 200 --no-pager
Use the Ready field to judge server health. A running process by itself is not proof that control and relay services are usable.
Ready: false
| Status hint | Action |
|---|---|
control_store_not_open | Check for a full disk and verify permissions on /var/lib/orange. |
control_hub_not_available | Inspect certificate errors in the log. Confirm the certificate and key paths exist and the private key is mode 0600. |
relay_hub_not_available | Check whether another process is already using port 51820. |
Common symptoms
| Symptom | Check |
|---|---|
| Device cannot reach the server | DNS resolution, 8088/tcp, synchronized system time, and whether the certificate recently changed. |
| Devices enrolled but cannot reach each other | 51820/tcp, followed by ACL rules if access control is enabled. |
| Relay is noticeably slow | 51821/udp. Without it, large transfers fall back to TCP. |
| Join credential is invalid or expired | It was used up, expired, revoked, or belongs to another mesh. |
| Join role mismatch | The requested device role does not match the credential. A normal credential cannot enroll a gateway. |
| Admin command returns 401 | Use the content of admin.token, not the token_hash verifier stored in the configuration. |
| Admin connection refused | The service is down, or the admin port changed and the command lacks --admin-port. |
| Startup configuration error | Check field spelling. Unknown fields stop startup. |
127.0.0.1 cannot be reached through the mesh. Bind it to the device's virtual IP or 0.0.0.0.Access control
OptionalAll settings from this point are optional. The generated defaults already work. Restart Orange with sudo systemctl restart orange after changing /var/lib/orange/config.toml.
By default, every registered device can reach every other device. Add ACL rules when only specific users or groups should reach a service.
[mesh.groups]
admins = ["alice-laptop", "bob-laptop"]
[[mesh.acl]]
from = ["group:admins"]
to = ["office-gateway:22"]
protocol = ["tcp"]
# via = "office-gateway" # Required when reaching a subnet behind a gateway.
from may name a group or a device. to may target a device name or subnet followed by a port. Supported protocols are tcp, udp, and icmp-echo; set via to the gateway device name when the destination is a LAN behind that gateway.
Custom ports
OptionalChange ports only when the defaults conflict with another service or are blocked by the network. Complete this before production enrollment; existing devices retain the old ports and must be enrolled again after a change.
[mesh.control]
listen = "0.0.0.0:18088"
[mesh.relay]
listen = "0.0.0.0:15120"
public_endpoint = "mesh.example.com:15120" # UDP fast path uses 15121.
Choose a non-default admin port during initialization with --admin-port. Every later admin command must then include --admin-port <port>.
Improve direct-path success
OptionalEnable STUN assistance when peers sit behind different carriers or complex NATs and too much traffic falls back to relay. Open 3478/udp on the server.
[mesh.direct.stun]
enabled = true
listen = "0.0.0.0:3478"
public_endpoint = "mesh.example.com:3478"
Enable the matching option in the Orange client. A failed direct attempt does not break connectivity; traffic automatically remains on the relay.
Relay bandwidth
OptionalUse bandwidth pacing when the host has ample bandwidth but measured relay throughput remains below the available link capacity.
[mesh.relay_udp_sidecar_pacing]
mode = "bandwidth"
target_bps = 100000000
burst_bytes = 250000
The example targets 100 Mbps. A reasonable starting value for burst_bytes is target_bps ÷ 400. Tune against your own measurements; the example is not a performance guarantee.
If UDP is completely unavailable, add relay_udp_sidecar_enabled = false under [mesh] to use TCP only.
Use your own TLS certificate
OptionalThe default self-signed certificate is secure because each join credential carries its fingerprint. Replacing that certificate later, however, requires new join credentials for every device. A trusted certificate avoids that operational cost.
sudo install -d -m 700 /var/lib/orange/tls
sudo install -m 600 \
/etc/letsencrypt/live/mesh.example.com/privkey.pem \
/var/lib/orange/tls/
sudo orange mesh init-server \
--public-endpoint mesh.example.com \
--trust webpki \
--cert /etc/letsencrypt/live/mesh.example.com/fullchain.pem \
--key /var/lib/orange/tls/privkey.pem
--trust-mode webpki every time you issue a join credential. Otherwise the credential pins the current certificate fingerprint and enrolled devices fail after renewal. Restart Orange after each certificate renewal.The private key must be a regular file owned by the Orange service user, mode 0600, inside a mode 0700 parent directory. Let's Encrypt's live/ entries are symlinks, so copy the key to a protected regular file first.
Keep the copy in sync at renewal
control_hub_not_available. Refresh the copy and restart in the same deploy hook, and renewal stays uneventful.#!/bin/sh
set -e
install -m 600 -o orange -g orange \
/etc/letsencrypt/live/mesh.example.com/privkey.pem \
/var/lib/orange/tls/privkey.pem
systemctl restart orange
Adjust the owner to whichever user runs Orange on your host. Certbot runs every executable in that directory after a successful renewal.
Additional notes
State directory
The default is /var/lib/orange. Commands run with sudo and commands run as a normal user resolve state differently, so always operate the server under the same identity. To use another path, add --state-dir <path> to every command.
Remote administration
The admin API remains on loopback. Run management commands through SSH instead of exposing it publicly.
ssh mesh.example.com 'sudo orange mesh status'
Rotate the admin credential
sudo orange admin token generate \
--output-file /var/lib/orange/admin.token.next
# Put the printed token_hash in [admin.auth], then:
sudo systemctl reload orange
sudo orange mesh status \
--admin-token-file /var/lib/orange/admin.token.next
sudo mv /var/lib/orange/admin.token.next \
/var/lib/orange/admin.token