Node.jsのEADDRINUSEは、サーバーが使おうとしたlocal addressを別のサーバーがすでに使っていて、bindできないときのエラーです。port番号だけを見てprocessを強制終了せず、address、port、実行場所、listenerの起動元を確認します。同じ名前のNode processでも、別projectの開発サーバーや自動起動されたworkerかもしれません。
この記事のコマンド例は、lsofが使えるmacOSやUnix系の環境を想定します。Windowsのprocess調査やcontainer内部は、その環境に合う管理方法で確認してください。以下のport・PIDは架空例で、実端末のport調査、process停止、Node サーバー起動は行っていません。
目次
エラーからaddressとportを取り出す
Node.jsのErrors資料(新しいタブで開く)でEADDRINUSEの意味を確認し、実際のエラーのcode、syscall、address、portを記録します。EACCESや接続先からのECONNREFUSEDは、同じ停止手順へ進めません。
表は横にスクロールできます
| エラーや観測 | 最初の確認 | 混同しないこと |
|---|---|---|
| EADDRINUSE / listen | bindしようとしたaddressとport | 外部APIへの接続失敗 |
| EACCES | その環境の権限・許可条件 | 既存processを止めれば解決 |
| ECONNREFUSED | 接続先の受付と経路 | 自分のlisten port競合 |
| 起動後に別portへ切替 | frameworkの自動選択 | 予定portで起動したと扱う |
127.0.0.1、0.0.0.0、IPv6の::など、どのaddressで待ち受けようとしたかも残します。広いaddressへのbindやIPv4/IPv6の扱いは環境に影響されるため、同じportでも「localhostの一行しか見ていない」状態で空きと決めません。
listenerを読取だけで確認する
lsofが導入済みで利用可能なら、port 3000のTCP listenerを見る例は次です。3000は実際のエラーにあるportへ置き換えます。このコマンドは一覧の読取例で、停止操作は含みません。
lsof -nP -a -iTCP:3000 -sTCP:LISTEN
lsofのmanpage(新しいタブで開く)で、TCPの選択とLISTEN状態の指定を確認できます。出力のCOMMAND、PID、USER、NAMEに相当する欄から対象を絞ります。権限やOSによって見える範囲は異なるため、結果が空でも他ユーザーや別namespaceのlistenerが存在しない証明にはしません。
表は横にスクロールできます
| 読み取る項目 | 確認する問い | まだ分からないこと |
|---|---|---|
| PID | どのprocessがlistenerを持つか | 停止してよいか |
| USER | 誰のprocessか | projectや起動元 |
| COMMAND | どの実行物か | 同名processの用途 |
| address/port | どのendpointを使うか | proxyやcontainer越しの関係 |
host側の公開portとcontainer内のlisten portは別です。エラーがDockerの公開port設定から出ているのか、container内のNodeから出ているのかを先に分けます。別namespaceの調査結果を同じport空き判定に使わないようにします。
PIDだけでなく起動元を調べる
PID 12345を調べる架空例です。実際には直前に確認した対象PIDを使います。processは入れ替わる可能性があるため、古いメモのPIDを停止操作へそのまま使いません。
ps -p 12345 -o pid=,ppid=,user=,comm=
親process、起動したterminal、IDE task、package script、process managerやservice管理を確認します。引数全文にはsecretや接続文字列が含まれる場合があるため、調査結果を共有するときは必要な項目だけにします。
watcherやprocess managerが自動で再起動しているなら、子processだけを終了しても再びportを使います。元の起動元で停止する方針を選び、同じappをterminalとIDEの両方から二重起動していないかも確認します。
表は横にスクロールできます
| 判明した状態 | 次の選択 | 元へ戻すための記録 |
|---|---|---|
| 自分の古い開発サーバー | 元のterminal/taskから正常停止 | 起動コマンドと作業場所 |
| 別projectの必要なサーバー | 停止せず自分のport変更を検討 | 変更前のportと参照先 |
| service管理下のprocess | 管理元と所有者を確認 | service名と通常の開始・停止方法 |
| 所有者・用途が不明 | 診断情報を渡して確認 | address/port・PID・発生時刻 |
process名でNodeを一括終了したり、portに一致したPIDへ無条件に強制終了を送ったりする手順にはしません。元のサーバーを止める場合も、未保存の作業や処理中の要求がないか確認します。
portを変える場合は参照先を追う
自分のサーバーを別portで起動する場合、API base URL、proxy、テスト設定、開発用callbackなどが古いportを参照していないかを調べます。サーバーが起動しても、browserや別serviceが旧portへ接続していれば、古いappを見続けることがあります。
変更した設定を記録し、うまくいかなければその変更だけを戻せるようにします。OAuthのcallbackや外部連携先が関係する場合は、単にport番号を書き換えて終わりにせず、その設定の所有者と許可条件も確認します。
再起動後に正しいprocessの応答かを確認する
目的のサーバーを起動したら、起動ログのaddress/port、現在のlistener、アプリの既知の画面や版情報を対応付けます。エラーが消えたことと、意図したprojectが応答していることを別々に確認します。
再び競合するなら、自動起動元の重複、同じapp内の複数listen、watcher再起動時の終了処理などへ調査を進めます。一時的に空いたportへ移るだけで原因解消とせず、通常の起動・停止を繰り返して同じ構成が再現できることを検証計画に含めます。
