EADDRINUSE(Address already in use)
サーバー起動時の「listen EADDRINUSE」を、掴んでいるPIDの特定から多重起動・TIME_WAIT滞留の切り分け、SO_REUSEADDRの使いどころまで一気に解決できる。
- 原因の大半は多重起動・前プロセスの残留・TIME_WAIT滞留の3つ。まずどのPIDがそのポートを掴んでいるかを ss / lsof で特定する。
- TIME_WAITは能動的に接続を閉じた側が最後のACKの再送に備えて2×MSL待つ状態で、サーバー再起動直後の bind を弾く。SO_REUSEADDR で回避できる。
- 根本対処は掴んでいるプロセスの停止かポート変更。再発防止はSO_REUSEADDRの常時付与、graceful shutdown、コンテナ/systemdでの単一起動保証。
一言でいうと(このエラーが何を意味するか)
EADDRINUSE(Address already in use)は、プログラムが bind() で「このIPアドレスとポート番号の組で待ち受けたい」と要求したのに、その組を別のソケットが既に占有していて割り当てられなかった、というOSからの拒否です。bind() が errno = EADDRINUSE(Linuxでは値98)を返し、Node.jsなら Error: listen EADDRINUSE: address already in use :::3000 のように表面化します。
ポートはIPアドレスごとに16ビット(0〜65535)の空間しかない共有資源で、原則として1つの「アドレス+ポート」の組で待ち受けられるソケットは1つだけです。したがってこのエラーは「ポートの取り合いに負けた」ことを意味し、犯人は多くの場合自分自身の別プロセスか、さっきまで動いていた同じアプリの名残です。
EADDRINUSE は「そのポートは使用中」。よく似た EADDRNOTAVAIL(99)は「そのIPアドレスがこのホストに存在しない/まだ割り当てられていない」で、原因も対処も異なります。前者はポートの競合、後者は bind 先アドレスの誤り(未設定のVIPやIPv6無効化など)を疑ってください。
横にスクロール
よくある原因(複数を具体的に、頻度順)
-
多重起動(同じポートで2つ目を立ち上げた) — 最頻出です。開発中に前のサーバーを止めずにもう一度
npm run devを叩いた、ホットリロードが旧プロセスを残したまま新プロセスを起動した、docker compose upを二重に走らせた、といったケース。1つ目が正常に待ち受けているので、2つ目のbind()が弾かれます。 -
前プロセスの残留(ゾンビではなく生存プロセス) — ターミナルを閉じただけ、
Ctrl+Cが効かず端末だけ落ちた、といった状況で、親から切り離された旧プロセスがポートを掴んだまま生きている。プロセスを名前で探しても見つからず、ポート番号から逆引きして初めて発見できることが多いです。 -
TIME_WAIT の滞留 — サーバーを正常終了させた直後に再起動すると、能動的に接続を閉じた側(多くはサーバー)のソケットが
TIME_WAIT状態で一定時間残り、同じポートへのbind()を拒否します。SO_REUSEADDRを付けずに listen していると顕在化します(詳細は後述)。負荷試験直後など短命接続を大量に閉じた場合に特に目立ちます。 -
別アプリによる占有 — 3000番や8080番のような定番ポートは、別のツール(プロキシ、他フレームワーク、監視エージェント)が先に使っていることがあります。自分のアプリを何度探しても犯人がいないときはこれを疑います。
-
ワイルドカードと個別アドレスの衝突 — 一方が
0.0.0.0:8080(全アドレスで待ち受け)、他方が192.168.1.10:8080のように、片方がワイルドカードだと個別バインドと衝突します。IPv6の::とIPv4の0.0.0.0をデュアルスタックで両取りしている場合にも起こります。
診断の手順(切り分け方・見るべきログ/コマンド)
原則は「まず犯人のPIDを1つに特定する」。プロセスを止める前に、それが本当に止めてよいものかを確認します。
Linux では ss が第一選択です(netstat の後継で高速)。-l で listen 中、-t でTCP、-n で名前解決を省き、-p でプロセスを表示します。
# 3000番を LISTEN/使用しているプロセスを特定
ss -ltnp 'sport = :3000'
# 出力例(Users:(("node",pid=12345,fd=23)) の pid が掴んでいる本体)
# State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# LISTEN 0 511 0.0.0.0:3000 0.0.0.0:* users:(("node",pid=12345,fd=23))
lsof でも同じことができ、こちらはファイルディスクリプタ単位で見えます。
# ポート番号から掴んでいるプロセスを逆引き(-i、-P/-nで表示を高速化)
lsof -i :3000 -P -n
# COMMAND PID USER FD TYPE NODE NAME
# node 12345 me 23u IPv4 ... TCP *:3000 (LISTEN)
TIME_WAIT が原因かは、状態を数えて確かめます。
# 対象ポートの接続状態の内訳を見る(TIME-WAIT が積み上がっていないか)
ss -tan | grep ':3000' | awk '{print $1}' | sort | uniq -c
# 1 LISTEN
# 132 TIME-WAIT ← これが bind を拒否している疑い
macOS は lsof -i :3000 がそのまま使え、Windows は netstat -ano | findstr :3000 でPIDを得て tasklist /FI "PID eq <PID>" で正体を確認します(PowerShell なら Get-NetTCPConnection -LocalPort 3000)。
kill -9 $(lsof -ti :3000) のような一撃コマンドは便利ですが、そのポートを掴んでいるのが自分のアプリだと確認できたときだけにしてください。共用ホストでは無関係なサービス(別チームのプロセスやシステムデーモン)を巻き込む事故になります。まず正体を見てから、可能なら SIGKILL(-9) ではなく SIGTERM(-15) で行儀よく止めます。
解決と予防(対処と再発防止)
即時の対処は状況で分かれます。犯人が自分の旧プロセスなら、それを止めれば解決します。犯人が正当に動いている別サービスなら、自分のアプリのポートを変える(PORT=3001 など)のが正解です。
TIME_WAIT が原因のときの本命は、リスニングソケットに SO_REUSEADDR を付けることです。これは同一「アドレス+ポート」の TIME_WAIT 状態のソケットを無視して bind を許可するオプションで、サーバーの再起動を待たされずに済みます。多くのフレームワークやランタイムは既定で付けています(Node.js の net/http サーバーも既定で有効)。自前でソケットを書くなら bind 前に必ず設定します。
/* C: bind の前に SO_REUSEADDR を立てる(再起動直後の bind 失敗を防ぐ) */
int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
なぜ TIME_WAIT が存在し、なぜ bind を阻むのか。TCPの4ウェイクローズで先に FIN を送って閉じた側(能動クローズ側)は、相手の FIN に対する最後の ACK を送ったあと TIME_WAIT に入り、2 × MSL(Maximum Segment Lifetime、Linux実装では合計おおむね60秒)だけ待ちます。理由は2つあります。第一に、最後の ACK が失われて相手が FIN を再送してきたとき、それに応答できるソケットを残しておくため。第二に、同じ「アドレス+ポート」の組で新しい接続をすぐ開くと、旧接続の遅延パケットが混入して壊れるのを防ぐためです。この安全機構ゆえに、閉じた直後のポートは即座には再利用できず、SO_REUSEADDR なしの再 bind が弾かれます。
SO_REUSEADDR: 主に TIME_WAIT の残骸を無視して bind できるようにする。サーバー再起動の定番。同じアドレスでLISTEN中のソケットが別にある場合の重複LISTENは許さない。SO_REUSEPORT(Linux 3.9+): 複数プロセスが同一ポートを同時にLISTENでき、カーネルが接続を各ソケットへ負荷分散する。マルチプロセスなワーカー構成のスケールアウト向けで、目的が全く異なる。混同しない。
再発防止は運用側で固めます。
| 対策 | 効果 | 使いどころ |
|---|---|---|
| SO_REUSEADDR を常時付与 | 再起動直後の TIME_WAIT による bind 失敗を消す | 全サーバーの既定に。自前ソケットは明示設定 |
| Graceful shutdown | SIGTERM で listen を確実に close し残留を防ぐ | 本番デーモン/コンテナ停止時 |
| 単一起動の保証 | 多重起動そのものを構造的に防ぐ | systemd の1ユニット化、compose の1サービス化 |
| ポートを設定可能に | 衝突時に即座に別ポートへ逃がせる | 環境変数 PORT で外部注入 |
TCPの状態遷移や MSL、名前解決とポートの原理はネットワーク、ソケットとプロセス・ファイルディスクリプタの関係はOS、systemd やコンテナでの単一起動・graceful shutdown の設計はDevOpsの各トピックも参照してください。
- EADDRINUSE は bind() が返す errno(Linuxで98)。ポートは16ビット空間で1つのアドレス+ポートは原則1ソケット
- TIME_WAIT は能動クローズ側が 2×MSL 待つ状態で、最後のACK再送への応答と旧接続の遅延パケット混入防止が目的
- SO_REUSEADDR は TIME_WAIT を無視して bind を許可(再起動用)、SO_REUSEPORT は複数プロセスの同時LISTEN(負荷分散用)で別物
- 診断は ss -ltnp / lsof -i でPID特定 → 正体確認 → SIGTERM で停止、が基本手順
エラー辞典の記事ガイド
EADDRINUSE(Address already in use)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ネットワーク
比較で見る軸
難易度: advanced / カテゴリ: エラー辞典 / タグ数: 6
導入後に効く点
TIME_WAITは能動的に接続を閉じた側が最後のACKの再送に備えて2×MSL待つ状態で、サーバー再起動直後の bind を弾く。SO_REUSEADDR で回避できる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- エラー辞典
- タグ数
- 6
判断チェックリスト
- 自社の用途が「ネットワーク / TCP」に近いか確認する。
- 強みである「原因の大半は多重起動・前プロセスの残留・TIME_WAIT滞留の3つ。まずどのPIDがそのポートを掴んでいるかを ss / lsof で特定する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。