Homeβ€Ί JavaScriptβ€Ί NVM: Install Node Version Manager and Switch Versions Safely

NVM: Install Node Version Manager and Switch Versions Safely

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: Node.js β†’ Topic 16 of 17
NVM lets you install and switch Node.
πŸ§‘β€πŸ’» Beginner-friendly β€” no prior JavaScript experience needed
In this tutorial, you'll learn:
  • NVM doesn't just switch Node versions β€” it switches entire environments. npm version, global packages, and native module binaries all change with it. One 'nvm use' call changes everything, which is exactly why it's powerful and why you need to know what's active before installing anything globally.
  • The .nvmrc file is the difference between 'works on my machine' and 'works on every machine'. Commit it to every repo. It costs nothing and saves your team from version mismatch debugging sessions that always happen at the worst possible time.
  • Global packages don't survive version switches. If your team suddenly can't find 'tsc' or 'nodemon' after a version switch, that's why. The fix is '--reinstall-packages-from' on install, or better yet, putting CLI tools in devDependencies so they're project-local and version-agnostic.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑ Quick Answer
Think of Node.js versions like different models of the same power tool. Your old kitchen renovation project needs the 2018 drill with the specific bit size. Your new bathroom project needs the 2023 model with a completely different chuck. NVM is your tool cabinet β€” every version sits in its own drawer, labelled, and you just pull out whichever one the current job needs. No throwing away tools. No borrowing from one job to screw up another.

I've watched a developer nuke a client's staging environment at 11pm because they ran 'npm install -g' after upgrading Node globally and silently broke every other project on the machine. Not a config error. Not a bad package. Just the wrong Node version running the wrong code β€” and no way to roll back fast. That's the kind of pain NVM exists to prevent.

The problem is that Node.js moves fast. A project you started two years ago might need Node 14. Your new microservice was built on Node 20. Your company's legacy monolith flatly refuses to start on anything above Node 16 because of a deprecated crypto API. Installing Node the 'normal' way β€” downloading it from nodejs.org and running the installer β€” gives you exactly one version at a time, globally, for everything. The moment you need two versions for two projects, you're either Googling workarounds or breaking something.

By the end of this, you'll know how to install NVM from scratch on macOS or Linux, install multiple Node versions, switch between them per-project automatically, and never think about version conflicts again. You'll also understand the one file β€” .nvmrc β€” that makes your whole team use the same Node version without anyone having to say a word about it.

Why Global Node Installs Will Eventually Burn You

Before NVM existed, the only way to change your Node version was to uninstall it and reinstall a different one. Full stop. People wrote shell scripts to automate the pain. Others just pinned everything to whatever Node version happened to be on the machine that week and hoped for the best.

The catastrophic version of this plays out when you join a team. The repo has no documented Node version. You run 'npm install', get 47 peer dependency warnings, and your app crashes on startup with 'TypeError: crypto.createCipher is not a function' β€” because createCipher was removed in Node 16 and you're running 18. You spend three hours debugging what looks like a broken crypto library before someone on Slack says 'oh yeah, use Node 14 for that one'. Three hours.

Global installs also mean global packages like 'ts-node' or 'nodemon' get installed against one specific Node version. Upgrade Node globally and those binaries can silently stop working or, worse, keep working but behave differently. NVM solves this by isolating each Node version in its own directory. When you switch versions, you switch the entire environment β€” runtime, npm, and all globally installed packages for that version.

NvmInstallSetup.bash Β· BASH
123456789101112131415161718192021222324252627282930313233
# io.thecodeforge β€” JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# STEP 1: Install NVM using the official install script.
# This downloads NVM and wires it into your shell automatically.
# Always check https://github.com/nvm-sh/nvm for the latest version tag.
# As of writing, the current release is v0.39.7.
# ─────────────────────────────────────────────────────────────
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# ─────────────────────────────────────────────────────────────
# STEP 2: The install script appends these lines to your shell config.
# If you use bash, this goes into ~/.bashrc
# If you use zsh (default on modern macOS), this goes into ~/.zshrc
# You can verify by opening the file and looking at the bottom.
# ─────────────────────────────────────────────────────────────
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"          # This loads nvm itself
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # Tab-completion for nvm commands

# ─────────────────────────────────────────────────────────────
# STEP 3: Reload your shell config so the changes take effect
# in the CURRENT terminal session without opening a new window.
# Replace .zshrc with .bashrc if you're on bash.
# ─────────────────────────────────────────────────────────────
source ~/.zshrc

