How to Install NVM for Node.js on Ubuntu: Complete Technical Guide
NVM (Node Version Manager) is a POSIX-compliant shell script that installs and manages multiple isolated Node.js runtime environments on a single machine, without requiring root privileges or modifying system-wide paths. Each Node.js version lives in its own directory under `~/.nvm/versions/node/`, giving you complete, conflict-free isolation between projects.
This guide walks through a production-grade NVM installation on Ubuntu (20.04, 22.04, and 24.04), covering not just the basic commands but also shell profile edge cases, `.nvmrc` workflow automation, global package migration, and server-specific pitfalls that most tutorials omit.
Why NVM Instead of the System Package Manager
Installing Node.js via `apt` places a single, system-wide binary at `/usr/bin/node`. Upgrading it affects every application on the host simultaneously. On a shared development machine or a VPS running multiple Node.js projects, this creates fragile, hard-to-reproduce environments.
NVM solves this by installing each Node.js version into a user-space directory and manipulating `PATH` at the shell level. The result is per-user, per-project version control with zero impact on the operating system's package state.
NVM vs. Other Node.js Version Managers
| Feature | NVM | fnm | Volta | n |
|---|
| — | — | — | — | — |
|---|
| Language | Shell (Bash/Zsh) | Rust | Rust | Shell |
|---|
| Speed | Moderate | Very fast | Very fast | Fast |
|---|
| `.nvmrc` support | Yes | Yes | Partial | No |
|---|
| Per-project pinning | Yes | Yes | Yes | No |
|---|
| Windows support | No (WSL only) | Yes | Yes | No |
|---|
| Global package isolation | Yes | Yes | Yes | No |
|---|
| Shell startup overhead | ~70ms | ~5ms | ~5ms | Minimal |
|---|
| Maturity / ecosystem | Highest | High | Medium | High |
|---|
NVM remains the most widely documented and ecosystem-supported option, making it the safest default for teams and server environments where reproducibility matters more than raw startup speed.
Prerequisites
- Ubuntu 20.04, 22.04, or 24.04 (desktop or server)
- A non-root user account with `sudo` privileges
- `curl` or `wget` installed (both are present by default on most Ubuntu images)
- Basic familiarity with Bash or Zsh
To confirm your shell and Ubuntu version before starting:
“`bash
echo $SHELL
lsb_release -a
“`
Step 1: Update the System Package Index
Refresh the APT package lists to ensure any dependencies resolved during the session are current:
“`bash
sudo apt-get update && sudo apt-get upgrade -y
“`
Also confirm that `curl` is available:
“`bash
curl –version || sudo apt-get install -y curl
“`
Step 2: Download and Run the NVM Installation Script
The official NVM installer is hosted on GitHub. It clones the NVM repository into `~/.nvm` and appends the necessary shell initialization block to your profile file.
Option A β Using curl (recommended):
“`bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
“`
Option B β Using wget:
“`bash
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
“`
Always verify the latest release tag on the NVM GitHub releases page before running. Replace `v0.40.1` with the current stable tag if a newer version has been published.
What the installer actually does:
- Clones the NVM repository to `~/.nvm`
- Detects your active shell (`bash`, `zsh`, `ksh`, or `fish`)
- Appends the following initialization block to the appropriate profile file (`~/.bashrc`, `~/.zshrc`, `~/.profile`, or `~/.bash_profile`)
“`bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"
“`
Security note: Piping a remote script directly into `bash` is a common pattern but carries inherent risk. For production servers or dedicated server environments, download the script first, inspect it, then execute:
“`bash
curl -o install_nvm.sh https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh
cat install_nvm.sh # review before running
bash install_nvm.sh
“`
Step 3: Activate NVM in the Current Shell Session
The installer modifies your profile, but those changes only take effect in new shell sessions. To activate NVM immediately without opening a new terminal:
For Bash:
“`bash
source ~/.bashrc
“`
For Zsh:
“`bash
source ~/.zshrc
“`
Manual sourcing (works in any shell):
“`bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
“`
Common Pitfall: Profile File Not Sourced in Non-Interactive Shells
On Ubuntu, `~/.bashrc` is only sourced for interactive non-login shells. If you are connecting via SSH (a login shell), Bash reads `~/.bash_profile` or `~/.profile` instead. If NVM is not found after SSH login, add the sourcing block to `~/.bash_profile`:
“`bash
echo 'source ~/.bashrc' >> ~/.bash_profile
source ~/.bash_profile
“`
This is one of the most frequently encountered issues when configuring NVM on remote servers.
Step 4: Verify the NVM Installation
“`bash
nvm –version
“`
Expected output (version number will vary):
“`
0.40.1
“`
If the command returns `nvm: command not found`, the shell profile was not sourced correctly. Re-run the sourcing command from Step 3 and verify that the initialization block exists in your profile file:
“`bash
grep -n 'NVM_DIR' ~/.bashrc
“`
Step 5: Install Node.js Using NVM
Install the Latest LTS Release (Recommended for Production)
“`bash
nvm install –lts
“`
The `–lts` flag installs the most recent Long-Term Support release, which receives security patches for 30 months. For production workloads on a VPS with cPanel or any server environment, LTS is strongly preferred over the current release.
Install the Absolute Latest Release
“`bash
nvm install node
“`
Install a Specific Version
“`bash
nvm install 20.14.0
“`
List All Available Remote Versions
“`bash
nvm ls-remote
“`
To filter for LTS versions only:
“`bash
nvm ls-remote –lts
“`
Step 6: Verify the Active Node.js and npm Versions
“`bash
node -v
npm -v
“`
Also confirm the binary path to ensure you are not accidentally using a system-wide Node.js installation:
“`bash
which node
Expected: /home/<username>/.nvm/versions/node/v20.14.0/bin/node
“`
Step 7: Manage Multiple Node.js Versions
List All Locally Installed Versions
“`bash
nvm ls
“`
Sample output:
“`
-> v20.14.0
v18.20.3
v16.20.2
default -> lts/* (-> v20.14.0)
node -> stable (-> v20.14.0) (default)
lts/* -> lts/iron (-> v20.14.0)
“`
Switch Between Versions
“`bash
nvm use 18.20.3
“`
This change applies only to the current terminal session. Opening a new terminal reverts to the default alias.
Check Which Version Is Currently Active
“`bash
nvm current
“`
Step 8: Set a Persistent Default Node.js Version
To make a specific version the default for all new shell sessions:
“`bash
nvm alias default 20.14.0
“`
You can also alias to a release line rather than a specific patch version, which automatically tracks updates within that line:
“`bash
nvm alias default lts/*
“`
Step 9: Automate Version Switching with `.nvmrc`
This is one of NVM's most powerful and underused features. Place a `.nvmrc` file in your project root containing the required Node.js version:
“`bash
echo "20.14.0" > /path/to/your/project/.nvmrc
“`
Then, when you `cd` into that directory:
“`bash
nvm use
Found '/path/to/your/project/.nvmrc' with version <20.14.0>
Now using node v20.14.0
“`
Automatic Version Switching on Directory Change
Add the following to your `~/.bashrc` (or `~/.zshrc`) to trigger `nvm use` automatically whenever you enter a directory containing a `.nvmrc` file:
For Bash:
“`bash
cdnvm() {
command cd "$@" || return $?
nvm_path="$(nvm_find_up .nvmrc | command tr -d 'n')"
if [[ ! $nvm_path = *[^[:space:]]* ]]; then
declare default_version
default_version="$(nvm version default)"
if [[ $default_version == "N/A" ]]; then
nvm use default
elif [[ $(nvm current) != "$default_version" ]]; then
nvm use default
fi
elif [[ -r "$nvm_path/.nvmrc" && -r "$nvm_path" ]]; then
declare nvm_version
nvm_version=$(<"$nvm_path/.nvmrc")
declare locally_resolved_nvm_version
locally_resolved_nvm_version="$(nvm ls –no-colors "$nvm_version" | command tail -1 | command tr -d '->*' | command tr -d '[:space:]')"
if [[ "$locally_resolved_nvm_version" == "N/A" ]]; then
nvm install "$nvm_version"
elif [[ $(nvm current) != "$locally_resolved_nvm_version" ]]; then
nvm use "$nvm_version"
fi
fi
}
alias cd='cdnvm'
“`
This pattern is especially valuable in CI/CD pipelines and team environments where multiple developers work on projects with different runtime requirements.
Step 10: Manage Global npm Packages Across Versions
Each Node.js version installed by NVM has its own isolated `node_modules` directory for globally installed packages. This means tools like `pm2`, `yarn`, or `typescript` installed globally under `v18` are not available under `v20`.
Install a Global Package for the Active Version
“`bash
npm install -g yarn
npm install -g pm2
npm install -g typescript
“`
Migrate Global Packages When Installing a New Version
NVM provides a built-in flag to copy all global packages from one version to a new installation:
“`bash
nvm install 20.14.0 –reinstall-packages-from=18.20.3
“`
This is critical when upgrading Node.js versions on a server running persistent processes managed by PM2 or similar tools.
List Globally Installed Packages for the Current Version
“`bash
npm list -g –depth=0
“`
Step 11: Uninstall a Node.js Version
Before uninstalling, switch away from the version you want to remove:
“`bash
nvm use 20.14.0
nvm uninstall 16.20.2
“`
You cannot uninstall the currently active version. Attempting to do so returns an error.
Advanced: Using NVM in Non-Interactive Environments (CI/CD, Cron, Systemd)
NVM relies on shell initialization files that are not sourced in non-interactive shells. This causes `node: command not found` errors in cron jobs, systemd unit files, and some CI environments.
Solution 1: Use the full binary path
“`bash
/home/username/.nvm/versions/node/v20.14.0/bin/node /path/to/app.js
“`
Solution 2: Source NVM explicitly in scripts
“`bash
#!/bin/bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 20.14.0
node /path/to/app.js
“`
Solution 3: Create a symlink for system-wide access (use with caution)
“`bash
sudo ln -s /home/username/.nvm/versions/node/v20.14.0/bin/node /usr/local/bin/node
“`
This approach sacrifices per-user isolation but is sometimes necessary for systemd services running as a dedicated service user.
NVM on Shared Hosting vs. VPS vs. Dedicated Servers
| Environment | NVM Suitability | Notes |
|---|
| — | — | — |
|---|
| Shared hosting | Not supported | No shell access; use platform-provided Node.js |
|---|
| [VPS Hosting](https://alexhost.com/vps/) | Excellent | Full shell access; per-user isolation works perfectly |
|---|
| [Dedicated Servers](https://alexhost.com/dedicated-servers/) | Excellent | Ideal for multi-project environments and CI runners |
|---|
| Docker containers | Partial | Consider using official Node.js Docker images instead |
|---|
| Shared Web Hosting | Not supported | No SSH access in most configurations |
|---|
For teams running Node.js applications alongside other services, a VPS gives you the shell-level control NVM requires without the overhead of managing physical hardware.
Practical Key-Takeaway Checklist
Use this as a deployment and configuration reference:
- Verify shell type (`echo $SHELL`) before installing β Zsh and Bash require different profile files
- Always use `–lts` for production Node.js installations; reserve `node` (latest) for experimental work
- Commit `.nvmrc` to version control so every team member and CI runner uses the identical runtime version
- Use `–reinstall-packages-from` when upgrading Node.js versions to avoid manually reinstalling global tools
- Source NVM explicitly in any non-interactive script (cron, systemd, CI pipelines) β never assume the shell profile has been loaded
- Audit global packages per version with `npm list -g –depth=0` after switching versions to catch missing dependencies early
- Pin exact versions in `.nvmrc` (e.g., `20.14.0`) rather than aliases (e.g., `lts`) for maximum reproducibility in production
- Check `which node` after switching versions to confirm you are not accidentally using a system-installed binary
Frequently Asked Questions
Does NVM require sudo or root access to install Node.js?
No. NVM installs everything under `~/.nvm` in the current user's home directory. No root privileges are needed for installing or switching Node.js versions. This is one of its primary advantages over system-level package managers.
Why does `nvm: command not found` appear after installation?
The NVM initialization block was added to your shell profile, but the profile has not been re-sourced in the current session. Run `source ~/.bashrc` (Bash) or `source ~/.zshrc` (Zsh). If the error persists after opening a new terminal, check that the initialization block was actually written to the correct file using `grep NVM_DIR ~/.bashrc`.
Can multiple users on the same server each have different Node.js versions via NVM?
Yes. Because NVM installs into each user's home directory (`~/.nvm`), every user maintains a completely independent set of Node.js versions and global packages. This is the correct architecture for multi-tenant servers.
What happens to globally installed npm packages when I switch Node.js versions with NVM?
Each Node.js version has its own isolated global package directory. Packages installed globally under one version are not visible to another. Use `nvm install <new-version> –reinstall-packages-from=<old-version>` to migrate them automatically.
Is NVM suitable for running Node.js applications in production on a server?
NVM is well-suited for managing which Node.js version is used, but for process management in production you should pair it with a process manager like PM2 or use systemd unit files. Ensure those non-interactive environments explicitly source NVM or reference the full binary path, as described in the CI/CD section above.
