SparkBox/Guides/VPN unhealthy

Your VPN Container Shows "Unhealthy" and Won't Stop Restarting

You open your dashboard and the VPN card is red: unhealthy. Check the container list and the status keeps flipping — Restarting (137), then up for a second, then down again. Almost always this is a restart loop, and the number in the status line is the single clue that tells you whether the VPN ran out of memory or crashed on startup.

Portainer container management on a SparkBox server
Portainer container management on a SparkBox server

Rather not read exit codes by hand? SparkBox now parses the restart-loop status for you and shows an out-of-memory vs. crash breakdown right on the VPN card, so you know which fix to apply. See the walkthrough →

The 10-second version: The VPN is exit-looping. Run docker ps and read the number in the STATUS column. 137 means it was killed for memory — raise its memory limit. Any other non-zero code means it crashed — check the logs for a config or credentials error.

What "unhealthy" actually means here

A health check is a small test your container runtime runs on a schedule to decide whether a service is really working, not just running. When that test fails repeatedly, the runtime marks the container unhealthy and dashboards turn the card red.

For a VPN, the health check almost always fails for one reason: the container isn't staying up. It starts, exits, and the runtime restarts it — over and over. That's a restart loop. Each time it exits, it produces an exit code: a number the process returns to say how it stopped. Zero means a clean exit; any other number means something went wrong, and the specific number narrows down what.

The frustrating part is that a raw "unhealthy" label hides all of this. Two completely different problems — running out of memory, and crashing on a bad config — look identical from the outside. You have to read the exit code to tell them apart.

Step 1: Read the exit code

The status line your runtime prints already contains the answer. List your containers:

docker ps -a

Look at the STATUS column for the VPN container. In a restart loop you'll see one of these shapes:

Restarting (137) 4 seconds ago
Exited (1) 12 seconds ago

The number in parentheses is the exit code. That single digit — captured by the pattern (?:Exited|Restarting) \((\d+)\) — is what separates a memory kill from a crash. This is exactly the fix we shipped in SparkBox's own monitoring: the scanner was reporting raw restart-loop exit codes to fleet telemetry without categorising them, so every unhealthy VPN looked the same. Parsing the code out lets the VPN card show an out-of-memory-vs-crash breakdown instead of a generic red dot. (For the record: 205/205 tests pass on that parser now.)

Gotcha: If STATUS just says Restarting with no number yet, wait a few seconds and run the command again — you need to catch it in the Exited state to see the code. You can also read it directly with docker inspect --format '{{.State.ExitCode}}' <container-name>.

Step 2 (if the code is 137): fix the out-of-memory kill

Exit code 137 means the process received a SIGKILL — a hard, unignorable termination signal. On self-hosted setups this is nearly always the out-of-memory (OOM) killer: the system, or a limit you set, decided the container was using too much memory and killed it. It restarts, climbs back to the same ceiling, and gets killed again. That's your loop.

  1. Check whether you set a memory cap. If your compose file or run command has a mem_limit or --memory value, the VPN may simply be bumping into it. Raise it, or remove it temporarily to confirm that's the cause.
  2. Check total host memory. If the whole machine is low on RAM, the kernel's OOM killer picks a victim — sometimes the VPN. Free up memory by stopping something non-essential, or add swap, or move to a box with more RAM.
  3. Reduce the load. VPN memory use often scales with the number of active connections or the traffic passing through it. If a download client behind the VPN opened hundreds of connections, dialling that back lowers the peak.
  4. Restart and watch. After changing the limit, restart the container and run docker ps a few times over the next minute. The STATUS should settle into Up and eventually Up (healthy).

Note: 137 isn't always OOM — it just means SIGKILL. If memory clearly isn't tight, something else killed the process (a stop timeout, a manual kill). But on home servers, memory is the overwhelming first suspect, so rule it out first.

Step 2 (if the code is anything else): fix the crash

A non-137 code — commonly 1, but it varies — means the VPN process itself decided to quit, usually during startup. This is almost always a configuration or credentials problem, and the logs will say so plainly. Read them:

docker logs --tail 50 <container-name>

Scan the last lines for the reason it bailed. The usual culprits, in rough order of frequency:

Fix whatever the log names, then restart and re-check with docker ps. A crash-loop that stops immediately after a config edit confirms you found it.

Step 3: confirm the apps behind it recover

Many people route other containers — download clients especially — through the VPN so their traffic only ever leaves via the tunnel. While the VPN restart-loops, those containers lose their network path and look stuck or offline too. Once the VPN reports Up (healthy), restart the dependent containers so they re-attach cleanly, and confirm they can reach the internet again.

Why the telemetry mattered: the reason a plain "unhealthy" flag is so unhelpful is that it collapses two fixes into one symptom. Once your monitoring separates OOM (137) from crash (everything else), you stop guessing — you go straight to raising memory or reading logs. That's the whole point of the parsing fix.

Frequently asked

What does it mean when my VPN is unhealthy?

It means the container's health check keeps failing — usually because the VPN is exiting and restarting in a loop instead of staying up. The exit code in the status line tells you why: 137 typically means an out-of-memory kill, while other codes usually mean the process crashed on startup.

How do I read the VPN container's exit code?

Run docker ps -a and look at the STATUS column. You'll see something like Restarting (137) or Exited (1). The number in parentheses is the exit code — the pattern (?:Exited|Restarting) \((\d+)\) is exactly what pulls that number out for a dashboard to categorise.

Why does exit code 137 keep showing on my VPN?

Code 137 means the process was hard-killed by SIGKILL, most often by the out-of-memory killer when it hit a memory ceiling. Raise or remove the container's memory limit, free host RAM, or reduce concurrent connections, and the loop usually stops.

Does an unhealthy VPN break the apps routed through it?

Frequently, yes. Any container that sends its traffic through the VPN loses its network path while the VPN restart-loops, so it appears stuck too. Fixing the exit-loop and restarting those containers restores them.

Skip the exit-code detective work

SparkBox reads the restart-loop status for you and shows an out-of-memory vs. crash breakdown on the VPN card — so you know instantly whether to add memory or fix a config.

Get SparkBox → Or read the media-server walkthrough →

About this guide: Written and tested by the SparkBox team on a UGREEN DXP4800 Plus and a $7/month Hostinger VPS, both running SparkBox 1.6.431. The causes above are the real ones we've diagnosed in d/sparkbox. If something doesn't match, tell us on YouTube.