エラーの内容
サーバーを起動すると、起動が完了する前に次のエラーが表示されます:
OSError: [Errno 98] Address already in use
Flaskでは次のように表示されます:
$ python app.py
* Running on http://127.0.0.1:5000
OSError: [Errno 98] Address already in use
FastAPI/uvicornの場合:
$ uvicorn main:app --reload
ERROR: [Errno 98] Address already in use
LinuxとmacOSではErrno 98が発生します。Windowsでは同じ問題に対してWinError 10048が表示されます — 問題は同じで、表示が異なるだけです。
原因
サーバーが起動する前に、別のプロセスがポートを占有しています。主な原因として以下が考えられます:
- 正常に終了しなかった以前のサーバーインスタンス — Ctrl+Cでターミナルを閉じたが、ソケットが開いたまま残っている
- 忘れていた別のタブで実行中の開発サーバー
- そのポートを占有しているシステムサービス(macOS Monterey以降では、AirPlayレシーバーがポート5000を静かに使用している)
- クラッシュ後もソケットを保持したままのuvicornまたはgunicornのゾンビワーカー —
ps aux | grep uvicornで確認できます
即時対処法:ブロックしているプロセスを終了する
ステップ1 — ポートを占有しているプロセスを特定する
Linux/macOSの場合:
# 5000を実際のポート番号に置き換えてください
lsof -ti:5000
このコマンドはPIDを直接出力します。何も表示されない場合、ポートは空いています — エラーは別の原因から来ています。
ssを使いたい場合は、Linuxでの同等コマンドはこちらです:
ss -lptn 'sport = :5000'
Windowsの場合(PowerShell):
netstat -ano | findstr :5000
ステップ2 — プロセスを終了する
Linux/macOS — lsofの出力をkillに直接パイプする:
kill -9 $(lsof -ti:5000)
ssでPIDを取得した場合は直接終了します:
kill -9 <PID>
Windowsの場合(PowerShell):
Stop-Process -Id <PID> -Force
サーバーを再起動すると、正常に起動するはずです。
macOS:ポート5000を使用するAirPlayレシーバー
macOS Monterey以降、AirPlayレシーバーは起動時にポート5000を静かにバインドします。通常のプロセス一覧には表示されません。無効にするには、システム設定 → 一般 → AirDropとHandoff → AirPlayレシーバーにアクセスしてください。または、Flaskアプリを別のポートに変更する方法もあります:
flask run --port 5001
恒久的な解決策
オプション1:SO_REUSEADDRを使用する(Flask)
Flaskの開発サーバーはデフォルトでこれを設定しますが、生のソケットやカスタムサーバーは設定しません。SO_REUSEADDRは、プロセス終了時にOSが即座にポートを解放するよう指示します — 最大60秒間TIME_WAIT状態で保持する代わりに:
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 5000))
オプション2:環境変数でポートを設定する
3つの異なるプロジェクトでポート5000をハードコーディングすると、衝突が起きるのは時間の問題です。環境変数1つで解決できます。
Flask(app.py):
import os
from flask import Flask
app = Flask(__name__)
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
FastAPI/uvicorn(start.sh):
PORT=${PORT:-8000} uvicorn main:app --host 0.0.0.0 --port $PORT --reload
これにより、コードを変更せずに2つのプロジェクトを並行して実行できます:
PORT=5001 python app.py
PORT=8001 uvicorn main:app --reload
オプション3:空きポートを自動検出する(開発環境向け)
5つのローカルプロジェクトを同時に実行していて、各プロジェクトのポートにこだわりがない場合は、OSに選ばせることができます:
import socket
from flask import Flask
def find_free_port():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', 0))
return s.getsockname()[1]
app = Flask(__name__)
if __name__ == '__main__':
port = find_free_port()
print(f'Starting on port {port}')
app.run(port=port)
本番環境では使用しないでください — 再起動のたびにURLが変わります。ローカル開発での複数プロジェクト管理には便利です。
オプション4:起動時にポートをクリアするラッパースクリプト
同じ開発サーバーを1日に何十回も再起動する場合は、古いプロセスによるブロックを防ぐために、起動スクリプトにポートのクリア処理を追加しましょう:
#!/bin/bash
PORT=8000
# 起動前にポートを解放する
fuser -k ${PORT}/tcp 2>/dev/null || true
uvicorn main:app --host 0.0.0.0 --port $PORT --reload
fuser -kは、ポートを保持しているプロセスにSIGKILLを送ります。|| trueは、何もプロセスがない場合にスクリプトが失敗しないようにします。
修正の確認
再起動する前に、ポートが実際に空いていることを確認します:
# ポートが空いている場合は何も返されないはず
lsof -ti:5000
Flaskが正常に起動した場合の表示:
* Running on http://127.0.0.1:5000
* Debugger is active!
uvicornが正常に起動した場合の表示:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: Started reloader process [12345]
プロセスを終了してもまだエラーが表示される場合は、30秒待ってください。OSはソケットをTIME_WAIT状態で保持します — これは遅延パケットをキャッチするためのTCPの仕組みです。SO_REUSEADDRを設定すると、この待機を完全にスキップできます。

