TypeScript

Fix npm EACCES: permission denied on package-lock.json

Separate a local lockfile permission failure from cache and global-install errors, then fix the owner without changing the dependency tree.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

When npm reports EACCES while opening your project's package-lock.json, inspect that file before changing npm's global configuration. In the source incident, it belonged to root while the developer ran npm as an ordinary user. Give that personal checkout's lockfile back to the intended user, retain its contents, and retry the original install without sudo. A successful global install does not test write access to your local project.

The reported failure on Stack Overflow names the exact target:

text
npm ERR! Error: EACCES: permission denied, open '/home/user/express/todoapp-api/package-lock.json'

The question's directory listing shows the lockfile owned by root, with write permission for its owner only. The surrounding project files belong to the developer. That mismatch explains the supplied evidence; it does not mean every npm permission error has the same cause.

Why npm can read the lockfile but cannot update it#

The npm lockfile documentation explains that the file records the resolved dependency tree and is intended for version control. It is project data, not a system file that inherently requires administrator privileges. An install may need to write it even when reading the existing JSON succeeded.

Linux checks the access requested by the process. The open system-call manual lists denied file access and missing search permission on a parent directory among the reasons for EACCES. Being able to list a directory or display a JSON file is therefore insufficient evidence that npm can open that file for writing.

Read the whole failing path. If it points into the npm cache or the global package directory instead, follow the cache and global-prefix EACCES diagnosis. Changing the global prefix cannot repair an existing lockfile inside a separate checkout. Likewise, clearing downloaded package data does not change that file's owner.

Fix the owner of the actual project file#

1. Inspect the checkout as the user who runs npm#

Run these commands from the project directory in your normal Linux shell:

bash
pwd
id
ls -ld . ..
ls -l package.json package-lock.json

Compare the owner and group with the identity shown by the second command. Check the pathname against npm's error, especially if you keep multiple copies of the project. An editor terminal, a CI runner and a container shell can refer to different checkouts even when their prompts look similar.

For the reported case, the evidence is specific: the developer owns the working directory, but root owns the lockfile. Do not apply a recursive ownership change to your home directory or a system folder to solve this one-file mismatch. In a shared checkout, establish the intended service account or shared group before changing anything.

2. Repair that file, preserving its dependency entries#

In a personal checkout that should belong to your current ordinary user:

bash
sudo chown "$(id -u):$(id -g)" package-lock.json

The GNU chown manual documents changing the owner and group of the named file. Here the shell obtains your normal user's identifiers before sudo runs the ownership change. Do this from your ordinary account, not a root login shell whose identifiers would select root again. No recursive flag is needed for the source case.

Ownership and mode are separate. If you already own the file but its owner-write permission is absent, the appropriate change is to add that permission, as described by the GNU chmod manual:

bash
chmod u+rw package-lock.json

Apply that second command only when the inspection shows the missing owner permission. Making the file writable by every user is unnecessary. Neither ownership nor mode changes require deleting the lockfile or resolving dependencies again.

3. Retry the same operation without elevation#

bash
npm install

Use the original command if you were adding a particular dependency. Retrying without sudo tests whether the intended account can now complete the operation. Inspect the resulting changes before committing: a normal dependency update can legitimately modify the lockfile, whereas the ownership repair alone does not alter its JSON contents.

If it fails again, compare the new path with the old one. A failure creating a directory is different from opening an existing file. The Linux pathname-resolution manual explains why the process also needs search permission through the directories leading to its target. Correct the particular inaccessible component; do not keep changing the lockfile after the error has moved elsewhere.

Verify what a read-only lockfile changes#

A partial reproduction on Windows used Node.js 26.7.0 and npm 11.19.0 in an isolated, dependency-free project. It tested npm's lockfile write behaviour, not Linux ownership or a Docker build. Making only the lockfile read-only caused the package-lock-only install to fail:

text
npm error code EPERM
npm error syscall open

Clearing that attribute alone made the same command succeed. While the attribute was still set, a clean install succeeded and the lockfile's SHA-256 hash stayed identical. The Windows EPERM result is not a claim to have reproduced Linux's EACCES code; the Linux diagnosis above rests on the source listing and the system-call documentation.

To repeat this narrower check, open PowerShell in a new empty directory:

powershell
npm init -y
npm install --package-lock-only --ignore-scripts --audit=false --fund=false
attrib +R package-lock.json
npm install --package-lock-only --ignore-scripts --audit=false --fund=false
npm ci --ignore-scripts --audit=false --fund=false
attrib -R package-lock.json
npm install --package-lock-only --ignore-scripts --audit=false --fund=false

The middle install is the deliberately failing operation. The fixture has no dependencies and disables lifecycle scripts so its result concerns the lockfile write, not a dependency's setup script. Restore the attribute at the end even if you stop the experiment early.

This also explains a useful diagnostic distinction. npm ci requires an existing matching lockfile and does not rewrite the manifest or lock. It can read a file that a development install cannot update. It still needs permission to replace dependency files and use its cache; it is not a general permission bypass. Use it when you want the recorded dependency set, not when you intend to add or update a package.

Docker copies and mounted checkouts need different repairs#

For an image build, Docker documents COPY ownership: copied files default to root ownership unless an owner is supplied. Setting the execution user does not transfer existing files. The official Node image provides a node user. A documentation-based install-stage example is:

dockerfile
FROM node:lts
WORKDIR /app
RUN chown node:node /app
COPY --chown=node:node package*.json ./
USER node
RUN npm install

The working directory and copied manifests are writable by the installer. This Dockerfile was not built in the Windows reproduction. If installation succeeds but the application later cannot create its cache under dependencies, continue with the Docker node_modules/.cache ownership fix.

A bind mount obscures files already in the image. When your host checkout is mounted over the application directory, changing ownership during the image build does not repair the host lockfile now visible there. Inspect the mounted file and the running process identity; fix that development checkout's ownership arrangement rather than rebuilding an unchanged mount repeatedly.

Finally, once installation passes, an execution failure such as react-scripts: Permission denied concerns launching a command, not updating this JSON file. Use the react-scripts execute-permission diagnosis for that next error. Keep the original lockfile and fix the access required by the specific operation that failed.

Further reading: Fix Docker npm EBADENGINE: Unsupported engine.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.