npm ERR! EACCES permission denied
Encountering npm ERR! EACCES permission denied means npm lacks write permission to the global node_modules directory; this guide explains how to fix it.
As a Platform Reliability Engineer, I've seen npm ERR! EACCES permission denied countless times, from local development machines to CI/CD pipelines and Docker containers. It's a common stumbling block for developers, often leading to frustration and the temptation to use sudo indiscriminately, which can cause more problems down the line. This guide will demystify this error and provide robust, maintainable solutions based on practical experience.
What This Error Means
At its core, npm ERR! EACCES permission denied signifies that the Node Package Manager (npm) process does not have the necessary permissions to write to a specific file or directory on your system. EACCES stands for "Error Access," and "permission denied" clearly indicates the operating system's security mechanism blocking the operation.
Specifically, when you encounter this error in the context of npm, it almost invariably means npm is trying to install or update a global package (using the -g flag) into a directory that is owned by another user, typically root. The default global installation directory for npm packages is usually /usr/local/lib/node_modules or similar system-owned paths, which regular user accounts do not have write access to.
You'll typically see this error message when attempting commands like:
* npm install -g <package-name>
* npm update -g <package-name>
* npm uninstall -g <package-name>
* Sometimes even npm install (without -g) if your project's node_modules directory has incorrect permissions, though this is less common for EACCES.
Why It Happens
The fundamental reason npm ERR! EACCES permission denied occurs is a mismatch between the user running the npm command and the ownership/permissions of the directory npm is trying to write to. Here's a breakdown:
- System-Wide Node/npm Installation: Often, Node.js and npm are installed system-wide via package managers (like
apt,yum,brew) or direct downloads. These installations place Node.js executables and npm's globalnode_modulesdirectory in system locations, such as/usr/local,/opt, or/usr. These directories are generally owned by therootuser to maintain system integrity. - Regular User Execution: When a standard user (i.e., not
root) executes annpm install -gcommand, npm attempts to write new package files into theseroot-owned global directories. Since the user doesn't have write permissions to these locations, the operating system intervenes, resulting in theEACCESerror. - Security Best Practices: This permission model is a crucial security feature. It prevents arbitrary users or processes from modifying core system files or installing programs that could compromise the system's stability or security. While inconvenient at times, it's there for a good reason.
- Misuse of
sudo: A common mistake, in my experience, is usingsudo npm install -g <package>initially to "fix" the problem. While this command succeeds becausesudotemporarily grants root privileges, it leaves the newly installed global packages (and potentially their containing directories) owned byroot. Subsequent npm commands run withoutsudoby a regular user will then again faceEACCESwhen trying to modify theseroot-owned packages.
Common Causes
Let's distill the specific scenarios that frequently lead to this error:
- Direct System Package Installation: You installed Node.js using
sudo apt install nodejs(Debian/Ubuntu),sudo yum install nodejs(CentOS/RHEL), or downloaded a.pkginstaller that placed files in/usr/local. - Initial
sudo npm install -g: You encountered anEACCESerror, searched for a fix, and were advised to usesudo npm install -g. This fixed the immediate issue but created permission problems for future global packages installed by your regular user. - Incorrect
npmGlobal Prefix: The defaultnpmglobal prefix (npm config get prefix) might point to a system directory (e.g.,/usr/local) that the current user doesn't have write access to. - Multiple Node.js Installations: You might have Node.js installed system-wide and also using a version manager like
nvm(Node Version Manager) orn. Depending on yourPATHconfiguration, you might be accidentally using the system-installed npm, which then tries to write to root-owned directories. - Docker Container User Permissions: In Docker, if your
Dockerfilerunsnpm install -gasroot(which is the default user inside a container unless specified), and later commands attempt to modify these packages as a non-root user (or if you mount a volume with incorrect host permissions), you'll hit this error. - CI/CD Pipeline Users: Similar to Docker, CI/CD runners often operate as specific, unprivileged users. If the CI/CD script tries to install global npm packages into a shared, system-owned directory, it will fail.
Step-by-Step Fix
The best approaches to fix npm ERR! EACCES permission denied involve avoiding sudo for global npm packages altogether. Here are the most robust and recommended methods, in order of preference:
Option 1 (Recommended): Use a Node Version Manager (NVM)
This is my go-to solution for local development environments and even many CI/CD setups. NVM allows you to install and manage multiple Node.js versions and their associated npm installations in your user's home directory. This completely bypasses system-wide permissions issues.
-
Remove any existing system-wide Node/npm installations (optional but recommended for a clean slate):
If you previously installed Node.js viaapt,yum, orbrew, remove it first. For example, on Ubuntu:
bash sudo apt purge nodejs npm sudo rm -rf /usr/local/lib/node_modules # remove global node_modules sudo rm -rf ~/.npm ~/.config/npm # clean npm caches and configs
Note: Adjust commands based on your OS and installation method. -
Install NVM:
Follow the official NVM installation instructions. This typically involves running acurlorwgetscript:
bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
After installation, close and reopen your terminal, or source your shell's config file (~/.bashrc,~/.zshrc, etc.) for NVM to be available. -
Install Node.js via NVM:
bash nvm install --lts # Installs the latest LTS version of Node.js nvm use --lts # Uses the installed LTS version nvm alias default --lts # Sets LTS as the default version for new shells
Now,nodeandnpmexecutables will be managed by NVM within your user's directory (~/.nvm). All global npm packages will be installed into~/.nvm/versions/node/<version>/lib/node_modules, which you own. -
Verify
npm prefix:
bash npm config get prefix
This should now point to a path within your~/.nvmdirectory.
Option 2: Change npm's Default Global Directory
If using NVM is not feasible (e.g., in a highly controlled environment or specific build systems), you can configure npm to install global packages into a directory you own.
-
Create a directory for global installations:
bash mkdir ~/.npm-global -
Configure npm to use this new directory:
bash npm config set prefix '~/.npm-global' -
Add the new directory to your
PATHenvironment variable:
You need to tell your shell where to find the executables of your globally installed packages.
Open your shell's configuration file (~/.bashrc,~/.zshrc,~/.profile, etc.) and add the following line:
bash export PATH=~/.npm-global/bin:$PATH
Then, apply the changes by sourcing the file or restarting your terminal:
bash source ~/.bashrc # or ~/.zshrc, ~/.profile -
Verify
npm prefixandPATH:
bash npm config get prefix echo $PATH
Ensure~/.npm-globalis correctly configured and in yourPATH.
Option 3 (Least Recommended): Fix Permissions of System Global Directory
This method involves changing the ownership or permissions of the system-wide global node_modules directory. While it fixes the immediate problem, it's generally discouraged because it modifies system-owned directories, which can have security implications and may be reverted by system updates. Only use this if you fully understand the risks and have no other options.
-
Find your npm global prefix:
bash npm config get prefix
This will likely output/usr/localor similar. The actual globalnode_modulesdirectory would then be/usr/local/lib/node_modules. -
Change ownership of the global directory to your user:
bash sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
This command recursively changes the ownership of thelib/node_modules,bin, andsharesubdirectories within the npm prefix to your current user. Replace$(whoami)with your actual username if you're executing this as a different user.A note from my experience: While
chownis better thanchmod777, this still means a regular user now owns parts of/usr/local, which can lead to unexpected behavior if other system processes expect root ownership.
Strongly Discouraged: Using sudo npm install -g
I cannot stress this enough: avoid using sudo npm install -g <package-name>. It's a quick fix that often creates more problems than it solves, leading to a tangled mess of root-owned files that will cause EACCES errors again in the future. It's a security risk, allowing arbitrary packages to run with root privileges. Choose one of the above, more sustainable solutions.
Code Examples
Here are some ready-to-use code snippets for the recommended solutions.
NVM Installation and Usage
# Install nvm (latest stable version)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# Source your shell config (or restart terminal)
source ~/.bashrc # or ~/.zshrc, ~/.profile
# Install the latest LTS version of Node.js
nvm install --lts
# Use the installed LTS version
nvm use --lts
# Set LTS as default for new shell sessions
nvm alias default --lts
# Install a global package (no sudo needed!)
npm install -g pnpm
Changing npm's Default Global Directory
# 1. Create a dedicated directory in your home folder
mkdir ~/.npm-global
# 2. Configure npm to use this directory for global installs
npm config set prefix '~/.npm-global'
# 3. Add this directory to your shell's PATH
# (Add this line to ~/.bashrc, ~/.zshrc, or ~/.profile)
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc # Example for Bash
# 4. Apply changes (or restart terminal)
source ~/.bashrc # or ~/.zshrc, ~/.profile
# 5. Verify the prefix and PATH
npm config get prefix
echo $PATH
# 6. Install a global package (no sudo needed!)
npm install -g yarn
Fixing System Directory Permissions (Use with Caution)
# Identify the problematic global npm prefix
npm config get prefix
# Example output might be /usr/local
# Change ownership of the global npm directories to your user
# Replace $(whoami) with your actual username if you're not the current user
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
# Verify ownership (example for /usr/local/lib/node_modules)
ls -ld /usr/local/lib/node_modules
# Expected output: drwxr-xr-x <your_username> staff ...
Environment-Specific Notes
The context in which you encounter EACCES can influence the best solution.
Local Development
For personal development machines, NVM (Node Version Manager) is hands down the most robust and flexible solution. It isolates Node.js installations to your user directory, eliminates sudo concerns for global packages, and makes managing multiple Node.js versions trivial. I've found this to be the most common and clean solution across my teams.
Docker Containers
In Docker, EACCES often happens when npm install -g is run as the root user (the default in many base images), but later processes or subsequent npm commands inside the container try to modify those files as a non-root user.
-
Best Practice: Always define a non-root user in your
Dockerfileand switch to it before runningnpm installornpm install -g.
```dockerfile
FROM node:lts-alpineWORKDIR /app
Create a non-root user
RUN addgroup -g 1000 nodeuser && adduser -u 1000 -G nodeuser -s /bin/sh -D nodeuser
Copy package.json and package-lock.json
COPY package*.json ./
Ensure npm cache and global installs are writable by the non-root user
ENV NPM_CONFIG_PREFIX=/home/nodeuser/.npm-global
RUN mkdir -p /home/nodeuser/.npm-global && chown -R nodeuser:nodeuser /home/nodeuser/.npm-global
RUN mkdir -p /home/nodeuser/.npm && chown -R nodeuser:nodeuser /home/nodeuser/.npmSwitch to non-root user
USER nodeuser
Install dependencies
RUN npm install --omit=dev # for production builds
If global packages are strictly needed, install them here
RUN npm install -g your-global-package
COPY . .
CMD ["node", "src/index.js"]
```
This ensures all npm operations occur within a user-owned context inside the container.
CI/CD Pipelines
CI/CD runners (e.g., Jenkins, GitLab CI, GitHub Actions) usually execute tasks as a specific, often unprivileged, user.
- Recommended: Use
nvmwithin your CI/CD script if global packages are required. Most CI environments allow you to source a setup script fornvm. - Alternative: If not using
nvm, ensure the globalnpmprefix is configured to a writable directory within the build agent's workspace. Often, the build agent's home directory (~) is a safe bet, similar to Option 2 above. npm ciis preferred overnpm installin CI/CD, as it's designed for clean, reproducible builds and typically avoids global package installations unless explicitly specified for build tools.
Cloud Instances (AWS EC2, Google Compute Engine)
Similar to local development, installing Node.js and npm directly on a cloud instance often involves system-level installations.
- Best Practice: Implement NVM for non-root users on these instances. If a service needs Node.js, install it via NVM for the service user or ensure a dedicated non-root user manages its Node.js environment.
- Avoid running application processes as
rootunless absolutely necessary, and never runnpm install -gasrootfor application dependencies. I've seen teams struggle with permission issues on deployment servers for months because of root-owned global packages.
Frequently Asked Questions
Q: Is sudo npm install -g <package> always bad?
A: Generally, yes. While it might seem like a quick fix, it creates files and directories owned by the root user, leading to future permission problems for your regular user. It also introduces security risks by running arbitrary package scripts with elevated privileges. Prefer nvm or configuring a user-owned npm prefix.
Q: Why do some online guides suggest sudo as a fix?
A: sudo provides a simple, immediate way to bypass permission errors. Many guides offer it as a quick solution without fully explaining the underlying problem or the long-term consequences. As engineers, we aim for robust, sustainable solutions, not just quick fixes.
Q: Does nvm interfere with existing Node.js installations?
A: No, nvm is designed to be self-contained. It installs Node.js versions into its own directory (~/.nvm) and manages your PATH environment variable to point to the nvm-managed Node.js. It effectively allows nvm to manage Node.js without touching system-wide installations. If you switch to an nvm-managed version, your shell will use that. If you deactivate nvm or specify a system path, it can coexist.
Q: I have multiple users on my system. How do they each handle npm?
A: Each user should independently set up their Node.js environment. The most effective way is for each user to install and use nvm in their own home directory. This ensures each user has full control over their Node.js versions and global packages without interfering with others or requiring sudo.
Q: After fixing the permissions, I still see errors. What should I check next?
A:
1. Restart your terminal: Shell configuration changes (like PATH updates) often require a new shell session to take effect.
2. Verify your PATH: Run echo $PATH to ensure your nvm or ~/.npm-global/bin path is correctly listed and appears before any system Node.js paths.
3. Clear npm cache: Sometimes a corrupted cache can cause issues. Run npm cache clean --force.
4. Check specific package issues: If the error persists for a particular package, it might be a specific installation script issue rather than a general permission one. Check the package's documentation or issue tracker.
Related Errors
(none)