$linuxjunkies
>

Install Node.js with nvm on Linux

Install nvm on Linux to manage multiple Node.js versions per project, set defaults, pin versions with .nvmrc, and choose the right approach for servers and CI.

BeginnerUbuntuDebianFedoraArch7 min readUpdated June 7, 2026

Before you start

  • curl or wget installed on the system
  • bash or zsh as your login shell
  • A non-root user account (nvm must not be installed as root)

Node.js version management is a real problem in production. Distro packages lag months behind upstream, and a project pinned to Node 18 will fight with one that needs Node 22. nvm (Node Version Manager) solves this by letting you install and switch between multiple Node.js versions per user, with no sudo required for day-to-day work. This guide covers installing nvm, managing versions, making a version the persistent default, and the key decisions around system-wide or deploy-time usage.

Prerequisites

  • curl or wget installed
  • bash, zsh, or fish as your shell (nvm supports all three)
  • Basic terminal familiarity

Install nvm

nvm is a shell function, not a binary. The installer script clones the nvm repo into ~/.nvm and adds a loader snippet to your shell config. Always check the nvm releases page for the latest version number before running — the version below may not be current.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

Or with wget if curl is unavailable:

wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

The script appends the following block to ~/.bashrc (or ~/.zshrc if you use zsh). You can verify it landed correctly:

tail -5 ~/.bashrc

Reload your shell without logging out:

source ~/.bashrc

Confirm nvm loaded:

nvm --version
# 0.39.7

If you get command not found, open a new terminal tab — some distros require a fresh session for ~/.bashrc changes to apply.

fish shell users

nvm is bash/zsh-native. For fish, install the community wrapper fisher and then jorgebucaran/nvm.fish, which is a separate but compatible implementation.

Install Node.js Versions

Install the current LTS release

nvm install --lts

Install a specific version

nvm install 20
nvm install 18.20.2

nvm accepts major version numbers (20), full semver (18.20.2), and aliases like lts/iron. When you install a version it becomes active for the current shell automatically.

List available and installed versions

# Versions you have installed
nvm ls

# All versions available to install (long list)
nvm ls-remote

# Filter to LTS lines only
nvm ls-remote --lts

Switch Between Versions

Switching is per-shell session by default:

nvm use 20
nvm use 18
nvm use --lts

Confirm the active version:

node --version
# v20.14.0  (output will vary)

Set a persistent default

Without a default, every new terminal starts with no Node active. Set one with the default alias:

nvm alias default 20

Now every new shell session will automatically activate Node 20. Check all your aliases:

nvm alias

Per-project version pinning with .nvmrc

Drop an .nvmrc file in a project root to record the required version:

echo "20" > .nvmrc

Anyone (or any CI job) with nvm can then run:

nvm use
# Found '/home/alice/myproject/.nvmrc' with version <20>
# Now using node v20.14.0

To make this automatic on directory change, add the following to ~/.bashrc after the nvm loader block:

cdnvm() {
  command cd "$@" || return
  if [[ -f .nvmrc ]]; then
    nvm use
  fi
}
alias cd=cdnvm

npm and Global Packages

Each Node version installed by nvm has its own isolated node_modules for global packages. Installing a global tool under Node 20 will not be visible when you switch to Node 18. This is intentional and avoids version conflicts, but means you need to reinstall globals when switching. nvm can migrate them:

# Install Node 22 and bring globals from your current version
nvm install 22 --reinstall-packages-from=current

System-Wide Installation (Multi-User / Server)

nvm is intentionally per-user. For servers where multiple users or process managers (systemd, PM2) need a shared, stable Node binary, a system-wide install from NodeSource or the distro's own repo is often the better choice.

Debian / Ubuntu — NodeSource

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

Fedora / RHEL / Rocky

sudo dnf module enable nodejs:20 -y
sudo dnf install nodejs -y

Arch Linux

sudo pacman -S nodejs npm

A system-wide install puts node in /usr/bin, which is accessible to all users and to systemd service units without any shell sourcing tricks.

Using nvm in a systemd service

If you must use an nvm-managed binary in a systemd unit (not recommended for production, but common during development), explicitly set the path. Find the binary location first:

nvm which 20
# /home/deploy/.nvm/versions/node/v20.14.0/bin/node

Then reference that absolute path in your unit file's ExecStart. Do not rely on nvm use inside a systemd ExecStart — systemd does not source user shell configs.

Deploy Considerations

  • CI/CD pipelines: Most CI platforms (GitHub Actions, GitLab CI) have a first-class actions/setup-node or built-in Node selectors — prefer those over installing nvm inside the runner.
  • Docker: Use the official node:20-alpine or node:20-slim images instead of layering nvm into a Dockerfile. It adds complexity and image size for no benefit.
  • PM2 / process managers: If running Node apps via PM2 under a dedicated system user, pair PM2 with the system-wide NodeSource install so the service survives reboots and user session changes.
  • Version drift: Commit .nvmrc to version control and enforce it in your CI pipeline with nvm use + a version check to prevent silent version drift between developer machines.

Uninstalling a Version or nvm Itself

# Remove a single Node version
nvm uninstall 18

# Remove nvm entirely
nvm deactivate
rm -rf ~/.nvm
# Then remove the loader lines from ~/.bashrc or ~/.zshrc manually

Verification

nvm --version
node --version
npm --version

# Quick sanity check
node -e "console.log('Node is working, version: ' + process.version)"

Troubleshooting

nvm: command not found after install

The installer script could not write to your shell config, or you have a custom $HOME. Check that ~/.nvm/nvm.sh exists, then manually add the loader to your shell config:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

node not found in scripts or cron

Cron and non-interactive shells do not source ~/.bashrc. Either use the absolute path from nvm which <version>, or use a system-wide install for any automated workloads.

SSL or certificate errors during nvm install

On minimal server images, ca-certificates may be missing. Install it first:

# Debian/Ubuntu
sudo apt install -y ca-certificates curl

# Fedora/RHEL
sudo dnf install -y ca-certificates curl
tested on:Ubuntu 24.04Debian 12Fedora 40Arch rolling

Frequently asked questions

Can I use nvm with sudo or for system-wide Node installs?
nvm is per-user by design and should never be used with sudo. For system-wide installs accessible to all users and service accounts, use NodeSource packages, your distro's package manager, or the official Node binaries.
Why does 'node' not work inside my cron job or systemd service even after installing via nvm?
Cron and systemd do not source user shell configs like ~/.bashrc, so the nvm function and PATH modifications never load. Use the absolute path returned by 'nvm which <version>', or use a system-wide Node install for automated workloads.
Do global npm packages carry over when I switch Node versions?
No — each Node version managed by nvm has its own isolated global package store. Use 'nvm install <version> --reinstall-packages-from=current' to migrate globals when installing a new version.
Should I use nvm inside a Docker container?
No. Use the official Node Docker images (e.g. node:20-alpine) instead. Adding nvm to a Dockerfile increases image complexity and build time without any practical benefit.
How do I update nvm itself to a newer version?
Re-run the install script with the new version tag — it will update the files in ~/.nvm in place. Then source your shell config to reload the updated function.

Related guides