Fixing Pangolin Newt Failing to Start Under Systemd on Enterprise Linux (SELinux)

From WireGuard and NGINX to Pangolin

I route the traffic of a number of on-prem, self-hosted services through a VPS on one of the hyperscalers’ cloud to benefit from their better peering than what my ISP can offer.

After a stretch of testing, I recently decided to migrate the bulk of them away from a custom setup - NGINX reverse proxy stitched together with WireGuard tunnels - over to Pangolin. Pangolin does essentially the same thing, but as a cohesive integrated platform built on a Traefik and WireGuard stack. Less glue, more sleep.

Across my infrastructure, containers are the default installation method for everything. When a service lives on Kubernetes, I run newt - the Pangolin tunnel client - as a sidecar, and that works beautifully. No complaints there.

But I still have a handful of services running the traditional way: native packages, directly on VMs. For those, the obvious choice is a systemd service - something that survives reboots without thinking about it. Pangolin’s own documentation covers exactly this:

https://docs.pangolin.net/manage/sites/install-site#systemd-service

What the documentation doesn’t mention, however, is what happens when you run this on Enterprise Linux with SELinux in enforcing mode.

The Problem: 203/EXEC

On Enterprise Linux - Oracle Linux, Red Hat, AlmaLinux, take your pick - SELinux is enabled and in enforcing mode by default. That’s the correct and expected configuration. You shouldn’t have it any other way.

When you follow Pangolin’s documentation and copy the newt binary to /usr/local/bin/newt, the file lands with the wrong SELinux security context (label). Specifically, it inherits a label from wherever the file came from - often something like user_tmp_t - rather than the bin_t label that executables in /usr/local/bin are supposed to carry.

This matters because systemd is constrained by SELinux policy. It is only allowed to execute files carrying the appropriate context. With the wrong label on newt, systemd refuses to launch it and bails out early with exit code 203/EXEC:

$ systemctl status newt
โ— newt.service - Newt
     Loaded: loaded (/etc/systemd/system/newt.service; enabled; preset: disabled)
     Active: activating (auto-restart) (Result: exit-code) since Sun 2026-05-03 23:05:58 +08; 1s ago
    Process: 539651 ExecStart=/usr/local/bin/newt (code=exited, status=203/EXEC)
   Main PID: 539651 (code=exited, status=203/EXEC)
        CPU: 7ms

What makes this particularly tricky to diagnose is that running the binary manually from a shell still works fine. Shells operate in a more permissive SELinux domain than systemd, so the execution goes through without issue. You might convince yourself the binary is fine, the service file is correct, permissions look right - and you’d be right on all counts. The culprit is the label, not any of those things.

The Fix: Relabel the Binary

The solution is a single command: restorecon. It resets the file’s SELinux context to what the policy expects for its location - in this case, bin_t for anything living under /usr/local/bin.

As root:

restorecon -v /usr/local/bin/newt
systemctl daemon-reload
systemctl enable --now newt

The output of restorecon will confirm the relabeling, and systemctl status should then show the service running cleanly:

$ restorecon -v /usr/local/bin/newt
Relabeled /usr/local/bin/newt from unconfined_u:object_r:user_tmp_t:s0 to unconfined_u:object_r:bin_t:s0

$ systemctl status newt
โ— newt.service - Newt
     Loaded: loaded (/etc/systemd/system/newt.service; enabled; preset: disabled)
     Active: active (running) since Sun 2026-05-03 23:12:56 +08; 30s ago
   Main PID: 542268 (newt)
      Tasks: 9 (limit: 23061)
     Memory: 15.4M (peak: 15.8M)
        CPU: 293ms
     CGroup: /system.slice/newt.service
             โ””โ”€542268 /usr/local/bin/newt

May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Newt version 1.12.3
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Server version: 1.18.2
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Websocket connected
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Connecting to endpoint: pangolin.example.com
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Tunnel connection to server established successfully!
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Starting monitoring for target 5 (127.0.0.1:80)
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Started tcp proxy to 127.0.0.1:80
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Starting health check monitoring for target 5 (127.0.0.1:80)
May 03 23:12:56 my-vm newt[542268]: INFO: 2026/05/03 23:12:56 Target 5 initial status: healthy
May 03 23:12:58 my-vm newt[542268]: INFO: 2026/05/03 23:12:58 Client connectivity setup. Ready to accept connections from clients!

Tunnel is up, service is healthy, survives reboots. That’s it.

Worth Remembering

203/EXEC under systemd on Enterprise Linux should always make you think SELinux first, especially when the binary runs fine by hand.

Cheers!