Troubleshooting

zsh: command not found: npm? Fix npm on macOS, Linux, sudo and cron

npm ships with Node.js. Fix zsh: command not found: npm and bash: npm: command not found on macOS, Ubuntu, Debian, with nvm, sudo, cron and CI.

Published ~11 min read
On this page

Short answer

npm ships with Node.js, so zsh: command not found: npm means this shell finds no npm: Node is not installed, your Linux package of Node came without npm, or Node’s bin directory is not on this shell’s PATH. Run command -v node npm and fix whichever of the three it shows.

Where to start

Check what this shell can see

Before installing anything, ask the shell where it looks. These commands are the same in bash and zsh and change nothing:

bash · zsh
command -v node npm      # path of each one this shell finds
type -a npm              # every npm, plus aliases and functions
echo "$PATH" | tr ':' '\n'  # the directories it searches, in order

On one of our Debian 12 servers, where Node came from nvm, a working shell answers like this. The npm file is a link to a JavaScript file whose first line is #!/usr/bin/env node, so npm also needs node on the PATH to start:

output · Debian 12, nvm
$ command -v node npm
/root/.nvm/versions/node/v26.12.1/bin/node
/root/.nvm/versions/node/v26.12.1/bin/npm
$ ls -l "$(command -v npm)"
... /root/.nvm/versions/node/v26.12.1/bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js
$ head -1 "$(command -v npm)"
#!/usr/bin/env node
  • No line for node and none for npm: Node.js is not installed, or it lives in a directory this shell does not search. Install it for your system below.
  • A line for node, none for npm: a distribution package that splits npm off (Debian and Ubuntu, Arch, Alpine).
  • Both found here, npm fails elsewhere: sudo, cron, systemd, CI or a new terminal has another PATH. Find your message in the next section.

In bash, which is an external program that searches files only; type -a (same in bash and zsh) also shows an alias or function named npm that would run first.

Which message do you see?

Symptom, cause, fix

  1. Symptom: zsh: command not found: npm

    Cause
    macOS has no Node.js out of the box, or Node is installed but the line that puts Homebrew or nvm on the PATH sits in a file zsh does not read.
    Fix
    Install Node, then check the zsh startup files: macOS and zsh.
  2. Symptom: bash: npm: command not found or -bash: npm: command not found

    Cause
    On a Linux server: Node.js is not installed, or only the distribution's nodejs package is. The -bash form comes from a login shell, typically an SSH session.
    Fix
    Ubuntu and Debian: NodeSource or the npm package
  3. Symptom: node --version works, npm is not found

    Cause
    Debian, Ubuntu, Arch and Alpine ship npm as its own package, and installing nodejs does not pull it in.
    Fix
    Debian · Ubuntu · Arch · Alpine
    sudo apt install npm   # Debian, Ubuntu
    sudo pacman -S npm     # Arch
    apk add npm            # Alpine
    Details and the NodeSource alternative: Ubuntu and Debian, Arch and Alpine.
  4. Symptom: sudo: npm: command not found or sudo: 'npm': command not found

    Cause
    sudo replaces your PATH with secure_path, which has no nvm or home directories in it. The quoted form is sudo-rs (Ubuntu 25.10 and 26.04).
    Fix
    Run it without sudo, or pass the PATH along
  5. Symptom: sh: 1: npm: not found, /bin/sh: 1: npm: not found or bash: line 1: npm: command not found

    Cause
    A non-interactive shell (cron, a Dockerfile RUN line, CI, ssh host 'npm ...', a deploy script) never ran the lines that load nvm or Homebrew.
    Fix
    cron, systemd and CI
  6. Symptom: bash: /usr/bin/npm: No such file or directory

    Cause
    bash remembered where npm was, and npm has since moved or been removed (for example after switching to nvm).
    Fix
    Clear the cache with hash -r (zsh: rehash) or open a new terminal.
  7. Symptom: /usr/bin/env: ‘node’: No such file or directory

    Cause
    npm was found by its full path, but node is not on the PATH of that shell.
    Fix
    node: command not found
  8. Symptom: 'npm' is not recognized as an internal or external command,

    Cause
    Windows: Node.js is not installed, or the terminal was already open when the installer changed the PATH.
    Fix
    Windows cmd and PowerShell

