Skip to content

Platform Support

plexd runs on Linux, macOS and Windows. Every release publishes seven binaries: plexd-linux-amd64, plexd-linux-arm64 and plexd-linux-mipsle, plexd-darwin-amd64 and plexd-darwin-arm64, and plexd-windows-amd64.exe and plexd-windows-arm64.exe, each with a Sigstore bundle. Node mode and bridge mode both work on all three.

The tables below say what each operating system does, feature by feature. Every row names the reference page it is derived from, so a cell that looks surprising can be checked against the page that owns it. Install instructions are in the bare-metal, macOS and Windows guides.

Binaries, installation and operation

FeatureLinuxmacOSWindowsSource
Release binariesplexd-linux-{amd64,arm64,mipsle}plexd-darwin-{amd64,arm64}plexd-windows-{amd64,arm64}.exeRelease assets
Tested in CIubuntu-latest, amd64macOS 26, arm64; the amd64 binary is cross-compiled onlyWindows Server 2025, amd64; the arm64 binary is cross-compiled onlyCI workflow
Install script deploy/install.shyesyesno; the install is a manual walkthrough from an elevated PowerShellInstall script
What plexd install registersa systemd unit, written and not enableda launchd daemon com.plexsphere.plexd, loaded at the next bootan SCM service plexd with automatic startplexd install
Daemon privilegesroot; the unit bounds it to CAP_NET_ADMIN and CAP_NET_RAWrootLocalSystem as a service, an elevated Administrator from a consolePackaging
Config, data and runtime directories/etc/plexd, /var/lib/plexd, /var/run/plexd/Library/Application Support/plexd, …/plexd/data, /var/run/plexd%ProgramData%\plexd, …\plexd\data, …\plexd\runPlatform defaults
Daemon logjournald/Library/Logs/plexd/plexd.log, rotated by newsyslogApplication Event Log, source plexdPlatform defaults
plexd logsjournalctl -u plexdtails the log file, 100 linesGet-WinEvent through PowerShell; --follow is refusedplexd logs
service.upgradeyesyesyes; the running image is renamed to plexd.exe.old firstRemote actions
Kubernetes DaemonSet, cloud-init, OpenWRTyesnonoKubernetes, cloud-init

Data plane

FeatureLinuxmacOSWindowsSource
WireGuard meshthe kernel module, through netlinkwireguard-go on a utun device; the kernel names it utunNwireguard-go on a Wintun adapter; wintun.dll is embedded in the binaryUserspace backend
NAT traversal (STUN), peer endpoint exchange, NAT relayyesyesyesNAT traversal
Policy enforcementnftables, forward hook onlya pf anchor; also governs traffic to the node itself, and only TCP keeps stateWFP filters; also govern traffic to the node itself, permits are soft, and a port-scoped allow reaches no forward filterWhat the rules govern
Secure access tunneling (SSH, Kubernetes API proxy)yesyesyesSecure access tunneling

Bridge mode

FeatureLinuxmacOSWindowsSource
Routing and IPv4 forwardingnetlink; a per-interface sysctlroute(8); one global sysctl, restored on teardownthe IP Helper API; a per-interface flag, restored on teardownForwarding
NAT masqueradenftablesa nat rule in the pf anchora WinNAT object scoped to the mesh prefix; user-access and site-to-site sources are not translatedNAT
User access (WireGuard interface)netlinkutunWintunAccessController
Site-to-site VPN (WireGuard tunnels)netlinka utun carrying the mesh IP as a /32WintunVPNController
User-access providers (Tailscale, Netbird) and site-to-site providers (OpenVPN, IPsec)the provider binary is invoked by name and must be on PATH; no e2e suite exercises one on any platformsamesameVPN providers, tunnel providers
Public ingress, ACME, SNI routingyesyesyesPublic ingress

bridge.access_interface carries the name the platform itself knows: a kernel name on Linux and macOS (eth1, en1), the adapter's friendly name on Windows (Ethernet). See Interface names.

Observability, node API and actions

FeatureLinuxmacOSWindowsSource
System metrics/procsysctl, Mach and the routing socket; best-effort per sourcekernel32 and the IP Helper API; best-effort per sourceDarwinSystemReader
Load averageyesyesno, Windows has none; the field is 0WindowsSystemReader
Log forwarding, daemon sourcejournaldthe launchd log filethe Event Log, provider plexdDaemonLogSource
Log forwarding, file_patternsyesyesyesFileSource
Audit forwardingthe daemon's own start event only; the auditd and Kubernetes audit sources are built but not wired into plexd upsamesameAudit forwarding
Local node API endpointUnix socket /var/run/plexd/api.sockUnix socket /var/run/plexd/api.socknamed pipe \\.\pipe\plexdNode API
Secret-route authorizationSO_PEERCRED; root or a member of plexd-secretsLOCAL_PEERCRED; root or a member of plexd-secrets, created by hand with dsclthe client's process token; an elevated Administrator or LocalSystemLocal peer authorization
Built-in actionsall 11all 1110 of 11; service.reload_config fails, Windows has no reload signalBuilt-in actions
Hook scriptsyesyesno; discovery needs the executable bit, which Windows does not report on a regular fileDiscoverHooks

Known limitations

  • Userspace WireGuard on macOS and Windows. Both run wireguard-go inside the plexd process where Linux uses the kernel module. plexd carries no throughput measurement for either path, so the cost is not quantified here.
  • Windows Defender Firewall may drop inbound handshakes. plexd's own WFP permits are soft and cannot open a port the host firewall closes. The Windows guide adds the inbound rule for the listen port.
  • pf keeps state for TCP only. A stateful rule covering every protocol would keep an inbound-initiated UDP or ICMP flow alive after its rule turned into a deny.
  • A port-scoped allow reaches no WFP forward filter. The traffic it covered falls through to the rules below it, at worst the default deny. Each apply logs how many rules that affects.
  • WinNAT translates mesh-sourced traffic only. It is scoped by source prefix rather than by outgoing interface, so a user-access or site-to-site source is not translated on Windows.
  • Only IPv4 forwarding is toggled, on every platform. An IPv6 subnet in access_subnets still gets its route.
  • launchd has no start limit. A daemon that exits on a configuration error restarts every five seconds until an operator boots it out.
  • launchd has no environment file. PLEXD_* overrides on macOS go into config.yaml, or into an EnvironmentVariables dict an operator adds to the plist by hand.
  • The SCM restarts indefinitely. Its recovery actions retry every five seconds and the last action applies to every later failure, so a misconfigured Windows service also restarts until an operator intervenes.
  • macOS resolves system paths only. There is no per-user fallback under ~/Library, because the CLI resolves the node API socket without knowing who started the daemon. An unprivileged run sets --config and data_dir itself.
  • wg show needs the Windows service. A plexd started from a console owns its UAPI pipe, and wgctrl requires a LocalSystem-owned one, so it finds no device for a console-started daemon.
  • The Event Log provider name is not an identity. Any local user can write events under provider plexd, and this node forwards them indistinguishably from the service's own records.
  • No hook scripts and no service.reload_config on Windows. Hooks are discovered by their executable bit and run as #!/bin/sh scripts; Windows has neither. Restart the service instead of reloading it.
  • plexd logs --follow is refused on Windows. Get-WinEvent reads a channel and returns; the Event Log's live feed has no command-line form.

Keeping this page current

Every row above names the reference page it is derived from. A pull request that adds or removes a _linux.go, _darwin.go or _windows.go implementation, or changes what one of them does, updates the row in the same pull request. Nothing checks this automatically: the docs build on pull requests catches a dead link and nothing else.

See Also