# ─────────────────────────────────────────────────────────────
# STEP 4: Verify NVM is installed and on your PATH.
# If this prints a version number, you're good.
# If it prints nothing, your shell config didn't load β€” see the warning callout.
# ─────────────────────────────────────────────────────────────
nvm --version
β–Ά Output
0.39.7
⚠️
Production Trap: 'nvm: command not found' After InstallIf 'nvm --version' returns nothing or 'command not found', the install script found your shell config file but your terminal session hasn't reloaded it. Run 'source ~/.zshrc' (or 'source ~/.bashrc') manually, or just close and reopen the terminal. If it still fails, open ~/.zshrc and check the three NVM export lines are actually there at the bottom β€” the install script occasionally silently fails to append them if the file has a syntax error above.

Installing Node Versions and Switching Between Them

NVM has a clean mental model: you install versions into its own directory (~/.nvm/versions/node/), and at any moment exactly one version is 'active' in your current shell session. Switching is instant β€” no download, no restart, no package reinstall β€” because you're just changing which directory your PATH points to.

Here's the real-world scenario where this matters. You're maintaining a payment integration service that runs on Node 14 (the client hasn't approved an upgrade β€” you know how it goes). You also have a new internal tool you're building on Node 20 LTS. Without NVM, you are constantly uninstalling and reinstalling. With NVM, you install both once and switch in under a second.

One thing people miss: 'nvm use' only changes the version for the current terminal session. Open a new terminal tab and you're back to the default. That's intentional β€” it means you can have different projects open in different tabs, each running their own Node version simultaneously. The way to make a version sticky across all sessions is 'nvm alias default'.

NvmNodeVersionManagement.bash Β· BASH
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
# io.thecodeforge β€” JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# See all available Node.js versions you can install.
# This list is long β€” pipe it through grep to filter for LTS versions,
# which are the ones you should be running in production.
# ─────────────────────────────────────────────────────────────
nvm ls-remote --lts

# ─────────────────────────────────────────────────────────────
# Install specific Node versions.
# You'll install as many as your projects need.
# LTS = Long Term Support β€” stable, security-patched, production-safe.
# ─────────────────────────────────────────────────────────────
nvm install 20          # Installs the latest Node 20.x.x release
nvm install 18          # Installs the latest Node 18.x.x release
nvm install 14.21.3     # Installs an exact patch version β€” useful when a
                        # specific patch fixed a bug your app depends on

# ─────────────────────────────────────────────────────────────
# See every version you have installed locally.
# The arrow (β†’) shows which one is currently active.
# ─────────────────────────────────────────────────────────────
nvm ls

# ─────────────────────────────────────────────────────────────
# Switch to a specific version for THIS terminal session only.
# This is the command you'll run most often.
# ─────────────────────────────────────────────────────────────
nvm use 18

# Confirm which version is now active
node --version
npm --version    # npm version changes too β€” it's bundled with Node

# ─────────────────────────────────────────────────────────────
# Set a default version that activates in every NEW terminal session.
# Without this, every new tab starts with no active version (or the
# last one you set as default β€” which might be wrong).
# ─────────────────────────────────────────────────────────────
nvm alias default 20

# ─────────────────────────────────────────────────────────────
# Jump to the version your current project's .nvmrc file specifies.
# This is the command you run when you cd into a project repo.
# If .nvmrc doesn't exist, this throws an error β€” see next section.
# ─────────────────────────────────────────────────────────────
nvm use
β–Ά Output
# nvm ls output example:
# v14.21.3
# v18.20.2
-> v20.11.1
default -> 20 (-> v20.11.1)
lts/* -> lts/iron (-> v20.11.1)

# node --version after 'nvm use 18':
v18.20.2

# npm --version after 'nvm use 18':
10.5.0
⚠️
Senior Shortcut: Install the Latest LTS in One CommandInstead of looking up the current LTS version number, run 'nvm install --lts' and NVM resolves it for you. Follow it with 'nvm alias default lts/*' to make the latest LTS your default automatically. This is the setup most senior devs run on fresh machines β€” no version-hunting required.

The .nvmrc File: Make Your Whole Team Use the Right Node Version

Here's the part most tutorials skip, and it's the part that actually matters in a team setting. A .nvmrc file lives in the root of your project and contains exactly one thing: the Node version that project needs. When any developer runs 'nvm use' inside that directory, NVM reads the file and switches to the right version automatically. No Slack message. No README footnote nobody reads. No 'it works on my machine'.

This is particularly important for CI/CD pipelines. Every GitHub Actions or Jenkins job that checks out your repo should call 'nvm use' before running any Node commands. Otherwise your pipeline runs whatever Node version happens to be installed on the build agent β€” which can drift over time as agents get updated. I've seen a CI agent upgrade Node from 18 to 20 during a routine maintenance window and break a production build at 6am because one package used a deprecated API that Node 20 finally removed.

You can also configure NVM to auto-switch when you change directories. This requires a small addition to your shell config. It's optional, but once you try it you won't go back β€” you just 'cd' into a project and the right Node version is already active.

NvmRcProjectSetup.bash Β· BASH
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
# io.thecodeforge β€” JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# CREATING A .nvmrc FILE FOR YOUR PROJECT
# Run this from your project root directory.
# This writes the current active Node version into .nvmrc.
# Commit this file β€” it belongs in source control.
# ─────────────────────────────────────────────────────────────
node --version > .nvmrc

# Verify what was written β€” should be something like 'v20.11.1'
cat .nvmrc

# ─────────────────────────────────────────────────────────────
# Or write a specific version manually:
# ─────────────────────────────────────────────────────────────
echo "20.11.1" > .nvmrc    # NVM accepts versions with or without the 'v' prefix

# ─────────────────────────────────────────────────────────────
# When any developer clones your repo and runs 'nvm use',
# NVM reads .nvmrc and switches to the right version.
# If they don't have it installed yet, they'll get:
#   N/A: version "20.11.1" is not yet installed.
# The fix: run 'nvm install' (no version arg) β€” it reads .nvmrc too.
# ─────────────────────────────────────────────────────────────
nvm install    # installs the version from .nvmrc if not already present
nvm use        # then activates it

# ─────────────────────────────────────────────────────────────
# AUTO-SWITCHING: make NVM change Node version automatically
# when you cd into a directory containing a .nvmrc file.
# Add this block to your ~/.zshrc (or ~/.bashrc for bash users).
# ─────────────────────────────────────────────────────────────

# Paste this into your shell config file:
autoload -U add-zsh-hook                    # zsh only β€” loads the hook utility

# This function runs every time you change directories
load-nvmrc() {
  local nvmrc_path
  nvmrc_path="$(nvm_find_nvmrc)"            # NVM's own function to find the nearest .nvmrc

  if [ -n "$nvmrc_path" ]; then             # if a .nvmrc was found...
    local nvmrc_node_version
    nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")  # resolve installed version

    if [ "$nvmrc_node_version" = "N/A" ]; then
      nvm install                           # version not installed yet β€” install it
    elif [ "$nvmrc_node_version" != "$(nvm version)" ]; then
      nvm use                               # version installed but not active β€” switch
    fi
  elif [ -n "$(PWD=$OLDPWD nvm_find_nvmrc)" ]; then
    nvm use default                         # left a .nvmrc directory β€” restore default
  fi
}

add-zsh-hook chpwd load-nvmrc               # wire the function to the directory-change hook
load-nvmrc                                  # also run it once on shell startup
β–Ά Output
# cat .nvmrc:
v20.11.1

# When you cd into a project directory with .nvmrc containing 'v18.20.2':
Found '/path/to/your-project/.nvmrc' with version <v18.20.2>
Now using node v18.20.2 (npm v10.5.0)

# When you cd out to a directory without .nvmrc:
Restored to default Node version: v20.11.1
πŸ”₯
Interview Gold: .nvmrc vs engines field in package.jsonBoth express a required Node version, but they do different things. The 'engines' field in package.json is a documentation hint β€” npm warns about mismatches but doesn't enforce them unless you run 'npm install --engine-strict'. The .nvmrc file is an active enforcement tool β€” NVM reads it and physically changes the runtime. Use both: .nvmrc for local development switching, engines for CI enforcement.

Managing Global Packages Per Node Version and Keeping Things Clean

Here's what surprises most developers switching to NVM for the first time: global packages don't carry over between Node versions. If you install 'npm install -g typescript' while on Node 20, then switch to Node 18, TypeScript is gone from that version's global scope. Each Node version in NVM has its own isolated node_modules directory for globals.

This is actually correct behaviour, not a bug. A TypeScript version compiled for Node 20 might behave differently on Node 14. Isolation prevents that silent mismatch. But it does mean you need to reinstall globals when you set up a new version. NVM has a shortcut for this: the '--reinstall-packages-from' flag copies all globally installed packages from one version to another during install.

Also know when to uninstall old versions. Unused Node versions sit in ~/.nvm/versions/ and each one takes around 50-100MB of disk space including its npm cache. After six months of grabbing versions you 'needed just to test something', you can easily have a gigabyte of stale Node installs. 'nvm uninstall' cleans them out. Run 'nvm ls' occasionally and prune anything you haven't touched in months.

NvmGlobalPackagesAndCleanup.bash Β· BASH
1234567891011121314151617181920212223242526272829303132333435363738394041424344
# io.thecodeforge β€” JavaScript tutorial

# ─────────────────────────────────────────────────────────────
# REINSTALLING GLOBAL PACKAGES FROM ANOTHER VERSION
# When you install Node 20 and want all the globals you had on Node 18,
# pass the --reinstall-packages-from flag during install.
# NVM copies the package list from the source version and installs each one fresh.
# ─────────────────────────────────────────────────────────────
nvm install 20 --reinstall-packages-from=18

# ─────────────────────────────────────────────────────────────
# Check which global packages are installed for the CURRENT active version.
# This scope flag (-g) is crucial β€” without it you see project-local packages.
# ─────────────────────────────────────────────────────────────
npm list -g --depth=0

# ─────────────────────────────────────────────────────────────
# UNINSTALLING A NODE VERSION YOU NO LONGER NEED
# You cannot uninstall the version that's currently active.
# Switch to a different version first, then uninstall.
# ─────────────────────────────────────────────────────────────
nvm use 20                  # Switch away from the version you want to remove
nvm uninstall 14.21.3       # Remove Node 14.21.3 and all its global packages

# ─────────────────────────────────────────────────────────────
# ALIASING VERSIONS: give a version a memorable name
# Useful when you're jumping between a 'client-legacy' version
# and your standard dev version constantly.
# ─────────────────────────────────────────────────────────────
nvm alias payments-service 18.20.2   # create a named alias
nvm use payments-service             # switch using the alias name
nvm alias --delete payments-service  # remove the alias when done

# ─────────────────────────────────────────────────────────────
# CHECKING WHICH VERSION A PROJECT SHOULD USE
# before running 'nvm use', see what version .nvmrc specifies
# without actually switching. Useful in scripts.
# ─────────────────────────────────────────────────────────────
cat .nvmrc

# ─────────────────────────────────────────────────────────────
# FULL CLEANUP: see how much disk space your NVM installations are using
# ─────────────────────────────────────────────────────────────
du -sh ~/.nvm/versions/node/*    # shows size of each installed version
β–Ά Output
# npm list -g --depth=0 on Node 20 after reinstall from Node 18:
/home/user/.nvm/versions/node/v20.11.1/lib
β”œβ”€β”€ npm@10.5.0
β”œβ”€β”€ typescript@5.3.3
└── nodemon@3.0.3

# nvm uninstall 14.21.3:
Uninstalled node v14.21.3

# du -sh output example:
98M /home/user/.nvm/versions/node/v14.21.3
112M /home/user/.nvm/versions/node/v18.20.2
108M /home/user/.nvm/versions/node/v20.11.1
⚠️
Never Do This: Install Global CLI Tools Without Checking Active VersionRunning 'npm install -g some-cli-tool' without confirming which Node version is active means the tool gets installed into the wrong version's global scope. You'll run the tool later, it'll fail with 'command not found', and you'll spend 20 minutes debugging PATH issues. Always run 'nvm current' or 'node --version' first. One second of checking saves twenty minutes of confusion.
Feature / AspectGlobal Node Install (nodejs.org)NVM
Multiple Node versions simultaneouslyNo β€” one version at a time, globallyYes β€” unlimited versions, all isolated
Switching versionsUninstall and reinstall β€” 5-10 minutesnvm use 18 β€” under 1 second
Per-project version pinningNot possible without manual PATH hacksAutomatic via .nvmrc file
Global package isolationOne shared global scope for all projectsEach Node version has its own global scope
CI/CD compatibilityDepends entirely on the build agent's Node versionnvm install + nvm use in pipeline script
Windows supportFull native supportNVM for Unix/macOS only β€” use nvm-windows on Windows
Team consistency enforcementREADME note that nobody reads.nvmrc committed to git β€” enforced automatically
Requires sudo/admin rightsYes β€” installs into system directoriesNo β€” installs entirely in your home directory
Rollback to previous versionFull uninstall + reinstall requirednvm use [previous-version] β€” instant
Disk footprintSingle install, minimal~100MB per version β€” prune unused ones regularly

🎯 Key Takeaways

  • NVM doesn't just switch Node versions β€” it switches entire environments. npm version, global packages, and native module binaries all change with it. One 'nvm use' call changes everything, which is exactly why it's powerful and why you need to know what's active before installing anything globally.
  • The .nvmrc file is the difference between 'works on my machine' and 'works on every machine'. Commit it to every repo. It costs nothing and saves your team from version mismatch debugging sessions that always happen at the worst possible time.
  • Global packages don't survive version switches. If your team suddenly can't find 'tsc' or 'nodemon' after a version switch, that's why. The fix is '--reinstall-packages-from' on install, or better yet, putting CLI tools in devDependencies so they're project-local and version-agnostic.
  • The counterintuitive truth: NVM installs entirely in your home directory with zero sudo privileges required. That's not a quirk β€” it's intentional. System-level Node installs require admin rights and pollute shared system directories. NVM keeps everything in ~/.nvm and that's the right design for a per-user tool.

⚠ Common Mistakes to Avoid

  • βœ•Mistake 1: Running 'nvm use' in a subshell or script and expecting it to affect the parent shell β€” the terminal prompt still shows the old version. NVM version switches only apply to the current shell process. A script that calls 'nvm use 18' affects nothing outside that script. To fix: source the script instead of executing it ('source ./setup.sh'), or set the version in your shell profile before running the script.
  • βœ•Mistake 2: Forgetting that 'nvm use' is session-scoped β€” opening a new terminal tab after switching to Node 18 drops you back to the default version, causing 'node: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.28 not found' type errors if the default is incompatible with installed native modules. Fix: run 'nvm alias default 18' to persist the version across all new sessions, or use .nvmrc + auto-switching so every project sets itself up.
  • βœ•Mistake 3: Installing NVM while a system Node.js install still exists on PATH β€” 'nvm --version' works but 'node --version' still returns the system version because the system Node is earlier in PATH. The symptom is that 'which node' returns '/usr/bin/node' instead of something inside '~/.nvm/'. Fix: remove the system Node installation completely ('sudo apt remove nodejs' on Ubuntu or uninstall via the nodejs.org package on macOS), then reload your shell.
  • βœ•Mistake 4: Committing node_modules built under one Node version and deploying to a server running a different version β€” native addons like 'bcrypt' or 'sharp' will throw 'Error: The module was compiled against a different Node.js version' at runtime. Fix: never commit node_modules, add it to .gitignore, and always run 'npm ci' on the target server after switching to the correct version via .nvmrc.

Interview Questions on This Topic

  • QIf two developers on your team both run 'nvm use' in the same project directory but get different active Node versions, what are the possible causes and how do you diagnose it?
  • QYour CI pipeline runs 'nvm use' inside a shell script but the Node version doesn't change for subsequent commands in the pipeline. What's happening and how do you fix it without restructuring the whole pipeline?
  • QWhen would you choose a Docker-based Node version strategy over NVM in a team environment, and what specific team or project conditions would push you toward Docker?
  • QA junior developer reports that 'nodemon' suddenly stopped working after they ran 'nvm install 20'. They can still run 'nodemon' on Node 18. What do you tell them, and what's the root cause?

Frequently Asked Questions

Does NVM work on Windows?

The original NVM (nvm-sh) only works on macOS and Linux. On Windows, use nvm-windows β€” a completely separate project maintained by coreybutler. The commands are almost identical ('nvm install', 'nvm use', 'nvm ls') but the internals are different and some edge-case behaviours differ. WSL2 (Windows Subsystem for Linux) is another solid option β€” install the Linux version of NVM inside your WSL2 Ubuntu environment and it works identically to native Linux.

What's the difference between 'nvm install' and 'nvm use'?

'nvm install' downloads a Node version from the internet and stores it in ~/.nvm β€” you only need to run it once per version. 'nvm use' switches your active session to an already-installed version β€” it's instant because nothing downloads. The rule: install once, use every time you switch. If you run 'nvm use 20' and get 'N/A: version not installed', you skipped the install step.

How do I make NVM automatically switch Node versions when I cd into a project folder?

Add the auto-switching hook to your shell config (~/.zshrc for zsh, ~/.bashrc for bash). The hook is a function that fires on directory change, checks for a .nvmrc file, and calls 'nvm use' if one exists. The full function is documented in the nvm-sh README under 'Deeper Shell Integration'. After adding it, run 'source ~/.zshrc' to activate without restarting the terminal.

Why do global npm packages disappear when I switch Node versions with NVM?

Each Node version in NVM has its own isolated node_modules directory for global packages at ~/.nvm/versions/node/vX.X.X/lib/node_modules/. This is by design β€” a native module compiled for Node 18 can corrupt silently or crash loudly when loaded by Node 20. When you install a new version, use 'nvm install [version] --reinstall-packages-from=[old-version]' to copy globals across. Long-term, move project CLI tools into devDependencies so they're version-pinned at the project level and don't depend on globals at all.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousNode.js ClusteringNext β†’Nodemon: Auto-Restart Node.js Apps During Development
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged