⚡ Fastest fix
npm can't write to a directory it doesn't own. Don't reach for sudo — it makes the next install fail. Install Node with a version manager so everything lives in your home directory:
# macOS / Linux — nvm puts Node under your $HOME (no root, no EACCES)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
nvm install --lts
npm install -g whatever # now writes to a folder you own
What you're seeing
npm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /usr/local/lib/node_modules/pkg
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/pkg'
npm ERR! [Error: EACCES: permission denied] { errno: -13, code: 'EACCES' }
The line that matters is npm ERR! path — it names the exact directory npm couldn't write to. That tells you which fix applies.
30-second triage
- Path is
/usr/local/libor/usr/lib(a global-ginstall)? → Fix 1 (version manager) or Fix 2 (own prefix) - Path is under
~/.npm(the cache)? → Fix 3 (fix cache ownership) — a pastsudodid this - Path is your project's
node_modules? → Fix 3 then reinstall clean - Want the cleanest long-term setup? → Fix 1
Fix 1 — Install Node with a version manager (recommended)
When: almost always — it removes the root-owned directories entirely.
# pick one:
# nvm — most common
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# fnm — fast, cross-platform
curl -fsSL https://fnm.vercel.app/install | bash
# Volta — pins versions per project
curl https://get.volta.sh | bash
nvm install --lts && nvm use --lts
node -v # now from ~/.nvm, owned by you
A version manager installs Node and all global packages under $HOME. Because your user owns that tree, npm install -g can never hit EACCES and you never type sudo again — plus you can switch Node versions per project.
Fix 2 — Move npm's global prefix to a folder you own
When: you can't (or don't want to) reinstall Node but still need global installs.
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
# add to ~/.zshrc or ~/.bashrc, then reopen the shell
export PATH=~/.npm-global/bin:$PATH
npm install -g eslint # writes to ~/.npm-global — no EACCES
This keeps the system Node but redirects global packages into your home directory. The one gotcha: remember to add the new bin to PATH, or the installed commands won't be found.
Fix 3 — Reclaim files a past sudo left root-owned
When: EACCES on ~/.npm or a project after you previously ran sudo npm.
# give the cache back to your user
sudo chown -R $(whoami) ~/.npm
# for a project poisoned by sudo, start clean
rm -rf node_modules package-lock.json
npm install # no sudo this time
Once root owns files in your cache or project, every normal install fails because npm can't overwrite them. Fixing ownership (and never using sudo npm again) clears it for good.
The one thing not to do
# ❌ makes it "work" once, then breaks the next normal install
sudo npm install -g pkg
# ❌ and this hands your whole system dir to your user — messy and unsafe
sudo chown -R $(whoami) /usr/local/lib/node_modules
sudo runs install scripts as root and drops root-owned files into your cache, guaranteeing a future EACCES. Use Fix 1 or Fix 2 instead.
Why this happens
Unix permissions let a directory be written only by its owner (or root). When Node is installed system-wide — from a .pkg, from apt, or from the macOS installer — its global node_modules prefix lives somewhere like /usr/local/lib that is owned by root. A normal npm install -g runs as you, so the kernel denies the write and npm reports EACCES (error number 13, "permission denied"). Local installs usually work because your project folder is yours. The permanent fix is to make sure everything npm writes to is a directory your user owns — which is exactly what a version manager or a relocated prefix guarantees.
By platform
| Where Node came from | Typical EACCES path | Best fix |
|---|---|---|
macOS .pkg installer | /usr/local/lib/node_modules | Fix 1 (nvm) |
Linux apt / dnf | /usr/lib/node_modules | Fix 1 or Fix 2 |
| Homebrew | /opt/homebrew/lib | Fix 2 (own prefix) |
| Previously used sudo | ~/.npm (cache) | Fix 3 (chown) |
✓ Confirm it's fixed
npm config get prefixshould print a path under your home directory (not/usr).npm install -g cowsayshould succeed with nosudoand the command should run.ls -la ~/.npm— every entry should be owned by your username, none byroot.
Frequently Asked Questions
What does 'npm ERR! code EACCES: permission denied' mean?
EACCES is the operating system's "access denied" error. npm tried to create or write a file in a directory your user account doesn't own — usually the global node_modules prefix (like /usr/local/lib) or the npm cache — and the OS refused. It is a filesystem permission problem, not a broken package.
Why shouldn't I just use sudo npm install?
sudo makes the install succeed by running as root, but it creates root-owned files in your cache and project, which cause new EACCES errors on the next normal install and are a security risk (install scripts run as root). The recommended fix is to install Node with a version manager or point npm's global prefix at a directory you own, so sudo is never needed.
What is the recommended way to fix npm EACCES?
Install Node through a version manager such as nvm, fnm, or Volta. These put Node and global packages under your home directory, which your user owns, so EACCES cannot happen and you never need sudo. It also lets you switch Node versions per project.
How do I fix EACCES without reinstalling Node?
Set npm's global prefix to a folder you own: mkdir ~/.npm-global && npm config set prefix '~/.npm-global', then add ~/.npm-global/bin to your PATH. Global installs then write to your home directory. If earlier sudo installs left root-owned files, fix ownership of ~/.npm and the prefix with chown first.
I already used sudo and now EACCES is worse — how do I recover?
Earlier sudo runs left root-owned files in ~/.npm and node_modules. Reclaim them: sudo chown -R $(whoami) ~/.npm and, for a project, delete node_modules and package-lock.json then reinstall without sudo. After that, switch to a version manager so it doesn't recur.
Why does EACCES happen only on global installs (npm install -g)?
Local installs write into your project's node_modules, which you own, so they usually succeed. Global installs (-g) write to the system prefix (e.g. /usr/local or /usr) owned by root, which triggers EACCES. Moving the global prefix to your home directory removes the distinction.
More npm & Node.js errors
Browse the full reference for npm, Node.js, and build errors — exact message, cause, and fix.