The PM2 Multi-Daemon Trap That Breaks Your Next Deploy
Start an app under the wrong user and your deploy can never restart it; here is how PM2_HOME ownership actually works.
Here is a failure that has eaten more deploy afternoons than it has any right to. You run pm2 start to bring up your app, it works, the site is live, you celebrate. Two weeks later your deploy script runs pm2 restart myapp and PM2 says the process does not exist. But it is clearly running, the site is up, ps shows the node process right there. PM2 is not lying and it is not broken. You started the app under one user's PM2 daemon and your deploy script is talking to a different one.
PM2 says your running process does not exist because PM2 runs one daemon per user, each with its own process list, and you started the app under a different user than the one your deploy script uses. The fix is a single discipline: always start, save, and restart the app as the same user (never mix in a bare sudo), so one daemon owns the process for its whole life.
This is the PM2 multi-daemon trap, and it comes from one fact that is easy to miss: PM2 is not a single system service. It is a per-user daemon. Every user on the box that runs PM2 gets their own separate daemon, with its own list of processes, its own socket, and its own home directory. A process registered with root's daemon is invisible to www-data's daemon and vice versa. Once you understand that, the confusing symptom becomes obvious, and the fix becomes a single discipline you apply every time.
PM2 is per-user, not global
When you run any pm2 command, PM2 looks for its daemon in the current user's home directory, specifically ~/.pm2, which holds the socket file the client uses to talk to the daemon, plus the process list and logs. If a daemon is not already running for that user, PM2 silently spawns one. So sudo pm2 start app registers the app under root's ~/.pm2. A later pm2 restart app run as www-data looks in /var/www/.pm2, finds a different daemon with an empty process list, and reports that app does not exist.
Both daemons are real. Both are running. They just do not know about each other, because one PM2 instance belongs strictly to the user that created it. There is no global registry. This is the entire bug, and it explains every variant of it: the app you cannot restart, the duplicate process you cannot kill, the pm2 list that shows nothing while the server is clearly serving.
The most common way to fall into it is reaching for sudo. You hit a permissions error starting the app, prefix the command with sudo, it works, and you have just registered the process under root instead of the app user. Now your deploy script, which correctly runs as the app user, cannot see or restart it. The sudo made the immediate command succeed and made every future deploy fail.
PM2_HOME is the lever that controls which daemon
The variable that decides which daemon a PM2 command talks to is PM2_HOME. It points at the directory PM2 uses for its socket, process list, and logs. By default it is the current user's ~/.pm2, but you can set it explicitly, and that is how you make different users share or target the same daemon deliberately.
This matters because the canonical pattern for serving web apps on a shared box is to run them all under one application user (often www-data) with one PM2 home, so there is a single daemon that owns every web process, in a known location. On the servers we run for our portfolio sites, that home is /var/www/.pm2, and every web app's process lives in that one daemon. There are separate root and ops daemons on the same box for ops scripts, but the web apps live in exactly one place, and nothing crosses the streams. Keeping that ownership consistent is part of shipping Next.js updates with zero downtime using PM2, and consolidating it under one application user echoes the cost-side reasoning in self-hosting versus managed cloud and the true cost founders miss.
The rule that keeps this consistent: always be explicit about which user and which PM2 home you are operating under, and never let sudo quietly change it.
Starting an app the way the deploy script can restart it
The whole point is that the user who starts the app and the user the deploy script runs as must be the same, talking to the same PM2 home. PM2 deploy is strict about this: the user who started a process is the one who can restart it, so a mismatch breaks every subsequent deploy.
To start a web app under the application user's daemon, you run PM2 as that user with the correct home, not with bare sudo:
sudo -u www-data PM2_HOME=/var/www/.pm2 pm2 start npm \
--name myapp --cwd /var/www/html/myapp/public_html -- start
sudo -u www-data PM2_HOME=/var/www/.pm2 pm2 save
sudo -u www-data runs the command as the app user rather than as root, so the process is registered in the app user's daemon. Setting PM2_HOME explicitly removes any ambiguity about which daemon that is. And pm2 save persists the process list so it survives a reboot. The critical thing this avoids is bare sudo pm2 start, which would register the process under root and guarantee the deploy script can never find it.
The deploy script then restarts the app as the same user:
sudo -iu www-data pm2 restart myapp --update-env
Because the start and the restart both run as www-data against the same PM2 home, the restart finds the process every time. --update-env makes the restart pick up any changed environment variables, which matters after a deploy that touched configuration. One detail worth knowing: sudo -iu invokes a login shell so the user's groups and PATH refresh, which some app users need; for a nologin user like www-data it still works because the inner pm2 command does not require a usable shell.
Diagnosing it when you are already stuck
If you have already fallen into the trap, the symptom is the giveaway: a running process that PM2 claims does not exist. To untangle it, check which daemons actually exist on the box. Look for .pm2 directories under the home directories of the users involved, root's /root/.pm2, the app user's home, anywhere a sudo pm2 might have spawned one. Run pm2 list as each candidate user (or with each PM2_HOME) and you will find your app registered under one of them.
Once you know which daemon owns the process, the fix is to stop it from the daemon that owns it, then start it correctly under the intended user and home, and pm2 save so the correct registration persists. From then on, every command for that app uses the same user and PM2_HOME, and the deploy script works. The ownership confusion here is a cousin of the Safari-only 520 error that large auth cookies quietly cause: both are production traps invisible until they bite a deploy.
The discipline that prevents it forever
The whole class of problem disappears if you hold one rule: every PM2 command for a given app runs as the same user, against the same PM2_HOME, with no bare sudo. Pick the application user, pick its PM2 home, start the app that way, and make your deploy script use that exact user and home for the restart. The trap only exists because PM2's per-user model is invisible until it bites, and it only bites when start and restart disagree about which daemon they are talking to.
This is the kind of operational detail that does not show up in a tutorial and shows up immediately in production, which is precisely why we bake it into the deploy harness rather than leaving it to be remembered per deploy. It is the same lesson behind the Safari-only 520 error large auth cookies quietly cause: production traps that are invisible until they bite. A single deploy pipeline that knows each app's path, user, and PM2 home runs the start and restart as the same user every time, so the trap cannot happen. This is the heart of making your scripts the source of truth and never fixing production by hand, and it is what lets a deploy script roll itself back when health checks fail restart the right process. That consistency is most of what reliable server administration actually is, the unglamorous discipline of doing the same correct thing the same way every time, so the 2am restart finds the process it is looking for. If your deploys occasionally fail to restart an app that is clearly running, the PM2 daemon ownership mismatch is the first thing to check, and we can help you straighten it out.






