Why you're seeing this (the short version)
npm needs write access to its global install directory. On a fresh macOS or Linux setup, that directory is usually owned by root — not by your user account. So when you run npm install -g, the OS refuses and throws EACCES: permission denied.
The knee-jerk reaction is to prepend sudo. Don't. That masks the problem and gives npm root privileges it has no business having. Every global package you install becomes root-owned, and later you'll hit permission errors even when you're not installing anything.
Below are the three most common causes and the fix for each, in the order you should try them.
Cause #1: The global npm directory is owned by root
This is the classic. You installed Node.js from the official .pkg installer on macOS, or via apt/yum on Linux, and it placed npm in a system directory like /usr/local/lib/node_modules or /usr/lib/node_modules. That directory is root-owned.
The recommended fix — and the one the npm docs themselves suggest — is to change ownership of that directory to your user.
Step-by-step
- Find npm's global root:
npm config get prefixOn macOS that usually returns /usr/local. On Linux it's often /usr.
- Take ownership of the npm-related subdirectories:
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}If you're on Linux and the prefix is /usr, run this instead:
sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules
sudo chown -R $(whoami) $(npm config get prefix)/binYou may also need $(npm config get prefix)/share on some setups.
What's actually happening here: you're taking ownership of the directories where npm writes global packages and symlinks. Once they're yours, npm doesn't need root anymore.
Why not just sudo?
Using sudo npm install -g works once, but every subsequent npm operation that touches a global package (like npm update -g or npm ls -g) will complain because the cache or logs are owned by root. You're creating a mess you'll debug later. Skip it.
Cause #2: You're using a Node version manager (nvm, n, fnm) incorrectly
If you installed Node via nvm, the global directory lives inside your home folder, e.g., ~/.nvm/versions/node/v20.11.0/lib/node_modules. That should be owned by you already. So why the EACCES?
The usual culprit is that you switched Node versions and now you're running an npm that points to a different prefix. Or — more likely — you're still using a system-installed npm because your shell's PATH has the system location before nvm's shim.
Verify npm is actually nvm's
which npmIf it returns something like /usr/local/bin/npm and you have nvm installed, your PATH is wrong. The fix:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"Add those lines to your ~/.bashrc, ~/.zshrc, or ~/.profile — whichever your shell uses. Then reload:
source ~/.zshrc # or ~/.bashrcNow which npm should point into ~/.nvm/.... The EACCES disappears because everything is in your home directory.
If you're not using nvm but your prefix points to /usr/local
Sometimes you install Node via Homebrew, but npm's prefix is still set to /usr/local. Homebrew now uses /opt/homebrew (Apple Silicon) or /usr/local (Intel) — but the ownership is on Homebrew's directory, not npm's default. Quick fix: point npm's prefix at a directory you own.
mkdir -p ~/.npm-global
npm config set prefix '~/.npm-global'Then add that to your PATH:
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc
source ~/.zshrcNow all global installs go to ~/.npm-global — no permissions involved. This is the cleanest workaround if you can't or won't change ownership of system directories.
Cause #3: npm's cache directory is owned by root
Less common, but it happens when you accidentally ran sudo npm install before. That created ~/.npm — wait, no. When you use sudo, npm runs as root, and root's home is /var/root (macOS) or /root (Linux). So the root-owned cache isn't in your home at all. The EACCES shows up when npm tries to write to your actual cache directory and finds a file or folder owned by root from an earlier mistake.
Check your cache location:
npm config get cacheUsually it's ~/.npm. If you see permission errors pointing to _cacache or _logs inside it, someone (past you) ran sudo and polluted it.
Fix it by removing the cache entirely — it's just a cache, it rebuilds:
rm -rf ~/.npm
npm installThat wipes the whole cache and forces fresh downloads. You lose no installed packages; it only re-downloads tarballs on the next install. This is safe and often fixes weird EACCES spooks.
If you still get an error after removing the cache, check if the directory that contains ~/.npm is writable:
ls -ld ~
ls -ld ~/.npm 2>/dev/null || echo "~/.npm doesn't exist"Your home directory should be drwxr-xr-x and owned by you. If it's owned by root, that's a bigger problem — usually from a bad user setup — and you'd need to fix it with sudo chown -R $(whoami) ~, but be careful with that on a system with other users.
Quick-reference summary
| Cause | Diagnosis | Fix |
|---|---|---|
| Global npm dir owned by root | npm config get prefix shows /usr/local or /usr; ls shows root | sudo chown -R $(whoami) prefix/lib/node_modules |
| Wrong npm (system vs nvm) | which npm doesn't point into ~/.nvm | Fix PATH, load nvm in shell rc |
| Root-owned npm cache | Error mentions ~/.npm/_cacache | rm -rf ~/.npm |
| Don't want to chown system dirs | You want a user-level prefix | npm config set prefix '~/.npm-global' and add to PATH |
That's the full picture. Start with Cause #1 — it's the one that hits 80% of people. Cause #3 is your fallback when chowning doesn't stick. And if you're on a managed system where you can't sudo, Cause #2's prefix trick will get you unblocked without touching anything root owns.
One more thing: after you fix the ownership, run npm install -g npm@latest once to make sure everything works. If that succeeds, you're done — and you never need sudo again.