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
| Goal | Prefer |
|---|---|
| CLI for one project (eslint, prettier) | npm i -D eslint then npx eslint . or an npm script |
| Same tool across many projects | nvm + npm i -g, or Corepack for Yarn/pnpm |
| Package managers themselves | corepack 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 .