In bash and dash a command that is not found ends with exit status 127, the number a CI log shows when a step fails this way (exit code 127).

Fix npm on Ubuntu and Debian

Debian and Ubuntu build Node.js and npm as two packages. The nodejs package only suggests npm, and apt does not install suggested packages by default, so sudo apt install nodejs leaves you with node and no npm. The versions are also fixed for the life of each release:

Distribution packages (checked 02.11.2026)

Debian 12
nodejs 18.20.4, npm 9.2.0apt-cache on our server
Debian 13
nodejs 20.19.2, npm 9.2.0packages.debian.org
Ubuntu 24.04
nodejs 18.19.1, npm 9.2.0packages.ubuntu.com
Ubuntu 26.04
nodejs 22.22.1, npm 9.2.0packages.ubuntu.com

Node 18 reached its upstream end of life in April 2025 and Node 20 in April 2026, and many current tools no longer start on them. So pick one of two routes and do not mix them.

Route 1: the distribution’s npm package

Fine for tools that still support the Node version your release ships, and it gets security updates through apt like everything else:

Debian · Ubuntu
sudo apt update
sudo apt install -y nodejs npm

Expect a long list: the Debian npm package pulls in dozens of separate node-* library packages instead of bundling them.

Route 2: a current Node.js from NodeSource

NodeSource builds one nodejs package that already contains npm and npx and installs them to /usr/bin, which is on every PATH, sudo’s and systemd’s included. That makes it the better choice on a server where services and cron jobs need npm:

Debian · Ubuntu
sudo apt update && sudo apt install -y ca-certificates curl
curl -fsSL https://deb.nodesource.com/setup_lts.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs

Arch and Alpine

Same split: on Arch, npm is its own package in the extra repository, which is why some Arch users lost npm after an update that kept nodejs. Install both by name:

Arch
sudo pacman -S nodejs npm
Alpine
apk add nodejs npm

Fix npm with nvm and other version managers

nvm installs Node into ~/.nvm/versions/node/VERSION/bin and adds that directory to the PATH of each shell that loads nvm.sh. Every shell that does not load it has no npm. On our server, the same machine answers like this once the shell starts without nvm’s lines:

output · Debian 12, nvm not loaded
$ env -i bash -c "npm --version"
bash: line 1: npm: command not found
$ env -i sh -c "npm --version"
sh: 1: npm: not found

Load nvm by hand, and npm is back. This also works in sh and dash:

