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.
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
curlorwgetinstalledbash,zsh, orfishas 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-nodeor built-in Node selectors — prefer those over installing nvm inside the runner. - Docker: Use the official
node:20-alpineornode:20-slimimages 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
.nvmrcto version control and enforce it in your CI pipeline withnvm 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 curlFrequently 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
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.