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.