MervCodes

Tech Reviews From A Programmer

How to Fix Node Modules Permission Errors on Mac (npm EACCES)

8 min read

If you've been developing on Mac for more than a week, you've probably hit this wall: npm ERR! code EACCES followed by permission denied errors when trying to install packages globally or locally. It's one of the most frustrating friction points for Node.js developers on macOS, but the fix is straightforward once you understand what's happening.

TL;DR — Quick Fixes

The fastest solution: Use Node Version Manager (nvm) to manage Node.js installations. This is the industry standard and solves 95% of permission issues.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install node
npm install -g your-package-name

If you need an immediate fix without reinstalling Node: Change npm's default directory.

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH

For a one-off global install without the error, use sudo sparingly (not recommended long-term).


What Causes npm EACCES Errors on Mac?

The root cause is simple: npm's default global directory (/usr/local/lib/node_modules) requires root/admin permissions, but npm runs as your user account. This permission mismatch creates a conflict that npm can't resolve, and your installation fails.

When you installed Node.js via the official installer or Homebrew, npm automatically configured itself to install global packages to a protected system directory. Your Mac's security model (Unix permissions) says you don't have write access there—even though you're the admin. This gap between expected permissions and actual permissions is what triggers EACCES (Error: Access).

This typically happens when:

  • Installing global CLI tools like create-react-app, typescript, or eslint
  • Running npm install -g for development dependencies
  • Using outdated Node.js installations that predate modern best practices
  • Mixing Homebrew, system Node, and manual installations (permission chaos)

Solution 1: Use nvm (Node Version Manager) — Recommended

nvm is the gold standard for Mac Node.js development. It installs Node.js into your user home directory (no sudo needed) and manages multiple versions simultaneously. 95% of permission issues vanish once you switch.

Install nvm

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

Close and reopen your terminal, then verify installation:

command -v nvm

Install Node.js with nvm

# Install the latest LTS version
nvm install --lts

# Or install a specific version
nvm install 20.10.0

# Set as default
nvm alias default node

Verify npm Works Without sudo

npm install -g typescript
# Should work without EACCES errors

Why this works: nvm installs Node.js to ~/.nvm/versions/node/vX.X.X/, which lives entirely in your home directory. Since you own your home directory, npm has full write permissions. No sudo needed, no permission conflicts.

If you used Homebrew or the official installer: Uninstall first to avoid conflicts.

# If installed via Homebrew
brew uninstall node npm

# If installed via official installer, use the uninstaller from /usr/local/bin

Solution 2: Change npm's Default Directory (Quick Fix)

If you can't reinstall Node.js right now, you can reconfigure npm to use a directory in your home folder where you already have permissions.

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'

Then add the directory to your PATH so commands work globally:

# Add this to ~/.zshrc (or ~/.bash_profile if using bash)
export PATH=~/.npm-global/bin:$PATH

Reload your shell:

source ~/.zshrc

Verify it worked:

npm config get prefix
# Should return: /Users/yourname/.npm-global

Now install globally without errors:

npm install -g create-react-app

Trade-offs: This works, but you're still using a system Node.js installation. You lose version management and can't easily switch between Node versions. Use this as a temporary fix, then migrate to nvm.


Solution 3: Fix Permissions on /usr/local (Not Recommended)

Some tutorials suggest changing ownership of /usr/local to your user. While it works, it's a security anti-pattern and can break system package managers.

# NOT RECOMMENDED - Don't do this
sudo chown -R $(whoami) /usr/local/lib/node_modules

Why not: You're lowering security on a system directory that Homebrew and other tools depend on. Future system updates or tool installations can fail or create conflicts. This is a band-aid, not a solution.


Solution 4: Use sudo (Last Resort)

If you're in a hurry and can't reconfigure npm:

sudo npm install -g your-package-name

Critical caveat: This works once, but creates long-term problems. Global packages installed with sudo can't be easily updated or removed without sudo. Subsequent npm commands may still error if permissions drift. New developers on your team will repeat the same mistake.

Only use sudo if you're on a deadline. Fix it properly afterward with nvm.


Diagnosing the Real Problem

Run this command to see what's actually happening:

npm config list -l | grep prefix

If the output shows /usr/local and you're getting permission errors, that's your culprit. After applying any solution above, re-run this command to verify the prefix changed.

You can also check which Node installation npm is using:

which node
which npm

If which node points to /usr/local/bin/node, you're using a system installation. If it points to ~/.nvm/versions/node/..., you're using nvm (correct).


Setting Up a Team Best Practice

If you're onboarding developers or setting up a team, standardize on nvm. Add this to your project README:

## Local Development Setup

1. Install nvm: https://github.com/nvm-sh/nvm
2. Install Node.js: `nvm install --lts`
3. Install dependencies: `npm install`

Do not use system Node.js or Homebrew Node. This ensures consistent environments across developers.

Better yet, add a .nvmrc file to your repo to lock Node.js version:

node --version > .nvmrc

Team members run nvm install to automatically use the correct version. When you're deploying to AWS or containerizing with Docker (see our guide on Docker Compose for Local Development), you'll want this consistency anyway.


Avoiding Permission Errors in CI/CD Pipelines

If you're hitting permission errors in GitHub Actions or similar pipelines, you're likely using the wrong Node.js installer in your CI step. Always use actions that respect nvm or install Node cleanly.

For AWS EC2 deployments, check out How to Deploy a Node.js App to AWS EC2 for best practices on permission setup in production environments.


Common Mistakes to Avoid

1. Installing Node via system package manager and expecting it to work painlessly System Node.js installations on Mac are outdated and permission-heavy. Switch to nvm immediately.

2. Mixing installation methods Don't install Node via both Homebrew and nvm. Pick one. nvm is superior for development.

3. Using sudo for global packages as a permanent solution Every sudo npm compounds the problem. Fix the root cause instead.

4. Not clearing npm cache after permission changes After reconfiguring npm, clear the cache to avoid stale metadata:

npm cache clean --force

Final Recommendation

Use nvm. Full stop. It's not an overcomplicated tool—it's the standard. Every Node.js developer on Mac should use it. It solves permission errors, enables version switching, and aligns your local environment with how production deploys work.

The 5 minutes it takes to install nvm saves you hours of debugging permission issues and version conflicts down the road.

# One-time setup
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install --lts

# Never think about npm permissions again

What's Next?

Once you've fixed permissions, level up your Node.js development:

And if you're setting up development environments for a team or evaluating hosting solutions, Adaptels specializes in custom Node.js deployments for growth-stage companies.

Sources

  1. Node.js Documentation
  2. MDN Web Docs
  3. npm Documentation