Next.js

Fix Next.js: Unable to Acquire the Dev Server Lock

Find the server that owns .next/dev/lock, distinguish a held lock from a leftover file, and restart without disrupting other Node applications.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

Unable to acquire lock at means Next.js could not take the exclusive lock for its output directory. Usually an existing development server owns it. Stop that specific server in its terminal, wait for it to exit, then run your development command once. Changing the port leaves the directory lock unchanged. A leftover lock file after a crash is a separate case: its existence alone does not establish an active lock.

The Stack Overflow report behind this error describes changing ports and deleting .next without resolving startup. Its reported error reads:

text
Unable to acquire lock at "filepath", is another instance of next dev running? Suggestion: If you intended to restart next dev, terminate the other process, and then try again.

That wording is present in the Next.js 16.0.0 lock implementation. Next.js 16.4.4 uses a clearer message for the same kind of conflict:

text
⨯ Another next dev server is already running.

This article reproduces the latter on Next.js 16.4.4, Node.js 24.22.2, with Webpack in the official node:24-slim Docker image, on 2 November 2026. The original question has no accepted resolution in the captured evidence; the diagnosis below is backed separately by that reproduction and the versioned framework source.

Why it happens#

The development server writes generated files into a directory it owns. In the current CLI documentation, development output lives under .next/dev. The development bundler setup creates an exclusive lock at the output directory's lock path before starting its hot reloader. This guard prevents competing development servers from using the same generated output at once.

The lock and the listening socket are different resources. Changing --port chooses another socket; it does not give the server another output directory. In the reproduction, the first server listened on port 35573 and a second attempt chose port 40909. The second attempt still exited with the lock error. Both processes used an automatically allocated port, so a network-port collision was excluded.

If your actual message is EADDRINUSE, use the separate Next.js port and address-in-use diagnosis. A port conflict can be solved by selecting a free port. A directory-lock conflict needs the existing owner to release the lock, or a genuinely separate working directory for an intentionally separate application instance.

The 16.4.4 lock implementation also distinguishes a failure to acquire an occupied lock from an I/O error while opening or creating the file. A permission failure is therefore not a reason to hunt for more listening ports. Read the complete exception and its cause before choosing a remedy.

Fix: stop the server that owns this directory#

1. Identify the project and installed version#

Run these commands from the application's directory:

bash
npx next --version
npm pkg get scripts.dev

The first command reports your installed Next.js version. The second shows what your development script launches. This matters in a monorepo: the command you type may start a task runner that launches multiple applications. You need the owner of the directory named in the error, rather than whichever Node process happens to appear first in Task Manager.

Read the actual path in the error, including any custom output directory. Do not assume every project writes to the default location. The official distDir reference describes changing the directory and warns that Next.js takes ownership of it. It must be dedicated to framework output, rather than shared with unrelated project files.

2. Stop it from the terminal or task that launched it#

Check your open terminals, editor tasks and any development process manager. If the original server is still useful, use its existing URL instead of starting another copy. Next.js 16.4.4 prints the existing server's URL and PID when it can read its metadata.

If you intend to replace it, press Ctrl+C in the original terminal and wait for the shell prompt. For an editor task, stop that task. For a managed container, stop the particular service through its supervisor. Starting a replacement while the old process is still shutting down can produce another conflict.

Avoid commands that terminate all Node processes. The same computer may be running your API, an editor helper and another developer's reproduction. Nor should you delete the output directory while its server is using it: removing generated files does not stop the process that writes them.

3. Inspect the reported PID when the original terminal is missing#

On current versions, the lock file can contain JSON server metadata. In Windows PowerShell, inspect the default file and the corresponding process:

powershell
$lockInfo = Get-Content -LiteralPath '.next/dev/lock' -Raw | ConvertFrom-Json
Get-CimInstance Win32_Process -Filter "ProcessId = $($lockInfo.pid)" |
  Select-Object ProcessId, ParentProcessId, CommandLine

