EACCES: permission denied

Fix EACCES permission denied on npm install (macOS/Linux)

Stop using sudo with npm. The real fix is fixing the directory ownership or switching to a Node version manager. Here's the order to try.

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

  1. Find npm's global root:
npm config get prefix

On macOS that usually returns /usr/local. On Linux it's often /usr.

  1. 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)/bin

You 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 npm

If 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 ~/.bashrc

Now 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 ~/.zshrc

Now 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 cache

Usually 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 install

That 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

CauseDiagnosisFix
Global npm dir owned by rootnpm config get prefix shows /usr/local or /usr; ls shows rootsudo chown -R $(whoami) prefix/lib/node_modules
Wrong npm (system vs nvm)which npm doesn't point into ~/.nvmFix PATH, load nvm in shell rc
Root-owned npm cacheError mentions ~/.npm/_cacacherm -rf ~/.npm
Don't want to chown system dirsYou want a user-level prefixnpm 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.

Related Errors in Programming & Dev Tools
net::ERR_BLOCKED_BY_CLIENT Fix 'net::ERR_BLOCKED_BY_CLIENT' in JavaScript when ad blockers interfere MissingPluginException Flutter MissingPluginException on iOS after dependency upgrade Module parse failed Webpack 5 Vue: Module parse failed Unexpected token fix Module not found: Error: Can't resolve Webpack 'Module not found' after npm update — fix

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.