Tóm tắt nhanh
Có một tiến trình khác đang lắng nghe trên cổng mà ứng dụng của bạn muốn dùng. Tìm nó bằng ss -tlnp | grep : hoặc lsof -i :, sau đó kill nó hoặc đổi cổng cho ứng dụng của bạn.
# Tìm tiến trình đang dùng cổng 8080
ss -tlnp | grep :8080
# Kill nó (thay PID bằng số thực tế)
kill -9
# Sau đó khởi động lại service của bạn
systemctl restart your-service
Chuyện gì đang xảy ra
Mỗi network service chiếm một cổng bằng cách gọi bind() trên một socket. Lỗi bind: Address already in use có nghĩa là hệ điều hành từ chối yêu cầu đó vì một tiến trình khác đã sở hữu cổng đó — hoặc một tiến trình vừa dừng đã để lại socket ở trạng thái TIME_WAIT và hệ điều hành chưa giải phóng nó.
Hai tình huống phổ biến nhất:
- Bạn khởi động lại ứng dụng quá nhanh và tiến trình cũ vẫn đang dọn dẹp (
TIME_WAIT) - Một tiến trình hoàn toàn khác đã chiếm cổng — một instance khác của ứng dụng bạn, một service xung đột, hoặc một tiến trình zombie từ lần crash trước
Bước 1: Xác định tiến trình đang giữ cổng
Thay 8080 bằng cổng bạn đang muốn dùng.
Dùng ss (khuyến nghị trên Linux hiện đại)
ss -tlnp | grep :8080
Kết quả mẫu:
LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=12345,fd=18))
PID hiện ngay ở đó — 12345 trong ví dụ này, đang chạy một tiến trình Node.js.
Dùng lsof
lsof -i :8080
Kết quả mẫu:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 12345 ubuntu 18u IPv4 98765 0t0 TCP *:8080 (LISTEN)
Dùng fuser
fuser 8080/tcp
Lệnh này chỉ in ra PID — hữu ích khi viết script.
Bước 2: Quyết định cách xử lý
Cách A — Kill tiến trình cũ
Nếu đó là một instance cũ của ứng dụng bạn:
# Thử kill nhẹ nhàng trước
kill 12345
# Nếu nó không chết sau vài giây, dùng kill cứng
kill -9 12345
Sau đó khởi động lại service của bạn bình thường.
Cách B — Dừng một system service đang chiếm cổng
Nếu bạn thấy thứ gì đó như nginx, apache2, hoặc postgres đang chiếm cổng của bạn:
# Kiểm tra service đó thuộc về cái gì
systemctl status nginx
# Dừng nó lại
systemctl stop nginx
Cách C — Kill tất cả mọi thứ trên cổng trong một lệnh
fuser -k 8080/tcp
Lệnh này kill tất cả các tiến trình đang bind vào cổng đó. Dùng cẩn thận nếu bạn đang trên server dùng chung.
Cách D — Đổi cổng cho ứng dụng của bạn
Đôi khi cổng đó đang được dùng hợp lệ bởi thứ gì đó bạn không nên đụng vào. Trong trường hợp đó, hãy cấu hình ứng dụng của bạn để lắng nghe trên một cổng khác. Ví dụ, trong ứng dụng Node.js:
// Đổi cái này
const PORT = process.env.PORT || 3001;
app.listen(PORT);
Hoặc đặt qua biến môi trường khi khởi động:
PORT=3001 node server.js
Bước 3: Xử lý TIME_WAIT (tình huống khởi động lại nhanh)
Nếu bạn dừng service rồi lập tức khởi động lại, bạn vẫn có thể thấy lỗi dù tiến trình của bạn đã biến mất. Kernel giữ socket ở trạng thái TIME_WAIT trong một khoảng thời gian ngắn (thường 60 giây) để đảm bảo các gói tin đến trễ được xử lý sạch sẽ.
Cách sửa gọn nhất cho các service bạn kiểm soát là đặt SO_REUSEADDR trong code ứng dụng. Hầu hết các framework và server đều làm điều này tự động, nhưng nếu bạn đang viết code socket thô:
// Trong C
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
Với system service, cách giải quyết tạm thời là chờ 30–60 giây rồi thử lại. Hoặc dùng ss -s để theo dõi số lượng TIME_WAIT giảm dần:
watch -n1 'ss -s'
Bước 4: Kiểm tra sau khi sửa
Sau khi đã giải phóng cổng (hoặc đổi cấu hình), hãy xác nhận cổng đã sạch trước khi khởi động service:
# Nên không trả về gì nếu cổng đã trống
ss -tlnp | grep :8080
Sau đó khởi động service và kiểm tra xem nó có thực sự đang lắng nghe không:
systemctl start your-service
# Xác nhận nó đã chạy
ss -tlnp | grep :8080
# Kiểm tra bằng curl
curl -v http://localhost:8080/health
Bonus: Kiểm tra cổng xung đột trước khi khởi động
Trước khi triển khai một service mới, nên kiểm tra xem những cổng nào đã bị chiếm trên server của bạn:
# Liệt kê tất cả các cổng đang lắng nghe
ss -tlnp
# Hoặc dùng netstat nếu không có ss
netstat -tlnp
Nếu bạn đang làm việc với container hoặc cấu hình mạng phức tạp và cần tính toán dải CIDR hoặc phân chia subnet song song với việc lập kế hoạch cổng, IP Subnet Calculator trên ToolCraft rất tiện để tính toán mạng nhanh ngay trên trình duyệt — không upload dữ liệu, hoàn toàn riêng tư.
Ngăn chặn lỗi này trong tương lai
- Dùng
SO_REUSEADDRtrong code socket của bạn — loại bỏ vấn đề khởi động lại do TIME_WAIT - Tài liệu hóa các cổng của service — giữ một danh sách đơn giản ghi cổng nào dành cho service nào để tránh xung đột khi thêm service mới
- Dùng process manager — các công cụ như
systemd,pm2, hoặcsupervisorxử lý việc tắt sạch trước khi khởi động lại, giảm xung đột cổng do tiến trình cũ - Đặt
Restart=alwaysvớiRestartSec=2trong systemd unit — cho kernel thời gian giải phóng socket giữa các lần khởi động lại

