<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prince Goel</title>
    <description>The latest articles on DEV Community by Prince Goel (@princegoel072).</description>
    <link>https://dev.to/princegoel072</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4223399%2Fb302fb8b-030e-49a9-a108-8f6ba1061aa6.jpg</url>
      <title>DEV Community: Prince Goel</title>
      <link>https://dev.to/princegoel072</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/princegoel072"/>
    <language>en</language>
    <item>
      <title>EADDRINUSE again? This tool tells you who owns the port before you kill anything</title>
      <dc:creator>Prince Goel</dc:creator>
      <pubDate>Tue, 03 Nov 2026 04:27:24 +0000</pubDate>
      <link>https://dev.to/princegoel072/eaddrinuse-again-this-tool-tells-you-who-owns-the-port-before-you-kill-anything-4jfi</link>
      <guid>https://dev.to/princegoel072/eaddrinuse-again-this-tool-tells-you-who-owns-the-port-before-you-kill-anything-4jfi</guid>
      <description>&lt;h1&gt;
  
  
  EADDRINUSE again? This tool tells you who owns the port before you kill anything
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;port-reclaim&lt;/code&gt; finds the process holding a busy dev port, shows you &lt;strong&gt;what&lt;/strong&gt; it is, reclaims it when it's obviously yours, asks when it isn't, and refuses when it would be a mistake.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faif726vw47s2wiivzeb2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faif726vw47s2wiivzeb2.gif" alt="port-reclaim freeing a port, declining to kill a database, and refusing a Docker process" width="720" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Try it now&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx port-reclaim 3000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platforms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;macOS · Linux · Windows · WSL2 (PowerShell + CMD)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Requires&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js 18+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime deps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Package size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~21 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Telemetry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;none — no network calls, nothing stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why this exists
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: listen EADDRINUSE: address already in use :::3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reflex is &lt;code&gt;lsof -i :3000&lt;/code&gt; → read a PID → &lt;code&gt;kill -9&lt;/code&gt; it. Three things go wrong with that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-9&lt;/code&gt; skips graceful shutdown&lt;/strong&gt; — anything mid-write to a socket, DB, or temp file gets truncated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're typing a number you read off a screen into a destructive command&lt;/strong&gt; — TCP and UDP both appear, PIDs repeat, and fatigue picks the wrong one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A PID is not a decision.&lt;/strong&gt; "Something is on 3000" ≠ "that's the dev server you orphaned 40 minutes ago, not the Postgres."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The one check that fixes it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;port-reclaim&lt;/code&gt; compares the &lt;strong&gt;working directory of the process&lt;/strong&gt; with the directory you're standing in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;port-reclaim 3000 &lt;span class="nt"&gt;--list&lt;/span&gt;
Port 3000 is used by &lt;span class="s1"&gt;'node'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;PID 21836, &lt;span class="s1"&gt;'~/project'&lt;/span&gt;, up 42m 11s&lt;span class="o"&gt;)&lt;/span&gt; — matches the current directory.

