<?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: Thomas Shafer</title>
    <description>The latest articles on DEV Community by Thomas Shafer (@thomas_shafer_783798ba461).</description>
    <link>https://dev.to/thomas_shafer_783798ba461</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%2F4187069%2F401ce5fb-4894-455c-bdc2-fab824447cf1.png</url>
      <title>DEV Community: Thomas Shafer</title>
      <link>https://dev.to/thomas_shafer_783798ba461</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thomas_shafer_783798ba461"/>
    <language>en</language>
    <item>
      <title>EADDRINUSE on macOS: find what holds the port, and stop it safely</title>
      <dc:creator>Thomas Shafer</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:03:06 +0000</pubDate>
      <link>https://dev.to/thomas_shafer_783798ba461/eaddrinuse-on-macos-find-what-holds-the-port-and-stop-it-safely-n1o</link>
      <guid>https://dev.to/thomas_shafer_783798ba461/eaddrinuse-on-macos-find-what-holds-the-port-and-stop-it-safely-n1o</guid>
      <description>&lt;p&gt;You run &lt;code&gt;npm run dev&lt;/code&gt; and get 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;Something is already listening on port 3000. Usually it's a dev server you forgot about: a terminal tab you closed, an editor task that never exited, or a watcher that outlived its parent. This post covers how to find the owner on macOS, how to stop it without guessing, and a pair of shell functions so you only have to work it out once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: ask the kernel who is listening
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;lsof&lt;/code&gt; is already on your Mac. This is the form worth memorising:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-iTCP&lt;/span&gt;:3000 &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each flag does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-n&lt;/code&gt; skips DNS lookups, so it returns instantly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-P&lt;/code&gt; shows &lt;code&gt;3000&lt;/code&gt; instead of a service name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-iTCP:3000&lt;/code&gt; limits it to TCP port 3000.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-sTCP:LISTEN&lt;/code&gt; hides client connections, so you only see the process that owns the port.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COMMAND   PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    48213 tom    23u  IPv6  0x1234      0t0  TCP *:3000 (LISTEN)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-sTCP:LISTEN&lt;/code&gt; filter matters more than it looks. Without it, &lt;code&gt;lsof -i :3000&lt;/code&gt; also lists your browser's open connections to the server, and you can end up killing Chrome instead.&lt;/p&gt;

&lt;p&gt;If you only want the PID, &lt;code&gt;-t&lt;/code&gt; prints just that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-tiTCP&lt;/span&gt;:3000 &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: check what it is before you kill it
&lt;/h2&gt;

&lt;p&gt;A PID tells you very little. Before you stop it, look at two things: the full command line and the working directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps &lt;span class="nt"&gt;-o&lt;/span&gt; pid,etime,command &lt;span class="nt"&gt;-p&lt;/span&gt; 48213
lsof &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 48213 &lt;span class="nt"&gt;-d&lt;/span&gt; cwd &lt;span class="nt"&gt;-Fn&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'s/^n//p'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;etime&lt;/code&gt; shows how long it has been running, and the &lt;code&gt;cwd&lt;/code&gt; line shows which project folder it was started from. Together they usually settle it. A &lt;code&gt;node&lt;/code&gt; process that has been up for three days, started from &lt;code&gt;~/old-prototype&lt;/code&gt;, is safe to stop. One that started two minutes ago from the project you're working on is probably your own server, running in another tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: ask nicely first
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kill&lt;/code&gt; with no signal sends &lt;code&gt;SIGTERM&lt;/code&gt;, which lets the process close its sockets and clean up. Use &lt;code&gt;kill -9&lt;/code&gt; only if that doesn't work.&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="nb"&gt;kill &lt;/span&gt;48213
&lt;span class="nb"&gt;sleep &lt;/span&gt;1
lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-tiTCP&lt;/span&gt;:3000 &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"port 3000 is free"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reaching for &lt;code&gt;kill -9&lt;/code&gt; first is a common habit and it costs you something real. A process killed that way can't remove its lock files, flush its logs or shut down its child processes, and you can end up with a half-written database or a leaked worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: put it in your dotfiles
&lt;/h2&gt;

&lt;p&gt;Here are two functions for &lt;code&gt;~/.zshrc&lt;/code&gt;. &lt;code&gt;port&lt;/code&gt; shows who holds a port. &lt;code&gt;killport&lt;/code&gt; stops it with &lt;code&gt;SIGTERM&lt;/code&gt;, waits, and only then falls back to &lt;code&gt;SIGKILL&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;port&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-iTCP&lt;/span&gt;:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?usage:&lt;span class="p"&gt; port N&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN
&lt;span class="o"&gt;}&lt;/span&gt;

