「Address already in use」の解決 - ポート競合の特定と解放
この記事で解決できること
Address already in use(EADDRINUSE)がなぜ出るのかを理解できます- ポートを占有しているプロセスを特定して安全に解放できます
- 「さっき止めたのにまた出る」TIME_WAIT 由来の競合を切り分けられます
結論(最短ルート)
- 誰が掴んでいるか特定:
sudo ss -lntp | grep ':PORT '(またはsudo lsof -i :PORT) - 正しく停止:サービスなら
systemctl stop、単発プロセスならkill PID - プロセスが無いのに出る:TIME_WAIT が原因 → アプリ側
SO_REUSEADDRか数十秒待つ
前提(対象環境)
- OS:Ubuntu(systemd 環境)
- 対象:自前のサーバ / アプリを起動して
Address already in useに当たった人 - 例として
8080番ポートを使用(読み替え可)
「Address already in use」とは何か?
結論: アプリが
bind()で確保しようとしたポートを別のプロセスが既に握っているため、OS が確保を拒否しているエラー。
サーバ系プロセスは起動時に「このポートで待ち受ける」と OS に宣言する(bind() システムコール)。同じ IP・ポートの組を二重に確保することは原則できないため、既に使われていると bind() が EADDRINUSE を返す。
典型的なメッセージ:
Error: listen EADDRINUSE: address already in use :::8080 bind: Address already in use OSError: [Errno 98] Address already in use
Errno 98 は Linux における EADDRINUSE の番号。言語・フレームワークが違っても原因は同じ。
なぜポートが競合するのか?
結論: 原因は「①前のプロセスが生きている」「②多重起動」「③直前に落としたプロセスの TIME_WAIT」のほぼ 3 種に集約される。
- 前のプロセスが残っている:再起動したつもりが旧プロセスが生存。デタッチ起動(
&/ nohup / コンテナ)で見落としやすい - 多重起動:同じアプリを 2 回起動、または別アプリが同じポートを使用
- TIME_WAIT:直前に終了したプロセスのコネクションが TCP の
TIME_WAIT状態で残り、SO_REUSEADDR未設定のアプリが再バインドに失敗
①②は「プロセスを止める」で解決する。③だけはプロセスが存在しないため別アプローチになる(後述)。
誰がポートを使っているか特定するには?
結論:
ss -lntpで待受プロセスと PID を確認するのが最短。lsof/fuserも同じ情報を別表現で出す。
ss で待受プロセスを特定(推奨)
sudo ss -lntp | grep ':8080 '
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=12345,fd=18))
users:(("node",pid=12345,...))の pid=12345 が占有元-l待受 /-n数値表示 /-tTCP /-pプロセス(sudo必須)
lsof で確認
sudo lsof -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 12345 hide 18u IPv4 98765 0t0 TCP *:8080 (LISTEN)
fuser で PID だけ素早く
sudo fuser 8080/tcp
8080/tcp: 12345
バインド先が 127.0.0.1:8080 か 0.0.0.0:8080 かも ss で確認できる。同じポートでも 127.0.0.1 と 0.0.0.0 は別扱いになるため、競合の有無の判断材料になる。
プロセスを止めてポートを解放するには?
結論: サービス管理下なら
systemctl stop、手動起動プロセスならkill PID。kill -9は最終手段。
サービスとして動いている場合
sudo systemctl stop myapp.service
systemd 管理下のプロセスを kill で直接落とすと、自動再起動(Restart= 設定)で即復活しポートを掴み直すことがある。必ず systemctl stop を使う。
手動起動のプロセスの場合
特定した PID に対して、まず通常のシグナル(SIGTERM)で停止を試みる:
sudo kill 12345
数秒待っても残る場合のみ、強制終了(SIGKILL):
sudo kill -9 12345
kill -9 はプロセスに後始末(接続のクローズ・一時ファイル削除)の機会を与えず即座に殺す。データ破損やロックファイル残留の原因になるため、まず kill(SIGTERM)を試し、それでも落ちないときの最終手段とする。
停止後、ポートが解放されたか確認:
sudo ss -lntp | grep ':8080 '
何も出力されなければ解放済み。
プロセスが無いのに出る(TIME_WAIT)ときは?
結論: 直前に落としたプロセスの接続が
TIME_WAITで残っている状態。アプリがSO_REUSEADDRを設定すれば即再起動でき、未設定なら数十秒で自然解消する。
ss -lntp で待受プロセスが見つからないのに Address already in use が出る場合、TIME_WAIT が疑わしい。確認:
ss -tan state time-wait | grep ':8080'
TIME_WAIT は TCP が「遅延パケットの取り違え」を防ぐための正常な状態で、通常 60 秒前後で消える。対処は次のいずれか。
- アプリ側で
SO_REUSEADDRを有効化(推奨・恒久対策):多くのサーバ実装はデフォルトで設定済み。自作サーバなら listen ソケットに設定する - 数十秒待ってから再起動:暫定対応
net.ipv4.tcp_tw_reuse や tcp_tw_recycle を安易に変更しないこと。これらは発信(outbound)側の TIME_WAIT 再利用に関する設定で、待受ポートの Address already in use の正しい対策ではない。tcp_tw_recycle は NAT 環境で接続障害を引き起こすためカーネルから既に削除されている。listen 側の対策は SO_REUSEADDR が正解。
再発を防ぐには?
結論: アプリに graceful shutdown と
SO_REUSEADDRを実装し、起動スクリプトで旧プロセスの停止を確実にする。
SO_REUSEADDRを設定:再起動時の TIME_WAIT 由来の失敗を防ぐ- graceful shutdown:SIGTERM を受けたら接続を閉じてからプロセス終了する。
kill -9依存を減らす - プロセス管理を systemd / コンテナに任せる:手動の
&起動は旧プロセスの取り残しを生みやすい - 起動前チェック:起動スクリプト冒頭で
ss -lntp | grep ':PORT 'を確認し、占有があれば停止してから起動
やってはいけないこと
結論: 「いきなり
kill -9」「ポート番号を変えて逃げる」「tcp_tw_recycleを有効化」は再発と別障害の温床。
やってはいけない1:原因を見ずに kill -9 を連打する
占有元が systemd 管理サービスなら、kill しても再起動で復活する。まず ss で正体を確認し systemctl stop する。
やってはいけない2:ポート番号を変えて回避する
8080 が競合するから 8081 にする、を繰り返すと「どのポートで何が動いているか」が崩壊する。占有元を特定して解放するのが筋。
やってはいけない3:tcp_tw_recycle を有効化する
過去のネット記事に残る対策だが、NAT 環境で接続不能を引き起こす危険な設定で、現行カーネルでは削除済み。listen 側は SO_REUSEADDR を使う。
コピペ用:特定から解放まで
# 1. 占有プロセスの特定 sudo ss -lntp | grep ':8080 ' sudo lsof -i :8080 # 2-a. サービスなら sudo systemctl stop <service> # 2-b. 手動プロセスなら(SIGTERM → 最終手段 SIGKILL) sudo kill <PID> sudo kill -9 <PID> # 3. 解放確認 sudo ss -lntp | grep ':8080 ' # プロセスが居ないのに出る場合(TIME_WAIT) ss -tan state time-wait | grep ':8080'