npm CLI npm permissions global-install

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:

  1. 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 global node_modules directory in system locations, such as /usr/local, /opt, or /usr. These directories are generally owned by the root user to maintain system integrity.
  2. Regular User Execution: When a standard user (i.e., not root) executes an npm install -g command, npm attempts to write new package files into these root-owned global directories. Since the user doesn't have write permissions to these locations, the operating system intervenes, resulting in the EACCES error.
  3. 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.
  4. Misuse of sudo: A common mistake, in my experience, is using sudo npm install -g <package> initially to "fix" the problem. While this command succeeds because sudo temporarily grants root privileges, it leaves the newly installed global packages (and potentially their containing directories) owned by root. Subsequent npm commands run without sudo by a regular user will then again face EACCES when trying to modify these root-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 .pkg installer that placed files in /usr/local.
  • Initial sudo npm install -g: You encountered an EACCES error, searched for a fix, and were advised to use sudo npm install -g. This fixed the immediate issue but created permission problems for future global packages installed by your regular user.
  • Incorrect npm Global Prefix: The default npm global 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) or n. Depending on your PATH configuration, 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 Dockerfile runs npm install -g as root (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:

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.

  1. Remove any existing system-wide Node/npm installations (optional but recommended for a clean slate):
    If you previously installed Node.js via apt, yum, or brew, 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.

  2. Install NVM:
    Follow the official NVM installation instructions. This typically involves running a curl or wget script:
    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.

  3. 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, node and npm executables 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.

  4. Verify npm prefix:
    bash npm config get prefix
    This should now point to a path within your ~/.nvm directory.

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.

  1. Create a directory for global installations:
    bash mkdir ~/.npm-global

  2. Configure npm to use this new directory:
    bash npm config set prefix '~/.npm-global'

  3. Add the new directory to your PATH environment 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

  4. Verify npm prefix and PATH:
    bash npm config get prefix echo $PATH
    Ensure ~/.npm-global is correctly configured and in your PATH.

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.

  1. Find your npm global prefix:
    bash npm config get prefix
    This will likely output /usr/local or similar. The actual global node_modules directory would then be /usr/local/lib/node_modules.

  2. 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 the lib/node_modules, bin, and share subdirectories 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 chown is better than chmod 777, 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 Dockerfile and switch to it before running npm install or npm install -g.
    ```dockerfile
    FROM node:lts-alpine

    WORKDIR /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/.npm

    Switch 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 nvm within your CI/CD script if global packages are required. Most CI environments allow you to source a setup script for nvm.
  • Alternative: If not using nvm, ensure the global npm prefix 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 ci is preferred over npm install in 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 root unless absolutely necessary, and never run npm install -g as root for 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.

(none)