Dieses Problem habe ich überall im Netz gesehen. Leute starten ihre React-App mit npm start und bekommen:
Something is already running on port 3000.
Du kannst sie auf einem anderen Port laufen lassen, und es funktioniert — aber das löst das Problem nicht. Also habe ich angefangen zu graben und nichts gefunden.
Dieser Artikel ist der komplette Weg, inklusive der Sackgassen, denn die Sackgassen sind der nützliche Teil. Die eigentliche Lösung am Ende ist ein Zeichen.
Alles, was nicht funktioniert hat
Ich habe den Standardport im Code auf eine zufällige Zahl geändert — 55000 oder so. Hat nicht funktioniert. Gleiche Meldung, andere Zahl.
Das hätte mir mehr sagen müssen, als es tat. Wenn ein frischer, unwahrscheinlicher Port ebenfalls "schon belegt" ist, dann ist der Port nicht das Problem. Aber ich habe getan, was alle tun, und die Liste weiter abgearbeitet.
Ich habe den Webpack-Dev-Server aktualisiert. Ich habe neue Abhängigkeiten installiert. Nichts hat geholfen — und es hat mich in mehr Schwierigkeiten gebracht, denn jetzt lief gar nichts mehr. Gott und Linus sei Dank für git.
Wenn eine Lösung die Lage verschlimmert, ist das Information. Ich habe sie ungefähr zwei Stunden lang ignoriert.
Ich habe den Port auch ordentlich geprüft, mit den Werkzeugen, die man dafür nehmen soll:
lsof -i :3000
netstat -tulpen | grep 3000
ss -tulpn | grep 3000
Nichts. Kein Prozess, nichts, das lauscht, nichts gebunden. Der Port war nach jedem Maßstab, den ich hatte, frei, und der Dev-Server bestand darauf, dass er es nicht war.

Das, was endlich falsch aussah
Statt nach dem Port zu filtern, habe ich mir angeschaut, was node insgesamt offen hatte:
lsof -i -P -n | grep node
node 40400 eiskalt 22u IPv4 752287 0t0 TCP dev.felixtravelblog.de:45355->dev.felixtravelblog.de:42018 (ESTABLISHED)
node 40400 eiskalt 24u IPv4 752288 0t0 TCP dev.felixtravelblog.de:45355->dev.felixtravelblog.de:42024 (ESTABLISHED)
node 40400 eiskalt 25u IPv4 752317 0t0 TCP dev.felixtravelblog.de:45355->dev.felixtravelblog.de:42026 (ESTABLISHED)
node 40400 eiskalt 26u IPv4 752318 0t0 TCP dev.felixtravelblog.de:45355->dev.felixtravelblog.de:42030 (ESTABLISHED)
Ein Haufen aufgebauter Verbindungen, alle von und zu dev.felixtravelblog.de.
Das ist meine lokale Entwicklungsumgebung, mit der ich eine TYPO3-Installation mit mehreren Domains teste. Es ist ein Hostname in /etc/hosts, der auf 127.0.0.1 zeigt — auf die lokale Maschine. Dass node unter diesem Namen mit sich selbst redet, ist also normal.
Merkwürdig war die Darstellung: lsof löste diese Adressen zu einem Hostnamen auf, statt mir schlicht 127.0.0.1 zu zeigen — was bedeutete, dass Loopback nicht ganz das war, wofür ich es hielt.
Die eigentliche Ursache
Ich habe mein Netzwerk geprüft und an der Route nichts Falsches gefunden. Dann habe ich /etc/hosts geöffnet, und da stand es:
27.0.0.1 localhost
Eine fehlende Ziffer. localhost war nicht mehr an 127.0.0.1 gebunden. Es zeigte auf 27.0.0.1, eine völlig gültige öffentliche IP-Adresse, die jemand ganz anderem gehört.
Zurück auf 127.0.0.1 geändert, und alles lief sofort.
Warum daraus genau dieser Fehler wurde
Lohnt sich auszubuchstabieren, denn der Zusammenhang ist nicht offensichtlich.
Vor dem Binden prüft der Dev-Server, ob der Port frei ist — indem er versucht, sich auf localhost mit diesem Port zu verbinden. Wenn die Verbindung scheitert, ist der Port frei. Wenn sie klappt oder sich seltsam verhält, wird angenommen, dass dort etwas ist.
Da localhost auf eine zufällige öffentliche Adresse auflöste, ging diese Prüfung ins Internet statt ins Loopback. Was auch immer zurückkam — ein Timeout, eine Ablehnung von einer Middlebox, eine Antwort von einem fremden Host —, war nicht das saubere "hier ist nichts", nach dem die Prüfung gesucht hatte. Also meldete sie den Port als belegt.
Was auch das erklärt, was mein Hinweis hätte sein müssen: Jeder Port war belegt. Die Prüfung hat nie nach meiner Maschine gefragt.
Was ich anders machen würde
Glaub dem merkwürdigen Ergebnis. Dass ein brandneuer zufälliger Port ebenfalls belegt ist, ist kein Zufall. Das war der Moment, um mit dem Port aufzuhören und mit der Frage anzufangen: "Was macht diese Prüfung eigentlich?"
Prüf die Annahme darunter, nicht nur das Symptom. Ich habe den Port ein Dutzend Mal geprüft. Ich habe nie geprüft, ob localhost auch localhost ist, weil man so etwas nicht prüft — bis man es doch muss.
ping -c1 localhost
getent hosts localhost
cat /etc/hosts
Installier nichts, um ein Rätsel zu lösen. Den Dev-Server zu aktualisieren und Abhängigkeiten hinzuzufügen hat mich der Ursache nicht nähergebracht; es hat einen zweiten, unabhängigen kaputten Zustand über den ersten gelegt. Wenn du keine Theorie hast, ist eine Änderung kein Experiment — sie ist Rauschen.
Mach den Filter weiter. lsof -i :3000 hat mir stundenlang nichts gegeben. lsof -i -P -n | grep node hat mir die Antwort in einer Zeile gegeben. Nach dem zu filtern, was du erwartest, wird dir nie das zeigen, was du nicht erwartet hast.
Wie kam es überhaupt dazu?
Ehrlich: Ich habe irgendwann /etc/hosts bearbeitet, vermutlich um eine Entwicklungsdomain einzutragen, und ein Zeichen verloren. Es ist eine Datei, die du selten anfasst, mit sudo, und danach nie wieder liest — nichts validiert sie, und nichts beschwert sich.
Also lag sie da und hat still eine bestimmte Prüfung in einem bestimmten Werkzeug kaputtgemacht, auf eine Art, die exakt nach einem völlig anderen Problem aussah.
Und das ist die allgemeine Form der schlimmsten Bugs. Nicht kompliziert — nur an einer Stelle, an der niemand nachschaut, weil sie zum Boden gehört, auf dem du stehst.