<?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: RAFAY ADEEL</title>
    <description>The latest articles on DEV Community by RAFAY ADEEL (@rafay_adeel).</description>
    <link>https://dev.to/rafay_adeel</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%2F4179423%2F6fa5a51b-c5bc-44a4-8044-de6d20cc5817.png</url>
      <title>DEV Community: RAFAY ADEEL</title>
      <link>https://dev.to/rafay_adeel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rafay_adeel"/>
    <language>en</language>
    <item>
      <title>kill-port 3000 kills port 30001 on Windows, and other things I found in a 2-million-downloads-a-week package</title>
      <dc:creator>RAFAY ADEEL</dc:creator>
      <pubDate>Mon, 12 Oct 2026 13:37:33 +0000</pubDate>
      <link>https://dev.to/rafay_adeel/kill-port-3000-kills-port-30001-on-windows-and-other-things-i-found-in-a-e9o</link>
      <guid>https://dev.to/rafay_adeel/kill-port-3000-kills-port-30001-on-windows-and-other-things-i-found-in-a-e9o</guid>
      <description>&lt;p&gt;If you write Node, you've seen this:&lt;br&gt;
&lt;/p&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;and you've probably fixed it with &lt;code&gt;npx kill-port 3000&lt;/code&gt;. kill-port is downloaded about 2.1 million times a week, and plenty of projects run it in &lt;code&gt;predev&lt;/code&gt; and &lt;code&gt;pretest&lt;/code&gt; hooks.&lt;/p&gt;

&lt;p&gt;I read through kill-port's issue tracker and the source of the released version, 2.0.1, and one line made me stop. So I tested it on Windows 11: one server on port 4471, an unrelated one on 44710, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; npx kill-port@2.0.1 4471
Process on port 4471 killed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both servers were dead. Then I ran it again, with nothing at all on 4471:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx kill-port@2.0.1 4471
&lt;span class="go"&gt;Process on port 4471 killed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 0 both times. I got the same false "killed" message on Ubuntu 22.04. Here's why, and what I built instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What kill-port 2.0.1 actually does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On Windows&lt;/strong&gt;, it runs &lt;code&gt;netstat -nao&lt;/code&gt; and keeps lines that match this regular expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`^ *TCP *[^ ]*:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no end anchor, so &lt;code&gt;:3000&lt;/code&gt; also matches &lt;code&gt;:30001&lt;/code&gt;, &lt;code&gt;:30002&lt;/code&gt; and so on. Ask it to free 3000 and it will also &lt;code&gt;TaskKill /F&lt;/code&gt; whatever is listening on 30001.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On macOS and Linux&lt;/strong&gt;, it first runs &lt;code&gt;lsof -i -P&lt;/code&gt;, which lists every socket on the machine and resolves every address through DNS. That is the slowness people report in &lt;a href="https://github.com/tiaanduplessis/kill-port/issues/61" rel="noopener noreferrer"&gt;issue #61&lt;/a&gt;. Then it decides whether anything is on the port with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`:*&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matches "3000" anywhere in the line, including a PID like 13000. And if &lt;code&gt;lsof&lt;/code&gt; prints nothing at all (no sockets it can see, as on my Ubuntu machine), the check is skipped and the call resolves as if it succeeded. Either way, the CLI only reports failure when the promise rejects, so you get &lt;code&gt;Process on port 3000 killed&lt;/code&gt; even though nothing was killed.&lt;/p&gt;

&lt;p&gt;A few more things you hit in scripts and CI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It needs &lt;code&gt;lsof&lt;/code&gt;, which Alpine, distroless and many slim images don't have (&lt;a href="https://github.com/tiaanduplessis/kill-port/issues/9" rel="noopener noreferrer"&gt;#9&lt;/a&gt;, &lt;a href="https://github.com/tiaanduplessis/kill-port/issues/45" rel="noopener noreferrer"&gt;#45&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;When the port is already free, it reports an error (&lt;a href="https://github.com/tiaanduplessis/kill-port/issues/87" rel="noopener noreferrer"&gt;#87&lt;/a&gt;). When a kill really fails, the CLI still exits 0.&lt;/li&gt;
&lt;li&gt;It always sends &lt;code&gt;SIGKILL&lt;/code&gt;; there's no way to let a server shut down cleanly (&lt;a href="https://github.com/tiaanduplessis/kill-port/issues/48" rel="noopener noreferrer"&gt;#48&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;It runs its commands through a shell and has two runtime dependencies. In November 2025, the Shai-Hulud worm published infected kill-port versions 2.0.2 and 2.0.3. npm removed them within about 12 hours, but anyone who installed them in that window had to treat the machine as compromised.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be fair to kill-port: its maintainer has been active again since early October 2026, and the master branch now has exact port matching, &lt;code&gt;--quiet&lt;/code&gt;, signal choice and bundled types. None of that is released yet, and it still needs &lt;code&gt;lsof&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built: kill-port-safe
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx kill-port-safe 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It keeps kill-port's CLI and API, so switching is a one-line change, and it also installs a &lt;code&gt;kill-port&lt;/code&gt; command so existing scripts keep working. The interesting parts are in how it finds the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux: no lsof, just /proc
&lt;/h3&gt;

