CLI tool
nvm-sh/nvm avatar
nvm-sh/nvm

nvm's install URL is pinned to a tag, and its shell matrix leaves ksh untested

nvm installs and switches between Node.js versions from a POSIX shell, with per-project version files and aliases.

95,216 stars10,478 forksShellMIT

At a glance

What is it?
nvm is a shell version manager for Node.js that is installed per-user and sourced per-shell, which makes its own instructions unusual: the install URL carries a version tag, the profile snippet reads XDG_CONFIG_HOME rather than assuming ~/.nvm, and a non-interactive shell gets no nvm at all. The test matrix covers sh, bash, dash, and zsh while the documentation also claims ksh.
Who is it for?
nvm suits a developer who runs several Node versions on one machine and is comfortable that a version manager is sourced shell code rather than a binary. It does not suit anyone who needs the same behaviour in a non-interactive shell without extra work, or who is on ksh and wants the project's own test suite behind the claim.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The install URL carries the version tag, so a copy-pasted command freezes it

The documented install is a pipe into bash, and the URL in it is not master:

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

A wget variant is given alongside it with the same tag. That detail is the whole upgrade story. Because the tag is in the path, the command you copy installs exactly the release whose number is written there, and updating means running the same pipeline with a different tag. The most recent tags are v0.40.8 dated 2026-09-21, v0.40.7 dated 2026-08-18, and v0.40.6 dated 2026-07-15, so a tutorial written months ago still works and still installs what it always installed.

The script itself clones the nvm repository to ~/.nvm and attempts to add source lines to the correct profile file, which the README names as ~/.bashrc, ~/.bash_profile, ~/.zshrc, or ~/.profile. The word attempts is doing real work. If it edits the wrong file, the fix is to set $PROFILE to the right path and rerun the script, which means a first install can appear to succeed and leave you with no nvm in your next shell until you go looking.

NVM_DIR follows XDG_CONFIG_HOME, so ~/.nvm is not always where nvm lands

The two lines the installer adds to your profile are these:

code
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Read the first line and the install location stops being fixed. If $XDG_CONFIG_HOME is set, nvm's files go to $XDG_CONFIG_HOME/nvm. If it is not set, they go to $HOME/.nvm. The README states the first half of that rule directly: if the environment variable $XDG_CONFIG_HOME is present, it will place the nvm files there.

The consequence is that a hardcoded path is wrong on somebody's machine. A CI image that sets XDG_CONFIG_HOME, a dotfiles setup, or a colleague following a Linux distribution's guide can all end up with nvm somewhere other than the home directory, and any script, Dockerfile, or runbook that assumes ~/.nvm points at nothing. One more constraint is called out in the same breath: ensure that the NVM_DIR does not contain a trailing slash. A trailing slash turns a path prefix into a doubled separator and the source line quietly stops matching.

nvm is sourced shell code, so a non-interactive shell never gets it

The About section says nvm is designed to be installed per-user, and invoked per-shell. The intro shows what that produces:

code
$ nvm install 24
Now using node v24.14.0 (npm v11.9.0)
$ nvm use 20
Now using node v20.20.1 (npm v10.8.2)

Each of those calls changes the version in the current shell only. Nothing is installed on your PATH, because there is no nvm binary; there is nvm.sh, which is sourced, and the sourcing line is the thing the installer wrote into your profile. That is also why the README offers a --no-use variant, which loads nvm without auto-using the default version.

The failure mode follows directly. A profile file is only read by an interactive login shell, and the Docker section says so plainly: when invoking bash as a non-interactive shell, like in a Docker container, none of the regular profile files are sourced. So a Makefile recipe, a CI step, or a container build that assumes your terminal's setup will find no nvm and no node. The fix is to source nvm.sh inside the non-interactive shell yourself, and the README does not pretend otherwise.

The test matrix runs four shells, and ksh is commented out with an issue number

The Makefile defines what gets tested like this:

code
SHELLS := sh bash dash zsh # ksh (#574)

The trailing comment is a disabled target and a bug reference. So the suite runs against sh, bash, dash, and zsh, and does not run against ksh, which the About section names among the POSIX shells nvm works on: sh, dash, ksh, zsh, and bash.

That gap matters more than it looks. The claim that nvm works on ksh is documented, but the project's own regression suite does not exercise it, and the exclusion carries an open issue number rather than a note saying ksh support was dropped. A reader on ksh is in the one place where the documentation and the test matrix disagree.

There is a second sharp edge in how the suite is invoked. The test script derives your shell from the parent process:

code
shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make test-$shell

If you launch the tests from a wrapper, an IDE task runner, or a process with an unusual command name, the derived name is not in the list and make has no such target.

A version bump has to touch four files, one of which is the README

The Makefile keeps a list of what changes when the version increments:

code
VERSIONED_FILES := nvm.sh install.sh README.md package.json

