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.