&lt;p&gt;The kernel already publishes every socket in &lt;code&gt;/proc/net/tcp&lt;/code&gt; and &lt;code&gt;/proc/net/tcp6&lt;/code&gt;. Each row has the local address in hex, the socket state (&lt;code&gt;0A&lt;/code&gt; is LISTEN) and an inode number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  sl  local_address rem_address   st ... inode
   0: 00000000:0BB8 00000000:0000 0A ... 123456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0BB8&lt;/code&gt; is 3000. To find the owning process, look at every &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/fd/*&lt;/code&gt; symlink. The one pointing at &lt;code&gt;socket:[123456]&lt;/code&gt; belongs to the process holding the port. That's what &lt;code&gt;lsof&lt;/code&gt; and &lt;code&gt;fuser&lt;/code&gt; do internally, and it works in any container that has &lt;code&gt;/proc&lt;/code&gt;, which is all of them. If the socket exists but no readable fd points at it, the port belongs to another user (usually root), and kill-port-safe says so instead of failing silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows: don't read the State column
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;netstat -nao&lt;/code&gt; prints &lt;code&gt;LISTENING&lt;/code&gt; on English Windows, &lt;code&gt;ABHÖREN&lt;/code&gt; on German Windows and something else again on other languages. Instead of reading the state, kill-port-safe treats a TCP row as a listener when its foreign address is &lt;code&gt;0.0.0.0:0&lt;/code&gt; or &lt;code&gt;[::]:0&lt;/code&gt;. It also reads the full table, not &lt;code&gt;netstat -p TCP&lt;/code&gt;, because that filter hides IPv6-only listeners like &lt;code&gt;::1&lt;/code&gt;, which is where some dev servers bind.&lt;/p&gt;

&lt;h3&gt;
  
  
  macOS: lsof, but only for the ports you asked about
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lsof -nP -iTCP:3000 -sTCP:LISTEN -Fpn&lt;/code&gt;: no DNS lookups, no full socket scan, listeners only, and a machine-readable output format that doesn't change between macOS releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  And the rest
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Exact port matching; only processes &lt;em&gt;listening&lt;/em&gt; on the port are touched, never ones merely connected to it.&lt;/li&gt;
&lt;li&gt;It tells you what it killed, and &lt;code&gt;who 3000&lt;/code&gt; shows what's on a port without killing anything.&lt;/li&gt;
&lt;li&gt;It exits 0 when the port is already free, so &lt;code&gt;predev&lt;/code&gt; hooks don't fail on a clean machine, and 1 when a kill really fails.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--graceful&lt;/code&gt; sends &lt;code&gt;SIGTERM&lt;/code&gt; and only force-kills if the process is still there after 5 seconds.&lt;/li&gt;
&lt;li&gt;It waits until the port is actually free before returning, so your next command doesn't race the dying process.&lt;/li&gt;
&lt;li&gt;It never runs a shell, and refuses to kill itself, its parent process or the Windows System process.&lt;/li&gt;
&lt;li&gt;Zero dependencies, no install scripts, published from GitHub Actions with npm provenance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Is it faster?
&lt;/h2&gt;

&lt;p&gt;Honestly, it depends on the OS. Median of 10 runs on GitHub Actions runners, Node 24, from a fresh listener to a free port (&lt;a href="https://github.com/CodeRafay/kill-port-safe/actions/runs/38248509217" rel="noopener noreferrer"&gt;run&lt;/a&gt;, &lt;a href="https://github.com/CodeRafay/kill-port-safe/blob/main/bench.js" rel="noopener noreferrer"&gt;script&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;kill-port-safe&lt;/th&gt;
&lt;th&gt;kill-port 2.0.1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;48 ms&lt;/td&gt;
&lt;td&gt;90 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;129 ms&lt;/td&gt;
&lt;td&gt;134 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;255 ms&lt;/td&gt;
&lt;td&gt;215 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Windows it's slower because it looks up the process name so it can tell you what it killed. On a busy machine with thousands of open connections, the gap on macOS and Linux grows, because kill-port 2.0.1 lists and resolves all of them. But speed isn't why you'd switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other options
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;kill-port-process&lt;/strong&gt; is zero-dependency too and actively maintained. It needs Node 22+, uses &lt;code&gt;lsof&lt;/code&gt; or &lt;code&gt;ss&lt;/code&gt; on Linux, and its API differs from kill-port's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fkill&lt;/strong&gt; is a general process killer (by PID, name or port) with a nice interactive CLI. Use it if that's what you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx kill-port-safe &lt;span class="nb"&gt;who &lt;/span&gt;3000
npx kill-port-safe 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/CodeRafay/kill-port-safe" rel="noopener noreferrer"&gt;https://github.com/CodeRafay/kill-port-safe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it fails on your machine, please open an issue with the output of &lt;code&gt;npx kill-port-safe who &amp;lt;port&amp;gt; --json&lt;/code&gt;. Odd platforms and localized Windows installs are exactly the reports I want.&lt;/p&gt;

&lt;p&gt;Thanks to Tiaan du Plessis for kill-port and to ceifa for kill-port-fast; the port lookup builds on their work.&lt;/p&gt;

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