TL;DR
アプリが使用しようとしているポートに、すでに別のプロセスがリッスンしています。ss -tlnp | grep : または lsof -i : で特定し、そのプロセスを終了するか、アプリのポートを変更してください。
# ポート8080を使用しているプロセスを探す
ss -tlnp | grep :8080
# 終了する(PIDを実際の番号に置き換える)
kill -9
# サービスを再起動する
systemctl restart your-service
何が起きているのか
すべてのネットワークサービスは、ソケット上で bind() を呼び出すことでポートを確保します。bind: Address already in use というエラーは、別のプロセスがすでにそのポートを占有しているか、直前に停止したプロセスがソケットを TIME_WAIT 状態のままにしており、OSがまだ解放していないために、OSがその要求を拒否したことを意味します。
よくある2つのケース:
- アプリを素早く再起動したため、古いプロセスがまだクリーンアップ中である(
TIME_WAIT) - まったく別のプロセスがポートを取得している — アプリの別インスタンス、競合するサービス、または以前のクラッシュによるゾンビプロセス
ステップ1:ポートを占有しているプロセスを特定する
8080 を実際に使用しようとしているポート番号に置き換えてください。
ss を使用する(モダンな Linux で推奨)
ss -tlnp | grep :8080
出力例:
LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=12345,fd=18))
PIDはそこに表示されます — この例では 12345 で、Node.js プロセスが動作しています。
lsof を使用する
lsof -i :8080
出力例:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 12345 ubuntu 18u IPv4 98765 0t0 TCP *:8080 (LISTEN)
fuser を使用する
fuser 8080/tcp
PIDのみを出力します — スクリプト処理に便利です。
ステップ2:対処方法を選択する
オプション A — 古いプロセスを終了する
自分のアプリの古いインスタンスが残っている場合:
# まずグレースフルキル
kill 12345
# 数秒経っても終了しない場合、強制終了
kill -9 12345
その後、通常どおりサービスを再起動してください。
オプション B — ポートを使用しているシステムサービスを停止する
nginx、apache2、postgres などがポートを占有している場合:
# どのサービスか確認する
systemctl status nginx
# 停止する
systemctl stop nginx
オプション C — ポート上のすべてのプロセスを一括終了する
fuser -k 8080/tcp
そのポートにバインドされているすべてのプロセスを終了します。共有サーバーで使用する場合は慎重に。
オプション D — アプリケーションのポートを変更する
触れるべきでない別のサービスが正当にポートを使用している場合は、アプリを別のポートでリッスンするよう設定してください。例えば、Node.js アプリの場合:
// この部分を変更する
const PORT = process.env.PORT || 3001;
app.listen(PORT);
または起動時に環境変数で指定する:
PORT=3001 node server.js
ステップ3:TIME_WAIT を処理する(素早い再起動のシナリオ)
サービスを停止してすぐに再起動した場合、自分のプロセスがなくなっているにもかかわらずエラーが表示されることがあります。カーネルは遅延パケットを適切に処理するため、ソケットを短時間(通常60秒)TIME_WAIT 状態に保持します。
自分で管理するサービスに対する最善の対処法は、アプリケーションコードで SO_REUSEADDR を設定することです。ほとんどのフレームワークやサーバーはこれを自動的に行いますが、生のソケットコードを書いている場合は:
// C言語の場合
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
システムサービスの場合、手っ取り早い回避策として30〜60秒待ってから再試行する方法があります。または ss -s で TIME_WAIT カウントが減少するのを確認できます:
watch -n1 'ss -s'
ステップ4:修正を確認する
ポートを解放した(または設定を変更した)後、サービスを起動する前にポートが空いていることを確認してください:
# ポートが空いていれば何も表示されない
ss -tlnp | grep :8080
次にサービスを起動し、実際にリッスンしているか確認します:
systemctl start your-service
# 起動を確認
ss -tlnp | grep :8080
# curl でテスト
curl -v http://localhost:8080/health
ボーナス:起動前に競合するポートを確認する
新しいサービスをデプロイする前に、サーバーでどのポートがすでに使用されているかを確認しておくと良いでしょう:
# リッスン中のすべてのポートを一覧表示
ss -tlnp
# ss が使えない場合は netstat
netstat -tlnp
コンテナや複雑なネットワーク構成で、ポート計画と合わせてCIDR範囲やサブネット割り当てを把握する必要がある場合、ToolCraft の IP Subnet Calculator はブラウザ上でネットワーク計算を素早く行うのに便利です — データのアップロードは不要で、完全にプライベートです。
今後の予防策
- ソケットコードで
SO_REUSEADDRを使用する — TIME_WAIT による再起動の問題を解消できます - サービスのポートをドキュメント化する — 各サービスが使用するポートを簡単なリストにまとめておくと、新しいサービスを追加する際の誤った競合を防げます
- プロセスマネージャーを使用する —
systemd、pm2、supervisorなどのツールは再起動前にクリーンなシャットダウンを行い、古いプロセスによるポート競合を軽減します - systemd ユニットで
Restart=alwaysとRestartSec=2を設定する — 再起動の間にカーネルがソケットを解放する時間を確保できます

