npm EACCES / EPERM permission denied on global install

Global npm installs write under /usr/local (or another system directory) that your user cannot change. The error is a filesystem permission problem, not a broken npm. Fix it by installing Node in your home directory, not by running sudo npm.

What the error looks like

npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /usr/local/lib/node_modules/…
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/…'

On Windows you may see EPERM instead. Same idea: npm tried to write somewhere the current user is not allowed to touch.

Why sudo npm is the wrong fix

sudo npm install -g … creates root-owned files in the global store and often in your cache. The next non-sudo npm command then fails harder, and a compromised package would run as root. Undo a past mistake with sudo chown -R $(whoami) ~/.npm (and never chown all of /usr/local on macOS — see SIP below).

Fix 1: a Node version manager (best default)

# nvm (bash/zsh)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
nvm install --lts
npm install -g npm-check-updates   # now writes under ~/.nvm/…

fnm and volta do the same job with faster shims. After install, which node should point inside your home directory, not /usr/bin/node.

Fix 2: npm prefix in your home directory

mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
# then add to ~/.profile or ~/.zshrc:
export PATH="$HOME/.npm-global/bin:$PATH"
# open a new shell, then:
npm install -g cowsay

Check with npm config get prefix and npm root -g. Avoid changing prefix while using nvm — the manager already owns that path.

Fix 3: you may not need a global install

GoalPrefer
CLI for one project (eslint, prettier)npm i -D eslint then npx eslint . or an npm script
Same tool across many projectsnvm + npm i -g, or Corepack for Yarn/pnpm
Package managers themselvescorepack enable then corepack prepare pnpm@latest --activate

Linux: /usr/local owned by root

Distro and NodeSource packages install Node so that /usr/local/lib/node_modules is root-owned. Changing those permissions invites the next apt upgrade to fight you. Use a version manager or a home prefix instead of chown on system paths.

macOS and SIP

System Integrity Protection keeps parts of the system read-only. Homebrew’s Node (brew install node) usually makes /opt/homebrew or /usr/local writable for your user for Homebrew-managed paths — if globals still fail, you likely mixed a pkg-installer Node with Homebrew. Stick to one install method; brew uninstall node leftovers plus nvm is a common cleanup.

Docker and CI

In images, install globals as the user that will run them, or better: install into the project and use npx. GitHub Actions and similar runners often already use a user-writable Node setup; if a step needs a global tool, prefer npm i -g after actions/setup-node (no sudo) or add the tool as a devDependency.

Related

npm can’t find a file at all (ENOENT) is a different failure: npm ERR! code ENOENT. A script itself refusing to run: Permission denied running script. The same “don’t install into the system copy” idea for Python is externally-managed-environment.

Questions

How do I fix npm EACCES permission denied?

Install Node with nvm, fnm or volta so global packages go in your home directory, or set npm's prefix to a folder you own (for example ~/.npm-global) and put its bin on PATH. Do not use sudo npm.

Why is sudo npm install -g a bad idea?

It creates root-owned files in the global store and cache, so the next normal npm command fails, and a malicious package would run with root rights.

What is the difference between EACCES and EPERM?

Both mean the OS refused the write. EACCES is the usual POSIX permission error; EPERM often appears on Windows or when a stronger restriction (flags, SIP) blocks the change.

Do I need global packages at all?

Often no. Install tools as devDependencies and run them with npx or npm scripts so each project pins its own versions.

Published .

Try it

Related guides

Newest guides