Adjust the path to the output directory from your own error if it differs. Confirm that the command line and parent process match the development server for this project. Older versions may have an empty lock file, so a JSON parsing failure does not prove corruption; use the original terminal, process tree or operating-system handle tools instead.

Only after that identity check, if the verified process cannot be stopped through its original terminal, target that PID. Microsoft's Stop-Process reference documents the process-ID parameter:

powershell
Stop-Process -Id $lockInfo.pid

Check the process again immediately before stopping it if time has passed: an old PID in a leftover file can refer to a different process after PID reuse. A number printed in an old log is evidence to investigate, not permanent authority to terminate that number.

On macOS or Linux, this read-only command can help find processes with the default lock file open:

bash
lsof .next/dev/lock

Inspect the resulting process and its arguments, then stop the verified server through its terminal or supervisor. The reproduction used a signal to the specific server PID read from its lock metadata; no process-name wildcard was involved.

4. Start one replacement#

After the original owner has exited, run the application's usual command once:

bash
npm run dev

Keep this terminal open. If a task runner or editor immediately creates another owner, stop the task that is respawning it before repeating startup. Repeatedly terminating a child while its supervisor recreates it leaves the underlying launch conflict in place.

Verify the fix#

Open the replacement server's printed local URL and request a real route. Confirm both that the route responds and that the replacement terminal handles the request. A browser tab that still reaches an older server is not evidence that your new launch worked.

In the controlled reproduction, the only change between the rejected second launch and the successful third launch was stopping the original verified lock owner. The same output directory and application were retained. Requesting the replacement server returned:

text
HTTP 200 PAGE MARKER true

The marker was a small page rendered by the test application. This verifies more than seeing a startup banner: the replacement could compile and serve its route. In this version, startup information can print before all configuration and lock work has completed, so read the terminal through to the final outcome.

If startup succeeds and compilation now reports Module not found: Can't resolve, you have moved beyond lock acquisition. Continue with the missing-module checks for imports, paths and dependencies; clearing another lock cannot repair an unresolved import.

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.

A leftover file is not necessarily a held lock#

The 16.4.4 implementation documents an operating-system lock associated with an open file handle. It makes a best-effort release on exit; the operating system releases the lock when the process exits even if explicit cleanup does not run. On POSIX systems the file itself can remain.

This difference was tested, rather than inferred from an empty directory. After the replacement served its route, the harness sent SIGKILL to that verified server PID inside the disposable Linux container. Its lock file remained, including the old PID and URL. A fourth launch then acquired the existing file and started successfully without deleting it.

That result is scoped to this Next.js version and Linux image. It does not prove every shared filesystem or older native binding behaves identically. It does show why “the file exists, therefore delete it” is an unreliable diagnosis. Try normal startup after the owner has exited. If acquisition still fails, establish whether a process still has the file open, whether a supervisor restarted it, or whether the underlying error is actually filesystem access.

Do not remove a lock from underneath an active server. File deletion is not process termination, and bypassing the guard can leave multiple writers targeting the same generated output. A full dependency reinstall changes unrelated state and was not needed for either successful restart in this test.

Containers, monorepos and repeated launches#

If a host process and a container both use the same bind-mounted project directory, inspect both launch paths. The relevant question is whether they target the same output tree. Giving each listener a different published port does not separate files on disk. The Docker development environment guide explains source bind mounts and separate dependency volumes; inspect those mounts when working out which environment writes your generated files.

For two intentional development instances, separate checkouts are a straightforward way to keep generated output separate. A custom output directory is also a framework feature, but do not change a shared configuration merely to hide an accidental duplicate launch. First decide which process should exist and which files each instance owns.

When an unexpected restart precedes the conflict, retain the earlier terminal output. A lock message can be the replacement launch's symptom while the first failure happened elsewhere. If that earlier message says the server is running out of memory, follow the Next.js memory restart diagnosis after resolving ownership. The useful prevention is a single deliberate launch path, a known owner for its output directory, and shutdown that completes before the next server starts.

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