bash · zsh · sh
export NVM_DIR="$HOME/.nvm"
. "$NVM_DIR/nvm.sh"
nvm current
npm --version
  • New terminals have no npm, the old one does: nvm starts each shell with the version named by the default alias. Run nvm ls, then nvm alias default node (newest installed) or nvm alias default lts/* (newest installed LTS; Node.js 26 has been the Active LTS line since 28 October 2026).
  • The line ends in --no-use: nvm’s README offers . "$NVM_DIR/nvm.sh" --no-use to load nvm without activating any Node. With it, npm appears only after nvm use.
  • Over SSH (-bash: npm: command not found): bash starts as a login shell and reads only the first of ~/.bash_profile, ~/.bash_login and ~/.profile that exists; the stock Debian and Ubuntu ~/.profile loads ~/.bashrc. A ~/.bash_profile you created yourself skips the nvm lines in ~/.bashrc. Add [ -f ~/.bashrc ] && . ~/.bashrc to it.

fnm, Volta, asdf and mise

They work like nvm: npm is on the PATH only after their line in the shell startup file has run. If new terminals have no npm, check that the line sits in the file of the shell you use (zsh never reads ~/.bashrc). The lines from each tool’s own docs:

~/.zshrc
# fnm (bash: --shell bash, in ~/.bashrc)
eval "$(fnm env --use-on-cd --shell zsh)"

# mise (bash: activate bash, in ~/.bashrc)
eval "$(~/.local/bin/mise activate zsh)"

# asdf 0.16 and later (bash: in ~/.bash_profile)
export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH"

# Volta (its installer adds these; check they are there)
export VOLTA_HOME="$HOME/.volta"
export PATH="$VOLTA_HOME/bin:$PATH"

asdf and mise also need a Node version selected for the directory or globally, or npm will not run; asdf current and mise ls show what is set. With Volta, volta install node installs the latest LTS with its npm. None of these lines run in cron or systemd either, so the non-interactive fixes apply to them too.

After npm config set prefix

The usual workaround for EACCES errors on global installs, npm config set prefix ~/.npm-global, writes a prefix line to ~/.npmrc. From then on npm install -g puts commands into ~/.npm-global/bin, a newer npm from npm install -g npm included. If that directory is not on the PATH, those commands are not found and the shell keeps running the old npm, or none at all once the old one is removed. Ask npm where global installs go and add that bin:

bash · zsh
npm prefix -g            # where npm install -g puts things
ls "$(npm prefix -g)/bin"
grep prefix ~/.npmrc     # if npm itself no longer starts
~/.zshrc or ~/.bashrc
export PATH="$HOME/.npm-global/bin:$PATH"

With nvm, remove the prefix instead: nvm already keeps global packages per Node version in your home, without root. In a test home directory on our server, a prefix line in ~/.npmrc made loading nvm print this, and nvm use exited with status 11:

output · Debian 12, nvm, prefix in ~/.npmrc
Your user’s .npmrc file (${HOME}/.npmrc)
has a `globalconfig` and/or a `prefix` setting, which are incompatible with nvm.
Run `nvm use --delete-prefix v26.12.1 --silent` to unset it.

Run the command it names, or delete the line with npm config delete prefix, then open a new terminal.

Fix “zsh: command not found: npm” on macOS

zsh is the default shell on macOS, and macOS does not include Node.js. If command -v node prints nothing, install it first, either with the installer package from nodejs.org, which puts node and npm into /usr/local/bin, or with Homebrew:

macOS · Homebrew
brew install node

Where Homebrew puts npm depends on the Mac. On Apple Silicon the prefix is /opt/homebrew, which macOS does not search, so zsh needs Homebrew’s shellenv line; Homebrew on an Intel Mac lives in /usr/local, which is on the PATH already:

Key facts

Homebrew on Apple Silicon
/opt/homebrew/bin/npm
Homebrew on an Intel Mac
/usr/local/bin/npm
nodejs.org installer
/usr/local/bin/npm
nvm
~/.nvm/versions/node/VERSION/bin/npm
~/.zprofile (Apple Silicon)
eval "$(/opt/homebrew/bin/brew shellenv)"

Then open a new terminal. If Node is installed and zsh still cannot find npm, the cause is almost always a startup file:

  • The lines went to a bash file. zsh reads ~/.zprofile for login shells (which Terminal opens) and ~/.zshrc for interactive ones, never ~/.bashrc or ~/.bash_profile. Move nvm’s three lines to ~/.zshrc:
~/.zshrc
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
  • Oh My Zsh replaced ~/.zshrc. Its installer overwrites an existing ~/.zshrc unless you pass --keep-zshrc, and keeps the old one as ~/.zshrc.pre-oh-my-zsh. Copy the nvm or PATH lines from there into the new file.
  • A PATH line without $PATH. export PATH="/some/dir" throws away every other directory, /usr/local/bin and /opt/homebrew/bin included. Write export PATH="/some/dir:$PATH".
  • A versioned formula such as node@24. It is keg-only, so Homebrew does not link it into /opt/homebrew/bin. Add export PATH="/opt/homebrew/opt/node@24/bin:$PATH" to ~/.zshrc.
  • You installed while the terminal was open. Run rehash or open a new window.

Check the result with source ~/.zshrc or a new terminal, then command -v npm. Avoid the $(npm bin -g) PATH line that older guides copy around: npm 9 removed npm bin, and the line needs a working npm to run in the first place. npm prefix -g plus /bin is the directory it meant.

“sudo: npm: command not found”

sudo does not use your PATH. With secure_path set in /etc/sudoers, it “will use this value in place of the user’s PATH” (sudoers manual). Debian 12’s value is /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, and an nvm Node in your home directory is not on it. What that looked like on our server:

output · Debian 12, sudo 1.9.13p3
$ sudo npm --version
sudo: npm: command not found
$ sudo -E npm --version
sudo: npm: command not found
$ sudo "$(command -v npm)" --version
/usr/bin/env: ‘node’: No such file or directory
$ sudo env "PATH=$PATH" npm --version
11.20.0

So sudo -E does not help, because secure_path still replaces PATH, and the full path to npm fails one step later because npm then cannot find node. Passing your PATH explicitly works for one command. Better fixes:

  • With nvm, drop sudo. nvm’s directories belong to you, so npm install -g PACKAGE needs no root at all.
  • For root, services and other users, install Node system-wide with NodeSource so npm sits in /usr/bin, which secure_path includes.

npm in cron, systemd and CI

None of these read ~/.zshrc, ~/.bashrc or ~/.profile, so whatever nvm or Homebrew added there is missing. With a system-wide Node in /usr/bin they just work; with nvm, give each one the path.

cron

cron runs jobs with /bin/sh (/usr/bin/sh on Debian 13) and sets only a few variables itself. A variable line in the crontab is taken literally, nothing in it is expanded; Debian 13’s crontab manual says that PATH=$HOME/bin:$PATH “will not work as you might expect”. Cron on Debian 12 behaves the same, its manual just does not spell it out. Write the directories out, or load nvm in the command line itself, where the shell does expand $HOME:

crontab -e
# option 1: literal PATH (update it when nvm installs a new version)
PATH=/home/USER/.nvm/versions/node/VERSION/bin:/usr/bin:/bin
0 3 * * * cd /srv/app && npm run nightly

# option 2: load nvm per job (nvm works in sh and dash)
0 3 * * * . "$HOME/.nvm/nvm.sh" && cd /srv/app && npm run nightly

If the job still does nothing, the reason is often not npm at all: cron job not running walks through the rest.

systemd

systemd looks up a bare ExecStart= command in a fixed list of system directories (systemd-path search-binaries-default prints it; on Debian 12 that is /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin), and the services it starts get a fixed PATH too. An nvm npm is in neither. Use absolute paths and give the unit a PATH that contains node, because npm starts through #!/usr/bin/env node:

/etc/systemd/system/app.service (excerpt)
[Service]
User=app
WorkingDirectory=/srv/app
Environment=PATH=/home/app/.nvm/versions/node/VERSION/bin:/usr/bin:/bin
ExecStart=/home/app/.nvm/versions/node/VERSION/bin/npm start

For a production service, a system-wide Node and ExecStart=/usr/bin/node server.js is simpler and survives nvm upgrades. The unit file, restart policy and logs are in create a systemd service.

CI, Docker and ssh commands

  • Dockerfile: RUN lines run under /bin/sh, and an image built FROM debian or FROM ubuntu has no Node, hence /bin/sh: 1: npm: not found. Start from an official Node image such as node:26-slim or node:lts-slim, which ship npm.
  • CI runners: install Node in the pipeline (for GitHub Actions, the setup-node action) instead of relying on a shell profile, then use npm ci.
  • ssh host 'npm run build': that shell is not interactive. Even where bash reads ~/.bashrc for it, the stock Debian and Ubuntu ~/.bashrc stops at its first lines (“If not running interactively, don't do anything”), before the nvm lines the installer appended at the end. Load nvm in the command: ssh host '. ~/.nvm/nvm.sh && npm run build'.

“node: command not found”

Usually it appears together with the npm error and has the same cause: no Node.js, or no Node on this PATH. Two cases are specific to node:

  • npm runs, then fails with /usr/bin/env: ‘node’: No such file or directory (straight quotes without a UTF-8 locale). The npm file was found by its full path, but node was not, as in the sudo example above. Put node’s directory on the PATH of that environment.
  • The package is nodejs, the command is node. On Debian and Ubuntu you install nodejs and run node; there is no command called nodejs to fall back on in a script.

Windows: “'npm' is not recognized”

cmd prints 'npm' is not recognized as an internal or external command, followed by operable program or batch file.; PowerShell says The term 'npm' is not recognized. Install the LTS release, then close every terminal and editor window: programs keep the PATH they started with.

PowerShell · cmd
winget install OpenJS.NodeJS.LTS
where.exe npm
npm --version

where.exe npm should name a file in C:\Program Files\nodejs. If PowerShell instead says npm.ps1 cannot be loaded because running scripts is disabled on this system, that is the execution policy, not the PATH: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, or type npm.cmd.

Check that it works

bash · zsh
command -v npm
npm --version
npm prefix -g

The first line names the npm this shell runs (/usr/bin/npm for NodeSource or apt, /opt/homebrew/bin/npm or /usr/local/bin/npm on a Mac, a path under ~/.nvm for nvm), the second its version, the third the prefix global packages go to. Repeat it wherever npm failed before: a new terminal, sudo, the cron job, the service.

npm works and the next tool is missing? yarn and pnpm are not part of npm: yarn: command not found and pnpm: command not found cover them. For any other program and for making a PATH change permanent in bash, zsh and fish, see command not found: the three causes.

Running npm on a small server

Once npm is found, the next wall on a small VPS is memory: if npm install or npm run build stops with nothing but Killed, the kernel’s OOM killer ended it (why Linux kills your process). Add swap or give the build more RAM. For Node apps that run around the clock, our Standard KVM plans come with full root access, so NodeSource, nvm and systemd work exactly as shown here.

How we tested

OS image
Debian 12 (bookworm)
Versions
Node.js v26.12.1 (nvm)npm 11.20.0GNU bash 5.2.15sudo 1.9.13p3systemd 252
Tested on
Where
One of our own Debian 12 servers
Method
Every output block marked Debian 12 was run as shown, as root; nothing was installed or reconfigured, and the npm prefix test used a throwaway home directory
From vendor sources, not a test install
Debian 13, Ubuntu 24.04 and 26.04 package versions (packages.debian.org, packages.ubuntu.com), NodeSource package metadata, Homebrew paths (docs.brew.sh), fnm, mise, asdf and Volta setup lines (their docs), Debian 13 crontab manual, macOS and Windows steps

Frequently asked

Why does my Mac say 'zsh: command not found: npm'?

macOS does not include Node.js, and npm comes with Node. Install it with Homebrew ('brew install node') or the installer from nodejs.org. If Node is installed, zsh is not loading its directory: on Apple Silicon add Homebrew's shellenv line to ~/.zprofile, and for nvm put its three setup lines in ~/.zshrc, because zsh never reads ~/.bashrc or ~/.bash_profile. Open a new terminal and check with 'command -v npm'.

Do I need to install npm separately from Node.js?

Not with the official builds: the nodejs.org installers, Homebrew, nvm and NodeSource all include npm. Debian, Ubuntu, Arch and Alpine package it separately, so 'apt install nodejs' gives you node without npm; add the npm package, or install Node from NodeSource, which already contains npm.

Why does node work but npm is not found?

Almost always a distribution package without npm. Run 'sudo apt install npm' on Debian or Ubuntu, 'sudo pacman -S npm' on Arch or 'apk add npm' on Alpine. On Debian 12 and Ubuntu 24.04 that still means Node 18, so switching to NodeSource is usually the better fix; do not install the distribution npm on top of NodeSource, its nodejs package conflicts with it.

How do I fix 'sudo: npm: command not found'?

sudo replaces your PATH with secure_path from /etc/sudoers, which does not include nvm's directory in your home. 'sudo -E' does not change that. Run npm without sudo when Node comes from nvm, pass the PATH for one command with 'sudo env "PATH=$PATH" npm ...', or install Node system-wide with NodeSource so npm is in /usr/bin.

Why is npm not found in cron or a systemd service?

cron and systemd never read ~/.bashrc, ~/.zshrc or ~/.profile, so the lines that load nvm do not run. Use a system-wide Node in /usr/bin, or give the job the full path: a literal PATH line in the crontab (variables there are not expanded), or Environment=PATH plus an absolute ExecStart path in the unit.

Related articles

All troubleshooting