FlaskとFastAPIでOSError: [Errno 98] Address already in useを修正する

beginner🐍 Python2026-06-23| Linux/macOS/Windows、Python 3.7+、Flask 2.x、FastAPI 0.x、uvicorn、gunicorn

Error Message

OSError: [Errno 98] Address already in use
#python#flask#fastapi#socket#ポート#サーバー

エラーの内容

サーバーを起動すると、起動が完了する前に次のエラーが表示されます:

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を設定すると、この待機を完全にスキップできます。

Related Error Notes