PM2 as systemd — force Node.js version
When PM2 is started by systemd, it does not read ~/.bashrc the way an interactive SSH session does. The node binary comes from PATH in the [Service] unit unless you pin interpreter in the ecosystem file.
If npm / EBADENGINE still sees an old Node while nvm shows a new one in your SSH session, two common causes:
- systemd
PATHstill has/usr/binbefore the nvmbindirectory. - nvm is only loaded from
.bashrc, and non-interactive shells exit.bashrcearly — sosudo -u coaching bash -lc 'node -v'never loads nvm (you see distro/usr/bin/node).
nvm — why sudo … bash -lc shows Node 20 but SSH shows Node 24
Interactive SSH runs interactive bash → .bashrc runs fully → nvm loads.
bash -lc 'cmd' is a login shell but non-interactive. On Ubuntu/Debian, ~/.profile often sources ~/.bashrc. The default .bashrc starts with “if not interactive, return”, so nvm never loads and command -v node is /usr/bin/node (distro).
Check like systemd (non-interactive)
sudo -u coaching -H bash -lc 'command -v node && node -v'
Check with nvm loaded explicitly (matches “what I expect”)
Either interactive:
sudo -u coaching -H bash -lic 'command -v node && node -v'
Or source nvm in one line:
sudo -u coaching -H bash -lc 'export NVM_DIR="$HOME/.nvm" && . "$NVM_DIR/nvm.sh" && command -v node && node -v'
Fix .profile so login + non-interactive shells get nvm (optional)
Edit /home/coaching/.profile: load nvm before the block that sources .bashrc, so even when .bashrc returns early, nvm is already on PATH.
Add above any source ~/.bashrc line:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
Then verify:
sudo -u coaching -H bash -lc 'command -v node && node -v'
You should see …/.nvm/versions/node/v24…/bin (or your default nvm alias default).
1. Path systemd should use (recommended)
Do not rely on nvm inside systemd. Put the exact version bin directory first in the unit PATH:
/home/coaching/.nvm/versions/node/v24.15.0/bin
(Adjust version folder if you nvm install another release.)
Drop-in override
sudo systemctl edit pm2-coaching.service
# use your real unit name from: systemctl list-units '*pm2*'
Example:
[Service]
Environment=PATH=/home/coaching/.nvm/versions/node/v24.15.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
Then:
sudo systemctl daemon-reload
sudo systemctl restart pm2-coaching.service
2. Confirm PM2 and the app
After fixing PATH, confirm under the same conditions systemd uses:
sudo -u coaching -H env PATH=/home/coaching/.nvm/versions/node/v24.15.0/bin:/usr/bin:/bin bash -lc 'node -v && pm2 describe coaching'
Adjust the PATH prefix to match your installed version directory.
3. Bulletproof: interpreter in ecosystem
Pin the binary so pm2 resurrect always uses the right Node:
module.exports = {
apps: [
{
name: 'coaching',
script: 'node_modules/next/dist/bin/next',
args: 'start',
interpreter: '/home/coaching/.nvm/versions/node/v24.15.0/bin/node',
cwd: '/var/www/coaching',
},
],
};
Then pm2 reload ecosystem.config.cjs --update-env and pm2 save.
4. Stale PM2 dump
If pm2 describe coaching still shows an old interpreter:
sudo -u coaching -H bash -lic 'cd /var/www/coaching && pm2 delete coaching && pm2 start ecosystem.config.cjs && pm2 save'
(bash -lic loads nvm so pm2 on PATH is the one you expect.)
Repository example
documentation/examples/systemd/pm2-node-path.conf.example — replace the first PATH segment with your nvm bin directory.