killport&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;pids
  &lt;span class="nv"&gt;pids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-tiTCP&lt;/span&gt;:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?usage:&lt;span class="p"&gt; killport N&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pids&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"nothing is listening on &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pids&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs &lt;span class="nb"&gt;kill
  sleep &lt;/span&gt;1
  &lt;span class="nv"&gt;pids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-tiTCP&lt;/span&gt;:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pids&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pids&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | xargs &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt;
  lsof &lt;span class="nt"&gt;-nP&lt;/span&gt; &lt;span class="nt"&gt;-tiTCP&lt;/span&gt;:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-sTCP&lt;/span&gt;:LISTEN &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"port &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; is free"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a new terminal and run &lt;code&gt;port 3000&lt;/code&gt; or &lt;code&gt;killport 3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One limit: this only sees processes you own. If the listener belongs to root or another user, &lt;code&gt;lsof&lt;/code&gt; without &lt;code&gt;sudo&lt;/code&gt; may show nothing at all. In that case you'll get "nothing is listening" even though the port really is taken, so try &lt;code&gt;sudo lsof ...&lt;/code&gt; before concluding it's free.&lt;/p&gt;

&lt;h2&gt;
  
  
  When killing the process isn't the answer
&lt;/h2&gt;

&lt;p&gt;Some holders come back, or aren't what they look like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Port 5000 or 7000 on a recent Mac.&lt;/strong&gt; macOS's AirPlay Receiver listens on both. The owner shows up as &lt;code&gt;ControlCe&lt;/code&gt; (Control Center). Turn it off in System Settings &amp;gt; General &amp;gt; AirDrop &amp;amp; Handoff &amp;gt; AirPlay Receiver, or move your dev server to another port. Killing it just makes macOS start it again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The owner is Docker.&lt;/strong&gt; If &lt;code&gt;lsof&lt;/code&gt; shows &lt;code&gt;com.docker.backend&lt;/code&gt; or similar, a container has published the port. Use &lt;code&gt;docker ps --format '{{.Names}}\t{{.Ports}}'&lt;/code&gt; to find the container, then &lt;code&gt;docker stop&lt;/code&gt; it. Killing the backend process takes down every container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The process has a parent that restarts it.&lt;/strong&gt; &lt;code&gt;nodemon&lt;/code&gt;, &lt;code&gt;next dev&lt;/code&gt;, a &lt;code&gt;docker compose&lt;/code&gt; restart policy and some editor tasks will respawn what you kill. Check the parent with &lt;code&gt;ps -o ppid= -p PID&lt;/code&gt;, then look at what that parent is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing is listening, but the bind still fails.&lt;/strong&gt; This is rare. It can happen when a server binds the same address and port twice, such as &lt;code&gt;localhost&lt;/code&gt; resolving to both &lt;code&gt;::1&lt;/code&gt; and &lt;code&gt;127.0.0.1&lt;/code&gt;, or two copies of a framework starting in one process. &lt;code&gt;lsof&lt;/code&gt; will show one listener for each address family, so read the NAME column carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small habit that prevents most of this
&lt;/h2&gt;

&lt;p&gt;Most EADDRINUSE errors come from servers that were started and then abandoned. Two habits cut them down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run dev servers in the foreground in a terminal you can see. Don't background them with &lt;code&gt;&amp;amp;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Let your tooling pick a free port where it supports that. Many frameworks will move to the next port if you tell them to, instead of failing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Full disclosure: I made a tool for this
&lt;/h2&gt;

&lt;p&gt;I built Binnacle, a native Mac menu bar monitor, partly because I got tired of typing the commands above. In its ⌘K command line, &lt;code&gt;port 3000&lt;/code&gt; shows the process, and &lt;code&gt;kill port 3000&lt;/code&gt; stops it after a confirmation. Its Projects page groups dev servers by project folder and shows their ports, memory and how long they've been idle, with a Stop button. The menu bar readouts are free. The rest of the app is free for 14 days, then $9 once. It needs macOS 14 or later. It's at &lt;a href="https://binnacle.shafer.llc?utm_source=devto" rel="noopener noreferrer"&gt;https://binnacle.shafer.llc?utm_source=devto&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You don't need it for any of the above. The two functions in your &lt;code&gt;.zshrc&lt;/code&gt; will cover most days.&lt;/p&gt;

&lt;p&gt;What's the strangest thing you've found sitting on a port? I'm curious what other surprise listeners are out there.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>node</category>
      <category>shell</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
