Fix OSError: [Errno 98] Address already in use trong Flask và 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

Lỗi Này Là Gì

Bạn khởi động server. Trước khi nó kịp boot xong, bạn thấy dòng này:

OSError: [Errno 98] Address already in use

Flask hiển thị như sau:

$ 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 và macOS báo Errno 98. Windows thì trả về WinError 10048 — cùng một vấn đề, chỉ khác tên gọi.

Nguyên Nhân

Có thứ gì đó đã chiếm port của bạn trước khi server kịp khởi động. Các nguyên nhân thường gặp:

  • Một tiến trình server trước đó chưa thoát hoàn toàn — bạn nhấn Ctrl+C đóng terminal nhưng socket vẫn còn mở
  • Một dev server khác đang chạy trong tab mà bạn quên mất
  • Một service hệ thống đang chiếm port đó (trên macOS Monterey trở lên, AirPlay Receiver âm thầm chiếm port 5000)
  • Một tiến trình uvicorn hoặc gunicorn zombie vẫn đang giữ socket sau khi crash — ps aux | grep uvicorn sẽ phát hiện ra nó

Cách Xử Lý Nhanh: Kill Tiến Trình Đang Chặn

Bước 1 — Tìm xem cái gì đang chiếm port

Linux/macOS:

# Thay 5000 bằng port thực tế của bạn
lsof -ti:5000

Lệnh này in thẳng PID ra. Không có output nghĩa là port đang trống — lỗi đến từ chỗ khác.

Thích dùng ss? Đây là lệnh tương đương trên Linux:

ss -lptn 'sport = :5000'

Trên Windows (PowerShell):

netstat -ano | findstr :5000

Bước 2 — Kill tiến trình đó

Linux/macOS — pipe lsof thẳng vào kill:

kill -9 $(lsof -ti:5000)

Đã có PID từ ss? Kill trực tiếp:

kill -9 <PID>

Windows (PowerShell):

Stop-Process -Id <PID> -Force

Khởi động lại server và mọi thứ sẽ hoạt động bình thường.

macOS: AirPlay Receiver chiếm port 5000

Từ macOS Monterey, AirPlay Receiver âm thầm bind port 5000 khi khởi động máy. Nó sẽ không xuất hiện trong danh sách tiến trình thông thường. Để tắt tính năng này, vào System Settings → General → AirDrop & Handoff → AirPlay Receiver. Hoặc đơn giản hơn, chuyển Flask app sang port khác:

flask run --port 5001

Giải Pháp Lâu Dài

Giải pháp 1: Dùng SO_REUSEADDR (Flask)

Dev server của Flask đã bật tùy chọn này mặc định, nhưng raw socket và custom server thì không. SO_REUSEADDR báo cho OS giải phóng port ngay khi tiến trình thoát — thay vì giữ nó ở trạng thái TIME_WAIT tới 60 giây:

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

Giải pháp 2: Cấu hình port qua biến môi trường

Hardcode port 5000 cho ba project khác nhau là rước họa vào thân. Một biến môi trường là đủ để giải quyết.

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

Giờ bạn có thể chạy song song hai project mà không cần động vào code:

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

Giải pháp 3: Tự động tìm port trống (môi trường dev)

Đang chạy năm project local cùng lúc và không quan tâm port nào là bao nhiêu? Để OS tự chọn:

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)

Đừng dùng cách này cho production — URL sẽ thay đổi mỗi lần restart. Chỉ phù hợp khi dev local với nhiều project cùng lúc.

Giải pháp 4: Script dọn port trước khi khởi động

Restart cùng một dev server hàng chục lần mỗi ngày? Thêm bước dọn port vào script khởi động để tiến trình cũ không bao giờ chặn bạn nữa:

#!/bin/bash
PORT=8000
# Giải phóng port trước khi khởi động
fuser -k ${PORT}/tcp 2>/dev/null || true
uvicorn main:app --host 0.0.0.0 --port $PORT --reload

fuser -k gửi SIGKILL tới bất cứ thứ gì đang giữ port. || true giúp script không bị lỗi khi không có tiến trình nào đang chiếm port.

Kiểm Tra Sau Khi Xử Lý

Trước khi restart, hãy xác nhận port đã thực sự trống:

# Không có output nghĩa là port đã trống
lsof -ti:5000

Flask khởi động thành công trông như sau:

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

uvicorn khởi động thành công:

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

Vẫn thấy lỗi sau khi đã kill tiến trình? Hãy chờ 30 giây. OS giữ socket ở trạng thái TIME_WAIT — đây là cơ chế TCP để bắt các gói tin đến trễ. Đặt SO_REUSEADDR sẽ bỏ qua hoàn toàn thời gian chờ này.

Related Error Notes