Four files, and the interesting one is the third. The README is not just prose in this project. It embeds the version in the install URLs, in both the curl and the wget command, and in the one-line PROFILE=/dev/null example. A release therefore has to move the number in the script, the number in the manifest, and the number in the documentation, and the Makefile naming README.md among the versioned files is the project admitting that these have to stay in step.

For a reader, the practical rule is simple. The tag in your install command is the version you got, and it is worth checking against the tag you think you are running, because the two can drift apart quietly and nothing in a normal session tells you. The repository also ships release-notes.sh, update_test_mocks.sh, and a ROADMAP.md, so the release process is scripted rather than improvised, which reduces the chance of drift without eliminating the possibility. The last push was 2026-09-28 and the newest tag is v0.40.8 from 2026-09-21, so trunk is already ahead of the latest release.

The Dockerfile is a 1.2 GB test bench that repoints apt at one university mirror

The repository's Dockerfile says what it is in its own first comment lines: this Dockerfile is for building nvm development environment only, not for any distribution or production usage. It also warns that it will use about 1.2 GB of disk space and about 15 minutes to build, depending on your hardware.

The image is FROM ubuntu:22.04 and installs the usual developer surface, coreutils, util-linux, file, openssl, libssl-dev, locales, ca-certificates, ssh, wget, patch, sudo, htop, dstat, vim, tmux, curl, and git, plus a pinned SHELLCHECK_VERSION of 0.7.0. Before that it rewrites the package sources, replacing archive.ubuntu.com and security.ubuntu.com with ubuntu.cs.utah.edu in /etc/apt/sources.list.

Two consequences. A build of this image depends on that single mirror being reachable, which is a different failure mode from a mirror rotation you did not choose, and a CI system that cannot reach it fails at the apt step rather than at anything to do with nvm. And because the file rules itself out for distribution, there is no container in this repository you can pull for production. What you get is a bench for running the shell matrix, not a deployable artifact.

There is no nvm uninstall, and the profile line it added may be in a file you forgot

The table of contents has a section titled Uninstalling / Removal, and it contains exactly one subsection, Manual Uninstall. There is no uninstall command anywhere in the documented interface. Removal is a matter of undoing two things by hand: the directory, which is ~/.nvm or $XDG_CONFIG_HOME/nvm depending on your environment, and the two lines the installer wrote into a profile file.

The second part is the awkward one, and it is awkward because of how the install works. The script attempts to pick the correct profile file from ~/.bashrc, ~/.bash_profile, ~/.zshrc, and ~/.profile, and the documented remedy for a wrong choice is to set $PROFILE and rerun the script. That remedy fixes the next install. It does not tell you which files an earlier install already touched, so a machine where nvm was installed more than once may have the source line in several files.

Leave one behind and every new shell tries to source a path that may no longer exist. The guard in the snippet, the test for a non-empty nvm.sh before sourcing it, is what keeps that quiet, which is exactly why the failure is easy to miss. Check all four profile files, not just the one your current shell reads.

Editorial conclusion

nvm suits a developer who runs several Node versions on one machine and is comfortable that a version manager is sourced shell code rather than a binary. It does not suit anyone who needs the same behaviour in a non-interactive shell without extra work, or who is on ksh and wants the project's own test suite behind the claim. Before relying on it, check the version tag in your install command against the version you think you have, find out whether XDG_CONFIG_HOME moves your NVM_DIR, and make sure your CI steps source nvm.sh rather than assuming a profile file did it.

Frequently asked questions

What does nvm stand for?

Node Version Manager. The package.json description calls it a simple bash script to manage multiple active node.js versions, and the README describes it as a version manager for node.js designed to be installed per-user and invoked per-shell.

Is nvm safe to use?

It is a shell script piped into your shell. The documented install runs install.sh from a version-pinned GitHub raw URL, and the script clones the repository into your home directory and attempts to add source lines to one of your profile files. The download itself uses git, curl, or wget, whichever is available.

How do I remove nvm?

By hand. The table of contents has an Uninstalling / Removal section whose only subsection is Manual Uninstall, and there is no nvm uninstall command. That means removing the nvm directory and undoing the source line the installer added to your shell profile.

Is FNM better than nvm?

The README does not compare nvm against any alternative. It states that nvm works on any POSIX-compliant shell, naming sh, dash, ksh, zsh, and bash, on unix, macOS, and Windows WSL, with separate troubleshooting sections for macOS, WSL, and Alpine Linux versions 3.13 and 3.5 through 3.12.

nvm vs nvm sh

nvm-sh is the GitHub organisation and nvm is the repository inside it, with the README titled Node Version Manager. The distribution is shell code rather than a binary: nvm.sh is the file you source, with install.sh, nvm-exec, and a bash_completion file at the top level.

Official sources

  1. Official README
  2. Project repository
  3. Release notes
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nvm-sh-nvm.svg)](https://hysenlabs.com/projects/nvm-sh-nvm)
Community notes

Community notes