Fixing the Node.js EACCES Permission Denied Error: A Complete Troubleshooting Guide
If you’ve spent any meaningful time building JavaScript applications, you’ve probably run into the dreaded nodejs eacces permission denied error at least once. It usually shows up at the worst possible moment — right after running npm install, when you’re trying to start your server, or worse, during a production deployment. I remember the first time I saw this error on a client’s Ubuntu server at 2 AM, and I spent an embarrassing amount of time Googling random terminal commands before understanding what was actually happening.
This guide walks you through exactly what this error means, why it happens, and how to fix it permanently — whether you’re on macOS, Linux, or Windows. I’ll cover the most common causes first, then dig into edge cases that most blog posts skip.
What Is the Node.js EACCES Permission Denied Error?
EACCES is a POSIX error code that stands for “permission denied.” In Node.js, it surfaces whenever your process tries to read, write, or execute something the current user isn’t authorized to access. Node wraps this under the ERR_FS_EACCES system error, and you’ll typically see something like this in your terminal:
Error: EACCES: permission denied, open '/usr/local/lib/node_modules/package/package.json'
at Object.openSync (fs.js:476:3)
at Object.readFileSync (fs.js:377:35)
...
errno: -13,
syscall: 'open',
code: 'EACCES',
path: '/usr/local/lib/node_modules/package/package.json'
The key pieces of information here are:
- errno: -13 — the numeric POSIX error code for EACCES
- syscall: ‘open’ — the operation that was attempted
- path — the file or directory the process tried to access
Understanding these three pieces is critical because the fix depends entirely on which path is causing the issue and what kind of operation failed.
Common Causes of the EACCES Error
Before jumping into fixes, let’s identify why this happens. The nodejs eacces permission denied error almost always traces back to one of these root causes:
1. Global npm Installs Requiring Root
The number one cause by far. When you install Node.js via a package manager like Homebrew or apt, the global node_modules directory often lives under /usr/local/lib or /usr/lib, which regular users can’t write to. Running npm install -g <package> without sudo then fails with EACCES.
2. Incorrect Directory Ownership
If you previously ran npm commands with sudo, those files now belong to the root user. When you later try to access them as a normal user, the OS blocks you. This is especially common with ~/.npm cache directories.
3. File Permission Mismatches
Files copied from Docker containers, WSL distributions, or network shares often arrive with restrictive permissions. Node then can’t read or modify them.
4. Port Conflicts on Privileged Ports
A less obvious cause: trying to bind your app to a port below 1024 (like 80 or 443) without root privileges triggers EACCES on Linux and macOS.
5. IDE or Editor Locks
On Windows especially, having a file open in another process can cause permission errors when Node tries to write to it.
How to Diagnose Your Specific EACCES Error
Don’t skip diagnosis. Throwing sudo at every command is a bad habit that creates security risks and breaks things in subtle ways. Instead, read the actual error.
Step 1: Identify the Failing Path
Look at the path field in your error message. This tells you exactly what Node is trying to access. For example:
/usr/local/lib/node_modules/...→ global npm packages/home/user/.npm/_cacache/...→ npm cache directory/var/www/myapp/logs/app.log→ application log files0.0.0.0:80→ port binding issue
Step 2: Check Current Permissions
Use ls -la to inspect the permissions on the failing path:
ls -la /usr/local/lib/node_modules/
You’ll see output like:
drwxr-xr-x 3 root root 4096 Jan 15 10:22 some-package
The first column shows permissions (drwxr-xr-x), followed by owner (root) and group (root). If you’re not root and not in the root group, you can’t write here.
Step 3: Check Your Current User
whoami
id
This shows your username and the groups you belong to. If your global npm directory is owned by root and you’re not in the root group or using sudo, that’s your problem.
Solutions Ranked by Reliability
Solution 1: Change npm’s Default Directory (Recommended for macOS/Linux)
This is the official npm recommendation and my personal favorite. Instead of fighting with system-owned directories, configure npm to install global packages into a directory you own.
# Create a hidden directory for global packages
mkdir ~/.npm-global
# Configure npm to use it
npm config set prefix '~/.npm-global'
# Add it to your PATH (bash users)
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# Zsh users (default on newer macOS)
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc
source ~/.zshrc
Now npm install -g <anything> works without sudo. This is the cleanest fix because it never touches system files.
Solution 2: Use a Node Version Manager (nvm or fnm)
Version managers like nvm or fnm install Node entirely inside your home directory. Since you own everything, permission issues disappear.
Installing nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
Then activate and install Node:
# Reload shell or run:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Install latest LTS
nvm install --lts
nvm use --lts
I switched to fnm on my development machines because it’s written in Rust and noticeably faster than nvm. Either works — the key point is that they sidestep the permission problem entirely.
Solution 3: Fix Ownership of npm Directories
If you’ve already created a mess with sudo npm install, you can reclaim ownership:
# Take ownership of the npm directories (macOS/Linux)
sudo chown -R $(whoami) ~/.npm
sudo chown -R $(whoami) ~/.config
sudo chown -R $(whoami) /usr/local/lib/node_modules
sudo chown -R $(whoami) /usr/local/bin
sudo chown -R $(whoami) /usr/local/share
Be careful with this — taking ownership of /usr/local/bin affects more than just Node. Some systems use this directory for other tools too.
Solution 4: Clear the npm Cache
Sometimes corrupted cache entries cause permission errors even after you fix directory ownership. Force-clear it:
npm cache clean --force
If that itself fails with EACCES, manually remove the cache directory:
# Linux/macOS
rm -rf ~/.npm/_cacache
# Windows
rmdir /s /q "%APPDATA%\npm-cache"
Solution 5: Fix File Permissions on Specific Files
For project-specific files, use chmod to grant proper permissions:
# Make a file readable/writable by the owner
chmod 644 path/to/file
# Make a directory and its contents accessible
chmod -R 755 path/to/directory
# Make a script executable
chmod +x scripts/build.sh
Permission codes are octal values:
– 7 = read + write + execute (4+2+1)
– 6 = read + write
– 5 = read + execute
– 4 = read only
The three digits represent owner, group, and others respectively. So 755 means owner has full access, while group and others can read and execute.
Solution 6: Handle Privileged Port Errors
If your error looks like Error: listen EACCES 0.0.0.0:80, you’re trying to bind to a privileged port. You have three options:
Option A: Use a non-privileged port (recommended for development)
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Option B: Grant Node permission to bind to lower ports
On Linux, use setcap:
# Find your node binary path
which node
# Grant it the cap_net_bind_service capability
sudo setcap cap_net_bind_service=+ep $(which node)
Option C: Run behind a reverse proxy
Use nginx or Caddy to listen on port 80/443 and proxy to your Node app on a higher port. This is the production-grade solution.
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Solution 7: Fix Permissions Inside Docker Containers
When working with containers, EACCES errors often come from running Node as a non-root user without proper ownership setup. Here’s a working Dockerfile pattern:
FROM node:20-alpine
# Create app directory
WORKDIR /app
# Copy package files and install deps as root
COPY package*.json ./
RUN npm ci --only=production
# Create non-root user and transfer ownership
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
RUN chown -R nextjs:nodejs /app
# Copy app source as root, then fix ownership
COPY --chown=nextjs:nodejs . .
# Switch to non-root user
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
This pattern keeps your container secure while avoiding EACCES issues.
Solution 8: Windows-Specific Fixes
On Windows, the nodejs eacces permission denied error often looks slightly different but stems from similar causes.
Fix npm global path on Windows:
Open PowerShell as Administrator and run:
# Create a directory for global packages
mkdir "$env:APPDATA\npm"
# Set npm prefix
npm config set prefix "$env:APPDATA\npm"
# Add to PATH (persistent)
[Environment]::SetEnvironmentVariable(
"PATH",
"$env:APPDATA\npm;$env:PATH",
"User"
)
Restart your terminal afterward.
Fix file locking issues:
If a specific file is locked, identify the process holding it:
# Using handle.exe from Sysinternals
handle.exe filename.log
# Or use PowerShell (Windows 10+)
Get-Process | Where-Object {$_.Modules.FileName -like "*filename*"}
Close the offending application (often VS Code, antivirus, or another Node process).
Solution 9: Fix WSL Permission Issues
If you’re using WSL2 and accessing files on the Windows filesystem (/mnt/c/...), permissions can be weird because of how WSL translates NTFS permissions. The fix is to either:
- Keep your project inside the WSL filesystem (
~/projects/...) — much faster too. - Configure
/etc/wsl.confto handle metadata:
sudo tee /etc/wsl.conf << EOF
[automount]
options = "metadata,umask=22,fmask=11"
EOF
Then restart WSL:
wsl --shutdown
Prevention Tips for Long-Term Sanity
Fixing the error is good. Preventing it from happening again is better. Here’s my checklist after years of running into this:
Use a Node Version Manager From Day One
I cannot emphasize this enough. Whether you choose nvm, fnm, Volta, or asdf — use something that installs Node into your home directory. This single decision eliminates 90% of permission issues.
Never Use sudo With npm
This is the biggest mistake developers make. sudo npm install -g permanently changes file ownership to root, creating a cascade of permission problems later. If you’re tempted to use sudo, something is wrong with your setup. Fix the setup, not the symptom.
Use a .npmrc File for Project-Specific Config
For team projects, commit a .npmrc file:
# .npmrc
prefix=${HOME}/.npm-global
fund=false
audit=false
This ensures every developer on the team has a consistent npm setup.
Lock Down Production With Non-Root Users
In production, never run Node as root. Create a dedicated user:
sudo useradd -m -s /bin/bash nodeapp
sudo chown -R nodeapp:nodeapp /var/www/myapp
sudo -u nodeapp npm install --production
sudo -u nodeapp node server.js
Better yet, use systemd to manage this:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node.js App
After=network.target
[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Handle File Uploads Safely
If your app handles file uploads, ensure the upload directory exists and has proper permissions before writing to it:
const fs = require('fs').promises;
const path = require('path');
async function ensureUploadDir(dir) {
try {
await fs.access(dir);
} catch (err) {
if (err.code === 'ENOENT') {
await fs.mkdir(dir, { recursive: true, mode: 0o755 });
} else if (err.code === 'EACCES') {
throw new Error(`Cannot access upload directory: ${dir}. Check permissions.`);
} else {
throw err;
}
}
}
// Usage
await ensureUploadDir('./uploads');
Validate Permissions Programmatically
For robust error handling, wrap file operations and check for EACCES specifically:
async function safeWriteFile(filepath, data) {
try {
await fs.writeFile(filepath, data);
return { success: true };
} catch (err) {
if (err.code === 'EACCES') {
console.error(`Permission denied writing to ${filepath}`);
console.error('Check file ownership and permissions.');
// Maybe fall back to a temp directory
const tmpPath = path.join(require('os').tmpdir(), path.basename(filepath));
await fs.writeFile(tmpPath, data);
return { success: true, fallback: tmpPath };
}
throw err;
}
}
Debugging Checklist
When the nodejs eacces permission denied error shows up, run through this checklist before doing anything else:
- Read the error message — what path is mentioned?
- Check who owns that path —
ls -laon the parent directory - Check your current user —
whoamiandid - Check if sudo was involved — look at file ownership patterns
- Verify your Node installation method — system package manager vs. version manager
- Check environment variables —
echo $PATHandnpm config get prefix - Look for Docker/WSL layer issues — if applicable
- Test with a minimal script — isolate the problem
Common Error Variations and Their Fixes
Different contexts produce slightly different error messages. Here are variations I’ve encountered:
EACCES: permission denied, mkdir '/usr/local/lib/node_modules'
You’re trying a global install without permissions. Use Solution 1 or 2 above.
EACCES: permission denied, open '/home/user/.npm/_logs/2024-01-15T...debug.log'
Your npm cache directory has wrong ownership. Fix with:
sudo chown -R $(whoami) ~/.npm
EACCES: permission denied, unlink '/usr/local/bin/somepackage'
A previous global install left a symlink owned by root. Remove it manually:
sudo rm /usr/local/bin/somepackage
Error: listen EACCES: permission denied 0.0.0.0:80
Privileged port issue. See Solution 6.
EACCES: permission denied, open '.env'
Project file ownership issue. Either fix permissions or check if the file exists and is readable.
EACCES: permission denied, scandir '/path/to/node_modules'
Your node_modules directory has inconsistent ownership. Blow it away and reinstall:
rm -rf node_modules package-lock.json
npm install
Key Takeaways
- The
nodejs eacces permission denied erroris always about file or directory ownership — never use random terminal commands without understanding the root cause - The best long-term fix is preventing the problem by using a Node version manager like nvm or fnm
- Never use
sudowith npm commands — it creates ownership issues that haunt you later - On Linux/macOS, configuring npm’s global prefix to a directory you own is the cleanest solution
- For production, always run Node as a non-root user with explicitly owned directories
- In Docker, set up proper ownership before switching to a non-root user with the
USERdirective - Always read the full error message — the path and syscall fields tell you exactly what’s wrong
- Implement defensive file operations in your code to