Node Version Manager (NVM) is normally installed for a single user, typically under ~/.nvm. This works well for development environments, but it can become problematic on servers where multiple users or services need Node.js.
For example, on an Ubuntu server you may need Node.js for:
- Jenkins CI/CD jobs
- Supervisor-managed applications
www-data- Deployment scripts
- Regular SSH users
A better approach for this scenario is to install NVM in a shared system location such as /opt/nvm.
This article explains how to install NVM system-wide while keeping the installation owned by root.
1. Install Prerequisites
Update the package list and install the required tools:
sudo apt update
sudo apt install -y curl git
2. Create a Shared NVM Directory
Instead of installing NVM under /root/.nvm, create a shared directory:
sudo mkdir -p /opt/nvm
Set the directory ownership and permissions:
sudo chown -R root:root /opt/nvm
sudo chmod -R 755 /opt/nvm
The important part here is that NVM remains owned by root.
Other users should be able to read and execute Node.js, but they should not be able to modify the installation.
3. Install NVM in /opt/nvm
Set the NVM_DIR environment variable:
export NVM_DIR=/opt/nvm
Then install NVM:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | NVM_DIR=/opt/nvm bash
Verify the installation:
ls -la /opt/nvm
You should see files such as:
nvm.sh
bash_completion
versions/
4. Load NVM
NVM is a shell function, so it needs to be loaded before using the nvm command.
Run:
export NVM_DIR=/opt/nvm
source /opt/nvm/nvm.sh
Verify:
nvm --version
5. Install Node.js
Install the required Node.js version.
For example:
nvm install 20
Set it as the default version:
nvm alias default 20
Verify:
node -v
npm -v
Example:
v20.20.2
10.x.x
The actual npm version will depend on the Node.js release.
6. Make NVM Available to Login Shells
Create a global profile file:
sudo nano /etc/profile.d/nvm.sh
Add:
export NVM_DIR="/opt/nvm"
if [ -s "$NVM_DIR/nvm.sh" ]; then
. "$NVM_DIR/nvm.sh"
fi
Set appropriate permissions:
sudo chmod 644 /etc/profile.d/nvm.sh
Now load the configuration:
source /etc/profile.d/nvm.sh
Verify:
nvm --version
node -v
npm -v
New login sessions will automatically load this configuration.
7. Allow Other Users to Execute Node.js
Make sure the shared NVM directory is readable and executable:
sudo chown -R root:root /opt/nvm
sudo chmod -R a+rX /opt/nvm
This allows users such as:
root
jenkins
www-data
to execute the installed Node.js binaries.
It does not give them permission to modify the NVM installation.
8. Verify Node.js as Jenkins
Jenkins jobs usually run in a non-interactive shell.
Therefore, you should not assume that /etc/profile.d/nvm.sh will always be loaded.
Test Node.js explicitly as the Jenkins user:
sudo -u jenkins bash -c '
export NVM_DIR=/opt/nvm
source "$NVM_DIR/nvm.sh"
node -v
npm -v
'
If everything is configured correctly, you should see the installed Node.js and npm versions.
9. Use NVM in Jenkins Jobs
For Jenkins shell scripts, explicitly load NVM:
export NVM_DIR="/opt/nvm"
source "$NVM_DIR/nvm.sh"
node -v
npm -v
For example:
#!/bin/bash
set -e
export NVM_DIR="/opt/nvm"
source "$NVM_DIR/nvm.sh"
echo "Node version:"
node -v
echo "NPM version:"
npm -v
npm install
npm run build
This is more reliable than assuming Jenkins will load a user’s normal shell profile.
10. Using Node.js with Supervisor
Supervisor also does not automatically load NVM.
Therefore, avoid configurations such as:
command=node server.js
because node may not exist in Supervisor’s PATH.
Instead, use the absolute path to the Node.js binary.
First find the installed Node.js path:
source /opt/nvm/nvm.sh
which node
For example:
/opt/nvm/versions/node/v20.20.2/bin/node
Then configure Supervisor:
[program:my-node-app]
directory=/var/www/my-node-app
command=/opt/nvm/versions/node/v20.20.2/bin/node server.js
user=www-data
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/my-node-app.log
stderr_logfile=/var/log/supervisor/my-node-app-error.log
You can also explicitly configure the PATH:
environment=NODE_ENV="production",PATH="/opt/nvm/versions/node/v20.20.2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
A complete example:
[program:my-node-app]
directory=/var/www/my-node-app
command=/opt/nvm/versions/node/v20.20.2/bin/node server.js
user=www-data
environment=NODE_ENV="production",PATH="/opt/nvm/versions/node/v20.20.2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/my-node-app.log
stderr_logfile=/var/log/supervisor/my-node-app-error.log
After changing the Supervisor configuration:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart my-node-app
11. Verify Node.js as www-data
If your Supervisor application runs as www-data, verify that user can execute Node.js:
sudo -u www-data bash -c '
export NVM_DIR=/opt/nvm
source "$NVM_DIR/nvm.sh"
node -v
npm -v
'
If this works, the www-data user can access the shared Node.js installation.
12. Recommended Permissions
The recommended ownership is:
/opt/nvm
└── root:root
with read/execute access for other users.
Check:
ls -ld /opt/nvm
A typical result would be:
drwxr-xr-x root root /opt/nvm
Avoid doing this:
sudo chown -R jenkins:jenkins /opt/nvm
because it makes Jenkins the owner of the system-wide Node.js installation.
Jenkins should generally only need permission to execute Node.js.
13. NVM Architecture
With this configuration, the server has a single shared NVM installation:
Ubuntu Server
│
▼
/opt/nvm
│
┌───────────┴───────────┐
│ │
▼ ▼
Node.js 20.x NVM scripts
│
┌───────┼────────┐
│ │ │
▼ ▼ ▼
Jenkins www-data Users
│ │
▼ ▼
CI/CD Supervisor
Interactive users can load NVM through:
/etc/profile.d/nvm.sh
Jenkins can explicitly load:
source /opt/nvm/nvm.sh
Supervisor can directly execute:
/opt/nvm/versions/node/<version>/bin/node
14. Why Use /opt/nvm Instead of /root/.nvm?
A normal NVM installation looks like:
/root/.nvm
This is appropriate when only the root user needs Node.js.
However, Jenkins and Supervisor are normally running under different users:
root
jenkins
www-data
They cannot reliably use:
/root/.nvm
A shared installation:
/opt/nvm
provides a common Node.js installation while still allowing each service to run under its own Linux user.
15. Important: NVM Is Not Really a System Service
NVM is primarily a shell-based version manager.
Therefore:
nvm use 20
only affects the current shell environment.
It does not globally change the node binary for every process on the server.
This is particularly important for:
- Jenkins
- Supervisor
- systemd
- cron
- deployment scripts
For these services, explicitly configure the Node.js environment or use the absolute path to the Node.js binary.
16. Recommended Setup for Jenkins + Supervisor
For a production Ubuntu server, the following setup is simple and predictable:
NVM
/opt/nvm
Ownership
root:root
Interactive shells
/etc/profile.d/nvm.sh
Jenkins
export NVM_DIR=/opt/nvm
source "$NVM_DIR/nvm.sh"
Supervisor
command=/opt/nvm/versions/node/v20.20.2/bin/node server.js
Security
Do not give Jenkins write access to:
/opt/nvm
Only give it the permissions required to execute Node.js.
Final Verification
Run all of the following:
node -v
sudo -u jenkins bash -c '
export NVM_DIR=/opt/nvm
source "$NVM_DIR/nvm.sh"
node -v
'
sudo -u www-data bash -c '
export NVM_DIR=/opt/nvm
source "$NVM_DIR/nvm.sh"
node -v
'
And finally verify the Supervisor application:
sudo supervisorctl status
If all three users can execute Node.js, you have a proper shared NVM installation suitable for a server running Jenkins + Supervisor + Node.js applications.
