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.

Control
Identity and policy
Authenticates devices, assigns stable virtual IPs, and distributes the peer map and access policy.
Relay
Encrypted fallback
Forwards ciphertext only when NAT or network policy prevents a direct peer-to-peer path.
Recommended scale
Up to 100 devices
A single server is intended for private networks of approximately one hundred devices or fewer.

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.
Prefer a domain name
The public endpoint is embedded into every enrolled device. With a domain, a future migration only requires a DNS change. With a raw IP address, moving the server requires new join credentials for every device.

Open the required ports

PortProtocolPurpose
8088TCPRequired. Device enrollment and control connection.
51820TCPRequired. Relay data path.
51821UDPStrongly recommended. Fast relay path. Without it, traffic falls back to TCP and large transfers are noticeably slower.
9898TCPAdmin 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 minutes

Download the archive, install the binary at a permanent path, initialize server state, and register the system service.

Linux shelldownload and install
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
Use a permanent binary path
Do not run the server from a download directory. The service records the binary's absolute path; moving it later prevents startup. If you keep the name 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

Linux shellreadiness
sudo orange mesh status
Expected outputfirst run
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.

OutputWhy it is normal on a new server
Peers: 0No device has enrolled yet.
Relay hub: sessions=0No enrolled peer is using the relay.
Direct path: disabledThe server is not an edge device and does not participate in peer-to-peer paths.
static_bootstrap_tokens_presentThe bootstrap credential still exists. Remove it in the next step.
Linux shelllistening sockets
sudo ss -lntup | grep orange

Confirm that 8088/tcp, 51820/tcp, 51821/udp, and loopback-only 9898/tcp are listening.

Remove the bootstrap credential

Required after initialization
The bootstrap credential is stored in plaintext and cannot be revoked online. Delete the entire [[mesh.control.join_tokens]] block as soon as the server passes its readiness check.
Linux shellremove bootstrap access
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

Linux shellone device
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.

Treat join files as secrets
Anyone holding an unused join file can enter your network. Transfer it through SSH/SCP, a password manager, or another secure channel. Never place it in chat, a ticket, or a Git repository.
OptionDefaultPurpose
--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>1Number of successful enrollments allowed.
--reusableNoPermits reuse, but does not raise the use count by itself. Also set --max-uses.
--roles edge,gatewayNormal device (edge)Grants gateway permission so a Linux device can publish a LAN behind it.
Linux shellbatch enrollment
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.

Linux shellconfirm enrollment
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

OperationCommand
Server statussudo orange mesh status
Enrolled devicessudo orange mesh nodes
Issue a join filesudo orange mesh issue-join --label <note>
Issued join credentialssudo orange mesh tokens
Revoke an unused credentialsudo orange mesh token revoke <ID>
Disable a lost devicesudo orange mesh node disable <device ID>
Remove a retired devicesudo orange mesh node remove <device ID>
Rename a devicesudo orange mesh node rename <device ID> <new name>
Restart after configuration changessudo systemctl restart orange
Follow logssudo journalctl -u orange -f

A unique prefix of a device ID is sufficient, such as a3f2.

Disable versus remove

ActionUse it whenEffectReversible
disableA device is lost or its credential may be exposed.Disconnects it immediately and persistently rejects reconnection.No enable command exists.
removeA 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.

SectionGuidance
[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.
Restart after changes
Run sudo systemctl restart orange after editing the configuration.

Backup

Linux shellconsistent backup
sudo systemctl stop orange
sudo tar czf orange-backup-$(date +%F).tar.gz -C /var/lib orange
sudo systemctl start orange
Back up the complete state directory
The node identity key and the control state database cannot be reconstructed and must be restored as a pair. Backing up the whole state directory, as above, covers both. Losing either one means rebuilding the mesh and enrolling every device again.

Create the first backup immediately after enrolling your first group of devices.

Migrate to a new server

Linux shellold and new hosts
# 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
Two migration rules
Stop the old server first. Never run both copies at the same time. Restore the state directory to the same absolute path used on the old server—normally /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 endpointAfter migration
Domain namePoint DNS to the new server. Devices reconnect automatically.
Raw IP addressDevices 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

Linux shellfirst checks
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 hintAction
control_store_not_openCheck for a full disk and verify permissions on /var/lib/orange.
control_hub_not_availableInspect certificate errors in the log. Confirm the certificate and key paths exist and the private key is mode 0600.
relay_hub_not_availableCheck whether another process is already using port 51820.

Common symptoms

SymptomCheck
Device cannot reach the serverDNS resolution, 8088/tcp, synchronized system time, and whether the certificate recently changed.
Devices enrolled but cannot reach each other51820/tcp, followed by ACL rules if access control is enabled.
Relay is noticeably slow51821/udp. Without it, large transfers fall back to TCP.
Join credential is invalid or expiredIt was used up, expired, revoked, or belongs to another mesh.
Join role mismatchThe requested device role does not match the credential. A normal credential cannot enroll a gateway.
Admin command returns 401Use the content of admin.token, not the token_hash verifier stored in the configuration.
Admin connection refusedThe service is down, or the admin port changed and the command lacks --admin-port.
Startup configuration errorCheck field spelling. Unknown fields stop startup.
Most common connectivity mistake
A target service listening only on 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

Optional

All 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.

Whitelist mode
As soon as one ACL rule exists, all unmatched traffic is denied. Prepare the complete policy before restarting the server.
TOMLgroups and ACL
[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

Optional

Change 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.

TOMLcustom control and relay
[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

Optional

Enable 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.

TOMLSTUN assistance
[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

Optional

Use bandwidth pacing when the host has ample bandwidth but measured relay throughput remains below the available link capacity.

TOMLUDP relay pacing
[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

Optional

The 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.

Linux shellWebPKI certificate
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
Join credentials must use WebPKI
Add --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

Renewal replaces the certificate but not your copy
Because the key is a copy rather than a link, a renewal updates the certificate while your copy stays behind—and the two no longer match, so Orange fails to start with control_hub_not_available. Refresh the copy and restart in the same deploy hook, and renewal stays uneventful.
/etc/letsencrypt/renewal-hooks/deploy/orange.shchmod +x
#!/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.

Shellremote status
ssh mesh.example.com 'sudo orange mesh status'

Rotate the admin credential

Linux shellcredential rotation
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
Change one thing at a time
Do not combine admin credential rotation with other configuration edits. Other changes require a full restart, which prevents this reload-based rotation procedure from taking effect as intended.