&lt;span class="nv"&gt;$ &lt;/span&gt;port-reclaim 3000
Released port 3000 from node &lt;span class="o"&gt;(&lt;/span&gt;PID 21836&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process started from your directory&lt;/td&gt;
&lt;td&gt;Reclaimed silently — graceful signal first, forceful only if it survives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different directory, or CWD unreadable&lt;/td&gt;
&lt;td&gt;Shows name, PID, path, uptime — &lt;strong&gt;asks you&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker, HTTP.sys, &lt;code&gt;systemd&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Refuses&lt;/strong&gt;, even with &lt;code&gt;--yes&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A database&lt;/td&gt;
&lt;td&gt;Always asks, even if it shares your directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner invisible to your user&lt;/td&gt;
&lt;td&gt;Reports it as unclaimable instead of "free"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows process, you're in WSL2&lt;/td&gt;
&lt;td&gt;Names it, and explains why Linux can't see it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;port-reclaim 5432
Port 5432 is used by &lt;span class="s1"&gt;'postgres'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;PID 3352, &lt;span class="s1"&gt;'~/dbdata'&lt;/span&gt;, up 3h 12m&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Kill it? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it refuses to touch
&lt;/h2&gt;

&lt;p&gt;The refusals are the point. They exist because the easy version of this tool is 20 lines of shell.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Docker &amp;amp; the kernel are off-limits.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Port 8080 is used by 'docker-proxy' (PID 16004, …). Refusing to kill it — it appears to be a Docker process — stop the container instead (for example 'docker stop &amp;lt;container&amp;gt;').&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Killing &lt;code&gt;docker-proxy&lt;/code&gt; doesn't stop your container, it just leaves Compose in a worse state. PIDs 0–4 are protected on the same logic: &lt;code&gt;System&lt;/code&gt;/HTTP.sys on Windows, &lt;code&gt;systemd&lt;/code&gt; on Linux. &lt;code&gt;--yes&lt;/code&gt; cannot override either.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Databases never die quietly.&lt;/strong&gt; &lt;code&gt;postgres&lt;/code&gt;, &lt;code&gt;postmaster&lt;/code&gt;, &lt;code&gt;mysqld&lt;/code&gt;, &lt;code&gt;mariadbd&lt;/code&gt;, &lt;code&gt;mongod&lt;/code&gt;, &lt;code&gt;redis-server&lt;/code&gt;, &lt;code&gt;memcached&lt;/code&gt;, &lt;code&gt;etcd&lt;/code&gt; always confirm — &lt;em&gt;even when running from inside your project&lt;/em&gt;, which is exactly the case a same-directory shortcut gets catastrophically wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"I found nothing" is not "the port is free."&lt;/strong&gt; When the owner is another user's or an elevated process, &lt;code&gt;lsof&lt;/code&gt; returns nothing — and empty output looks identical to a free port. So the tool checks the answer by briefly binding the port:&lt;br&gt;
&lt;code&gt;Port 3000 is in use by a process port-reclaim cannot see — it may belong to another user, an elevated process, or a socket still closing.&lt;/code&gt;&lt;br&gt;
It exits &lt;code&gt;1&lt;/code&gt;, so &lt;code&gt;port-reclaim 3000 &amp;amp;&amp;amp; next dev&lt;/code&gt; fails loudly instead of succeeding at nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WSL2 tells the truth.&lt;/strong&gt; Under default NAT networking, Linux and Windows have separate port spaces, so a Windows-held port looks free to every Linux tool — including this one until I tested it. It reaches across and says:&lt;br&gt;
&lt;code&gt;Port 4951 is free inside WSL, but a Windows tcp listener 'node.exe' (PID 19220) already holds it.&lt;/code&gt;&lt;br&gt;
It reports rather than acts: a Windows PID passed to &lt;code&gt;process.kill()&lt;/code&gt; would signal an unrelated Linux process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Made for scripts
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;port-reclaim 3000 5173 8080      &lt;span class="c"&gt;# several ports&lt;/span&gt;
port-reclaim &lt;span class="nt"&gt;--match&lt;/span&gt; &lt;span class="s1"&gt;'^node$'&lt;/span&gt;    &lt;span class="c"&gt;# every port held by a matching process name&lt;/span&gt;
port-reclaim &lt;span class="nt"&gt;--json&lt;/span&gt;              &lt;span class="c"&gt;# machine-readable, never terminates&lt;/span&gt;
port-reclaim                     &lt;span class="c"&gt;# ports from config, no arguments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"port-reclaim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5173&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;.reclaimignore&lt;/code&gt; file protects ports absolutely — that beats every heuristic. &lt;code&gt;--list&lt;/code&gt; inspects without acting. Exit codes: &lt;code&gt;0&lt;/code&gt; free or released, &lt;code&gt;1&lt;/code&gt; still in use, &lt;code&gt;2&lt;/code&gt; bad input. Colour turns itself off when output isn't a terminal (&lt;code&gt;--no-color&lt;/code&gt;, &lt;code&gt;NO_COLOR&lt;/code&gt;, &lt;code&gt;FORCE_COLOR&lt;/code&gt; all respected — that's how the GIF above was recorded). There's a programmatic API too: &lt;code&gt;discover&lt;/code&gt;, &lt;code&gt;discoverListening&lt;/code&gt;, &lt;code&gt;terminate&lt;/code&gt;, &lt;code&gt;probe&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast, and measured
&lt;/h2&gt;

&lt;p&gt;Whole-command wall clock on Windows 11 under Node 24, including Node's own startup, because that's the part people leave out of benchmarks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;now&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;answer "is port 3000 free?"&lt;/td&gt;
&lt;td&gt;~1.00s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.10s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;identify what's on a busy port&lt;/td&gt;
&lt;td&gt;~1.26s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.41s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;full reclaim of one process&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.59s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "before" was a bug: discovery started PowerShell three times per port, and a cold &lt;code&gt;powershell.exe&lt;/code&gt; costs ~0.7s. Now &lt;code&gt;netstat&lt;/code&gt; decides whether anything is on the port, and PowerShell starts at most once, to name the processes actually found.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not a process manager or supervisor, and no substitute for &lt;code&gt;docker compose down&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;UDP matching is a heuristic — a UDP socket on a port can belong to a client, not a server.&lt;/li&gt;
&lt;li&gt;The free-port check binds momentarily, so a connection arriving in that instant can read as "in use". A race, not a flaw I'll paper over.&lt;/li&gt;
&lt;li&gt;It can't see other users' processes without your own elevated privileges.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0.x&lt;/code&gt;, one maintainer, ~52 tests across macOS/Linux/Windows × Node 18/20/22/24. The programmatic API may still move — read the &lt;a href="https://github.com/princegoel0/port-reclaim/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; before upgrading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five real bugs are documented in that changelog — a &lt;code&gt;taskkill&lt;/code&gt; missing &lt;code&gt;/F&lt;/code&gt; that reported success while the port stayed busy, a PowerShell cmdlet name that never existed, an uptime calculation always negative on Windows, process names silently wrong on macOS and Node 24 (which quietly disabled the protections above), and a WSL check that once blamed WSL's own relay process. All five are now covered by tests that bind a real socket and make the tool find it through the actual OS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx port-reclaim 3000
&lt;span class="c"&gt;# or&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; port-reclaim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;npm&lt;/strong&gt; → &lt;a href="https://www.npmjs.com/package/port-reclaim" rel="noopener noreferrer"&gt;port-reclaim&lt;/a&gt; · &lt;strong&gt;source&lt;/strong&gt; → &lt;a href="https://github.com/princegoel0/port-reclaim" rel="noopener noreferrer"&gt;princegoel0/port-reclaim&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it saves you one bad &lt;code&gt;kill -9&lt;/code&gt;, it did its job. OS quirks I haven't hit are exactly what I want an issue about.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>node</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
