Fix OSError: [Errno 98] Address already in use in Flask and FastAPI

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#port#server

The Error

You run the server. Before it finishes booting, you see this:

OSError: [Errno 98] Address already in use

Flask shows it like this:

$ 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 and macOS throw Errno 98. Windows gives you WinError 10048 instead β€” same problem, different label.

Why It Happens

Something else grabbed your port before your server could. The usual suspects:

  • A previous server instance that didn't exit cleanly β€” Ctrl+C closed your terminal but the socket stayed open
  • Another dev server running in a tab you forgot about
  • A system service sitting on that port (on macOS Monterey+, AirPlay Receiver quietly claims port 5000)
  • A zombie uvicorn or gunicorn worker still holding the socket after a crash β€” ps aux | grep uvicorn will expose it

Quick Fix: Kill the Blocking Process

Step 1 β€” Find what's on the port

Linux/macOS:

# Replace 5000 with your actual port
lsof -ti:5000

That prints the PID directly. No output means the port is free β€” the error is coming from somewhere else.

Prefer ss? Here's the Linux equivalent:

ss -lptn 'sport = :5000'

On Windows (PowerShell):

netstat -ano | findstr :5000

Step 2 β€” Kill it

Linux/macOS β€” pipe lsof straight into kill:

kill -9 $(lsof -ti:5000)

Got the PID from ss? Kill it directly:

kill -9 <PID>

Windows (PowerShell):

Stop-Process -Id <PID> -Force

Restart your server and it should come up clean.

macOS: AirPlay Receiver on port 5000

Since macOS Monterey, AirPlay Receiver silently binds port 5000 at boot. It won't show up in a normal process list. To disable it, go to System Settings β†’ General β†’ AirDrop & Handoff β†’ AirPlay Receiver. Or just move your Flask app to a different port:

flask run --port 5001

Permanent Fix Options

Option 1: Use SO_REUSEADDR (Flask)

Flask's dev server sets this by default, but raw sockets and custom servers don't. SO_REUSEADDR tells the OS to release the port immediately when your process exits β€” instead of holding it in TIME_WAIT for up to 60 seconds:

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))

Option 2: Configure the port via environment variable

Hardcoding port 5000 across three different projects is a collision waiting to happen. One env var fixes it.

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

Now you can run two projects side by side without touching code:

PORT=5001 python app.py
PORT=8001 uvicorn main:app --reload

Option 3: Auto-find a free port (dev environments)

Running five local projects at once and don't care which port each gets? Let the OS pick one:

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)

Skip this for production β€” your URL changes every restart. Fine for local dev juggling.

Option 4: Kill-on-startup wrapper script

Restarting the same dev server dozens of times a day? Add a port-clearing step to your launch script so stale processes never block you:

#!/bin/bash
PORT=8000
# Free the port before starting
fuser -k ${PORT}/tcp 2>/dev/null || true
uvicorn main:app --host 0.0.0.0 --port $PORT --reload

fuser -k sends SIGKILL to whatever holds the port. The || true keeps the script from failing when nothing is there.

Verify the Fix

Before restarting, confirm the port is actually free:

# Should return nothing if port is free
lsof -ti:5000

A clean Flask startup looks like:

 * Running on http://127.0.0.1:5000
 * Debugger is active!

Clean uvicorn startup:

INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [12345]

Still seeing the error after killing the process? Wait 30 seconds. The OS holds sockets in TIME_WAIT state β€” a TCP mechanism to catch delayed packets in flight. Setting SO_REUSEADDR bypasses this wait entirely.

Related Error Notes