nvm v0.40.4 is a POSIX-compliant bash shell function for managing multiple concurrent Node.js installations. It is not a daemon, not a binary, and has no server component: the entire tool is a single bash function sourced into the user's shell on login.
1. Architecture Overview
How nvm Works
nvm is implemented as a shell function (not an executable) defined in nvm.sh. Sourcing the file adds the nvm function — and ~120 helper functions — to the current shell's environment. All state is represented as files on disk; there is no background process, no daemon, and no IPC.
Shell Session
└── source ~/.nvm/nvm.sh
├── nvm() — main dispatch function
├── nvm_ls() — local version scanning
├── nvm_ls_remote() — remote index fetch (nodejs.org)
├── nvm_install_binary() / nvm_install_node_source()
└── ...~120 helper functions
Filesystem State
~/.nvm/
├── nvm.sh — main script (sourced by shell)
├── nvm-exec — exec wrapper (executable)
├── bash_completion — tab-completion definitions
├── alias/
│ ├── default — "default" alias file
│ ├── lts/
│ │ ├── * — lts/* alias
│ │ └── iron — lts/iron alias
│ └── <custom> — user-created aliases
├── versions/
│ ├── node/
│ │ ├── v20.11.0/ — Node.js installation
│ │ └── v22.4.0/
│ └── io.js/ — io.js installations (legacy)
└── .cache/
└── bin/ — downloaded binary cache
Note
All aliases are plain text files: the file name is the alias name and the file content is the target version string. This makes aliases trivially auditable and scriptable.
PATH Manipulation
nvm use <version> modifies $PATH by prepending $NVM_DIR/versions/node/<version>/bin and removing any previously prepended nvm path. It does not modify system paths. The effect is scoped to the current shell process.
Version Resolution
nvm resolves version strings through a chain:
- Exact match against installed versions in
$NVM_DIR/versions/ - Alias resolution (reads
$NVM_DIR/alias/<name>) - Implicit alias resolution (
node→ latest installed,lts/*→ latest lts alias) - Partial version expansion (e.g.,
22→ latest installedv22.x.x)
Remote resolution queries $NVM_NODEJS_ORG_MIRROR/index.tab (defaults to https://nodejs.org/dist/index.tab).
2. Component Inventory
| File | Role |
|---|---|
nvm.sh |
Main shell script — defines the nvm function and all helpers |
install.sh |
Installer — clones or downloads nvm, patches shell profiles |
nvm-exec |
Executable wrapper: runs a command with NODE_VERSION in PATH |
bash_completion |
Tab-completion definitions for nvm subcommands and versions |
Makefile |
Test runner — delegates to urchin for fast/slow/install test suites |
Dockerfile |
Container definition for CI test environments |
Internal Functions (key subset)
| Function | Purpose |
|---|---|
nvm_ls() |
Scans $NVM_DIR/versions/ for installed versions |
nvm_ls_remote() |
Fetches and parses index.tab from mirror |
nvm_install_binary() |
Downloads, checksums, and extracts Node binary tarball |
nvm_install_node_source() |
Compiles Node from source |
nvm_get_mirror() |
Returns the correct download mirror URL |
nvm_get_checksum_binary() |
Detects available checksum tool (sha256sum, shasum, openssl, etc.) |
nvm_compute_checksum() / nvm_compare_checksum() |
SHA-256 verification of downloaded artifacts |
nvm_detect_profile() |
Finds the user's shell profile for loader injection |
nvm_find_nvmrc() |
Walks parent directories to locate .nvmrc |
nvm_process_nvmrc() |
Parses .nvmrc content (bare version or key=value pairs) |
nvm_install_latest_npm() |
Upgrades npm to the highest compatible version for the active Node |
nvm_sanitize_auth_header() |
Strips non-alphanumeric chars from NVM_AUTH_HEADER before use |
nvm_get_arch() |
Detects CPU architecture including unofficial (Alpine musl, ARM variants) |
nvm_get_os() |
Detects OS (darwin, linux, freebsd, openbsd, sunos, win) |
nvm_die_on_prefix() |
Aborts if $PREFIX or $NPM_CONFIG_PREFIX would conflict |
3. System Requirements
Runtime Dependencies
| Dependency | Required | Notes |
|---|---|---|
| bash 3.2+ or zsh 5.0+ | Yes | Must be POSIX-compliant; sourced into the shell |
curl or wget |
Yes (for install/download) | Both supported; curl preferred |
git |
For git install method | Optional if using script method |
awk |
Yes | Used for index.tab parsing, version formatting |
grep |
Yes | Used throughout (with GREP_OPTIONS='' to prevent interference) |
sed |
Yes | Path/version string manipulation |
sort |
Yes | Version ordering |
tar |
Yes (for binary installs) | Extracts downloaded tarballs |
sha256sum / shasum / openssl / bssl |
Yes | Checksum verification of downloads |
| C compiler + make | Source-only installs | Not needed for binary installs |
Platform Support
| Platform | Binary | Source | Notes |
|---|---|---|---|
| Linux (x86_64, arm64, armv7) | Yes | Yes | Full support |
| macOS (x86_64, arm64) | Yes | Yes | Requires Xcode CLT for source builds |
| Alpine Linux | Unofficial binary | Yes | musl-based; unofficial binary slug |
| FreeBSD | No | Yes | No official binary; source install only |
| OpenBSD | No | Yes | No official binary; source install only |
| SunOS/SmartOS | Version-dependent | Yes | Binary available for some versions only |
| Windows | No | No | Not supported natively; use WSL |
4. Configuration Reference
Environment Variables
| Variable | Default | Required | Description |
|---|---|---|---|
NVM_DIR |
~/.nvm (or $XDG_CONFIG_HOME/nvm) |
No | nvm installation directory. Trailing slashes are stripped with a warning. |
NVM_NODEJS_ORG_MIRROR |
https://nodejs.org/dist |
No | Download mirror for Node.js binaries and index |
NVM_IOJS_ORG_MIRROR |
https://iojs.org/dist |
No | Download mirror for io.js (legacy) |
NVM_SOURCE |
unset | No | Override source URL for install script |
NVM_INSTALL_GITHUB_REPO |
nvm-sh/nvm |
No | Override GitHub repo for installation. Changing this triggers a security warning in the installer. |
NVM_INSTALL_VERSION |
latest tag | No | Pin the nvm version to install |
NVM_AUTH_HEADER |
unset | No | Authorization header for authenticated mirror downloads. Non-alphanumeric chars are stripped before use. |
NVM_NO_COLORS |
unset | No | Set to --no-colors to suppress ANSI color output globally |
NVM_DEBUG |
unset | No | Set to 1 to enable debug logging in npm upgrade logic |
NVM_INSTALL_THIRD_PARTY_HOOK |
unset | No | Path to a script called instead of the standard binary/source installer |
NVM_CD_FLAGS |
'' (or -q on zsh) |
No | Flags passed to cd internally; set automatically for zsh |
METHOD |
auto | No | Override install method: git or script |
NODE_VERSION |
unset | No | Used by install.sh to auto-install a Node version after nvm itself is installed |
PROFILE |
auto-detected | No | Override shell profile file for installer. Set to /dev/null to prevent any profile modification. |
Important
NVM_DIRmust not have a trailing slash. nvm auto-corrects this and prints a warning, but scripts that setNVM_DIRshould omit trailing slashes to avoid subtle path issues.
The .nvmrc File
A .nvmrc file in a project directory controls which Node.js version nvm selects when no version argument is given. nvm walks up the directory tree to find the nearest one.
Simple format:
v22.4.0
Key-value format (extended):
22.4.0
some-key=some-value
The key-value pairs are read by nvm_process_nvmrc() but currently only the bare version string is acted on by nvm commands. The key node is explicitly reserved and rejected.
Note
Comments in
.nvmrcare supported: anything after#on a line is ignored. Leading/trailing whitespace is stripped. The file must contain exactly one bare version string (after stripping comments).
The default-packages File
$NVM_DIR/default-packages — one npm package name per line — lists packages to install globally whenever nvm install installs a new Node.js version.
# $NVM_DIR/default-packages
typescript
ts-node
prettier
Packages from npm link-ed directories in a source version are also re-linked by nvm reinstall-packages.
Alias Storage
Aliases are stored as plain text files in $NVM_DIR/alias/. The file name is the alias name; the file content is the target (a version string or another alias name). LTS aliases live in $NVM_DIR/alias/lts/.
Reserved (built-in) aliases that cannot be deleted:
node— resolves to latest stablestable— resolves to latest stableunstable— resolves to latest unstable (odd major)iojs— resolves to latest io.jssystem— refers to the system-installed Node.js outside nvm
Alias constraints:
- Alias names may not contain
/or# - Uppercase alias names work on case-insensitive filesystems (macOS HFS+) but fail on Linux (issue #3764)
5. Installation Details
Install Script Flow (install.sh)
nvm_do_install()
├── Check NVM_DIR validity
├── Detect macOS + Xcode requirement
├── Select method (git | script | auto)
│ ├── git: install_nvm_from_git()
│ │ ├── Clone or update git repo to $NVM_DIR
│ │ ├── Fetch and checkout tag $NVM_INSTALL_VERSION
│ │ └── gc --aggressive --prune=now
│ └── script: install_nvm_as_script()
│ ├── Download nvm.sh, nvm-exec, bash_completion in parallel
│ └── chmod +x nvm-exec
├── Detect shell profile (nvm_detect_profile())
├── Append loader + completion strings to profile
├── Source nvm.sh
├── nvm_check_global_modules() — warn about pre-existing globals
├── nvm_install_node() — install NODE_VERSION if set
└── nvm_reset() — unset all installer functions
Shell Profile Detection Order
nvm_detect_profile() checks in this order:
$PROFILEenvironment variable (if set and points to an existing file)- Shell-specific: bash →
.bashrc, then.bash_profile; zsh →${ZDOTDIR:-$HOME}/.zshrc, then.zprofile - Fallback scan:
.profile,.bashrc,.bash_profile,.zprofile,.zshrc
Set PROFILE=/dev/null to prevent any profile modification by the installer.
Removing nvm
No uninstaller exists. Remove manually:
rm -rf "$NVM_DIR"
# Edit ~/.bashrc / ~/.zshrc to remove the nvm loader lines
6. Operations
Binary Install Flow
nvm install <version>
└── nvm_install_binary(flavor, type, version, nosource)
├── Resolve version via mirror index
├── Build download URL: $MIRROR/$VERSION/node-$VERSION-$OS-$ARCH.$EXT
├── Download tarball (curl --compressed or wget fallback)
├── Verify checksum against SHASUMS256.txt
├── Extract to $NVM_DIR/versions/node/$VERSION/
└── If successful: nvm use $VERSION
Supported compression: .tar.xz (preferred, requires xz), .tar.gz (fallback).
Source Build Flow
nvm install -s <version>
└── nvm_install_node_source(version, nosource, make_jobs, additional)
├── Download source tarball
├── Verify checksum
├── Extract to temp dir
├── ./configure --prefix=$NVM_DIR/versions/node/$VERSION
├── make -j<jobs>
└── make install
Cache
Downloaded binaries are cached in $NVM_DIR/.cache/bin/ as node-<version>-<os>-<arch> files. nvm install --offline reads only from the cache without network access.
nvm cache dir # print cache location
nvm cache clear # wipe the cache
LTS Alias Refresh
When nvm ls-remote or nvm install --lts is called, nvm fetches index.tab and automatically updates $NVM_DIR/alias/lts/* and all named LTS alias files. These aliases are refreshed on every remote index fetch.
Checksum Verification
nvm detects available checksum binaries in this priority order:
sha256sum → shasum → sha256 → gsha256sum → openssl → bssl → sha1sum → sha1
If only SHA-1 tools are available, nvm falls back to SHASUMS.txt (SHA-1). If no checksum binary is found, the download continues without verification and prints a warning.
Warning
On systems without any supported checksum binary, downloads are not verified. Ensure at least one of
sha256sum,shasum, oropensslis installed.
nvm debug Output
nvm debug prints diagnostic information to stderr and exits with code 42:
nvm debug
nvm --version: v0.40.4
$SHELL: /bin/bash
$SHLVL: 1
${NVM_DIR}: '~/.nvm'
${PATH}: ~/.nvm/versions/node/v22.4.0/bin:...
curl: /usr/bin/curl, curl 8.x.x ...
git: /usr/bin/git, git version 2.x.x
nvm current: v22.4.0
which node: ~/.nvm/versions/node/v22.4.0/bin/node
Tip
Always include
nvm debugoutput when reporting issues. It captures the complete environment state needed for diagnosis.
Updating nvm
Re-run the install script with the desired version:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
The installer detects an existing $NVM_DIR/.git and runs git fetch + git checkout instead of cloning.
7. Security
Download Verification
All Node.js binaries are verified against SHASUMS256.txt (or SHASUMS.txt for SHA-1 fallback) fetched from the same mirror as the binary. If checksums don't match, the install fails with an error.
Caution
If
NVM_NODEJS_ORG_MIRRORis set to a custom mirror, ensure that mirror also provides valid SHASUMS256.txt files. A mirror without checksum files will result in unverified downloads.
NVM_AUTH_HEADER Sanitization
nvm_sanitize_auth_header() strips all characters that are not alphanumeric, colon, semicolon, underscore, period, space, or hyphen before passing the header to curl/wget. This prevents header injection.
NVM_INSTALL_GITHUB_REPO Warning
If the installer is run with NVM_INSTALL_GITHUB_REPO set to anything other than nvm-sh/nvm, it prints a prominent security warning:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE REPO IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
This mirrors the SSH host-key-change warning pattern and is intentional.
$PREFIX Conflict Detection
nvm_die_on_prefix() aborts nvm use if $PREFIX or $NPM_CONFIG_PREFIX is set to a path outside $NVM_DIR. This prevents npm from writing global packages to a system path that could conflict with nvm-managed installations.
No Root Required
nvm installs all Node.js versions into ~/.nvm/, which is owned by the user. No sudo is used or needed at any point. Global npm packages installed via nvm go into ~/.nvm/versions/node/<version>/lib/node_modules/.
8. Developer Internals
Repository Structure
nvm-sh/nvm/
├── nvm.sh — Main shell function library (~3,600 lines)
├── install.sh — Self-contained installer
├── nvm-exec — Executable: runs commands with NODE_VERSION in PATH
├── bash_completion — Tab completion
├── Makefile — Test suite runner
├── Dockerfile — CI test image
├── README.md — Project documentation
├── CONTRIBUTING.md — Contribution guide
├── CHANGELOG.md — Version history
├── ROADMAP.md — Project direction
├── GOVERNANCE.md — Governance model
├── test/
│ ├── common.sh — Shared test helpers
│ ├── fast/ — Fast unit-style tests (no network, no install)
│ ├── slow/ — Integration tests (installs Node, requires network)
│ ├── install_script/ — Installer-specific tests
│ ├── installation_node/ — Full Node install+use tests
│ ├── installation_iojs/ — io.js install+use tests
│ ├── sourcing/ — Shell sourcing behavior tests
│ ├── xenial/ — Source-build tests on Ubuntu Xenial
│ ├── fixtures/ — Test fixture files including .nvmrc examples
│ └── mocks/ — OS detection mocks (uname, isainfo, etc.)
└── package.json — Dev dependencies + test scripts
Code Patterns
All shell functions are prefixed nvm_ to avoid namespace collisions with user-defined functions. The top-level nvm() function itself dispatches to these helpers via a case statement.
GREP_OPTIONS='' command grep is used throughout to suppress user-defined grep aliases/options that could break pattern matching.
command prefix on external binaries prevents alias interference.
set -e / set -a / set -E compatibility: The nvm() function detects these shell options and temporarily disables them, running itself recursively in a clean environment. This allows nvm to work correctly regardless of the caller's set flags.
nvm_is_zsh guard: Multiple zsh-specific behaviors (glob expansion, word splitting) are handled with conditional setopt/unsetopt calls.
Version Directory Layout
$NVM_DIR/versions/node/v22.4.0/
├── bin/
│ ├── node
│ ├── npm -> ../lib/node_modules/npm/bin/npm-cli.js
│ └── npx -> ../lib/node_modules/npm/bin/npx-cli.js
├── include/node/
├── lib/
│ └── node_modules/
│ └── npm/
└── share/
The current symlink (created by nvm use unless NVM_SYMLINK_CURRENT=false) points to the active version directory.
npm Compatibility Matrix (from source)
nvm_install_latest_npm() implements a precise Node→npm compatibility matrix to avoid installing npm versions that are known-broken on specific Node releases:
| Node range | Maximum npm |
|---|---|
| v0.6.x | npm@1.3 |
| v0.9.x, v0.6.x | No further upgrade |
| < v1.1.0 | npm@4.5 |
| < v4.0.0 | npm@4 |
| v4.x, v5.x (below 5.10) | npm@5.3 |
| v4.5–v4.6 | npm@5.4.1 |
| < v6.0.0 | npm@5 |
| v6.0–v6.1, v9.0–v9.2 | npm@6.9 |
| < v10.0.0 | npm@6 |
| v12.0–v12.12, v13.x, v15.x, v14.0–v14.14 | npm@7 |
| v12.13–v12.x, v14.13–v14.16, v16.0–v16.12, v17.x | npm@8.6 |
| < v18.17, v19.x, v20.0–v20.4 | npm@9 |
| < v20.17, v21.x, v22.0–v22.8 | npm@10 |
| ≥ v20.17, ≥ v22.9 | Latest npm |
Testing
Tests are run via urchin (a POSIX test runner). Test files are plain shell scripts — each file is a test case; a zero exit code means pass.
# Run all tests for current shell
npm test
# Fast tests only (no network, no install)
npm run test/fast
# Slow integration tests (requires network)
npm run test/slow
# Installer tests
npm run test/install_script
Test suites are organized under test/fast/ and test/slow/, with each subdirectory representing a test group and each file in it a test case (executable bit required — enforced by npm run test:check-exec).
Key test categories:
- Alias creation, resolution, circular alias detection
- Version listing (local and remote, with mocks)
.nvmrcprocessing and sourcing behavior- Install/uninstall (slow, requires download)
nvm exec/nvm run/nvm use- Color output in pipelines
- Security: wget injection prevention
Contributing
See CONTRIBUTING.md in the repository. Key points:
- All new features require tests in
test/fast/ortest/slow/ - Shell scripts must pass
shellcheck(enforced in CI) - Markdown files must pass
markdown-link-check .editorconfigenforces indentation and line endings (enforced byeclint)- Commits go directly to main; PRs are reviewed by maintainers per
GOVERNANCE.md
9. Troubleshooting (Admin)
Error: Failed to clone nvm repo
Cause: Network connectivity issue or git not available.
Solution:
- Verify network:
curl -I https://github.com - Verify git:
git --version - Try the script method instead:
METHOD=script curl -o- https://... | bash
Error: Checksums do not match
Cause: Download corruption or mirror serving stale/wrong files.
Solution:
- Clear the cache:
nvm cache clear - Retry:
nvm install <version> - If using a custom mirror, verify it serves correct SHASUMS256.txt.
Caution
Do not proceed with a failed checksum. The download is discarded automatically.
nvm use Breaks After set -u (Unbound Variable)
Cause: nvm references unset variables in nounset mode (issue #3820: PATTERN unbound variable).
Solution: Source nvm.sh before enabling set -u in your shell profile. This is an upstream known issue.
Global npm Packages Missing After nvm install
Cause: Global packages are version-scoped; each Node.js version has its own node_modules.
Solution:
nvm reinstall-packages <previous-version>
# or set up $NVM_DIR/default-packages before installing
$PREFIX Conflict Error
Cause: $PREFIX or $NPM_CONFIG_PREFIX is set, pointing outside $NVM_DIR.
Solution:
# Unset conflicting variables
unset PREFIX
unset NPM_CONFIG_PREFIX
nvm use <version>
Check ~/.npmrc and your shell profile for these settings.
Mirror Connectivity
Cause: Corporate firewall blocks nodejs.org or download mirrors.
Solution:
export NVM_NODEJS_ORG_MIRROR=https://your-internal-mirror.example.com/nodejs
export NVM_AUTH_HEADER="Bearer your-token-here"
nvm install 22