Cách sửa lỗi 'listen tcp :8080: bind: address already in use' trong Go

beginner🔷 Go2026-04-10| Go (Golang) trên Linux, macOS hoặc Windows

Error Message

listen tcp :8080: bind: address already in use
#go#http#network#port#bind#server

Vấn đềBạn vừa hoàn thành một tính năng, sẵn sàng chạy thử và gõ lệnh go run main.go. Thay vì nhận được thông báo 'Server started', bạn lại phải đối mặt với một dòng thông báo khó chịu: listen tcp :8080: bind: address already in use. Đây là một lỗi kinh điển mà mọi lập trình viên backend đều từng gặp. Về cơ bản, chương trình Go của bạn đang cố gắng truy cập vào cổng 8080, nhưng hệ điều hành đã giao quyền sử dụng cổng này cho một ứng dụng khác rồi.

Tại sao cổng của bạn lại bị chiếm dụng?Lỗi này hiếm khi nằm ở logic code của bạn. Thông thường, một trong ba nguyên nhân sau là thủ phạm:

  • Tiến trình "ma": Server trước đó của bạn bị sập hoặc bạn đã đóng terminal mà không dừng nó. Nó vẫn đang chạy ngầm mà bạn không biết.- Kẻ chiếm dụng cổng: Một container Docker, một instance Node.js bị lãng quên hoặc một dịch vụ hệ thống như Nginx đã chiếm cổng đó.- TCP TIME_WAIT: Nếu bạn khởi động lại liên tục, hệ điều hành có thể giữ cổng ở trạng thái TIME_WAIT trong khoảng 60 giây để đảm bảo tất cả các gói tin đã được xử lý xong.## Các bước khắc phục### 1. Tìm kiếm tiến trình (Linux hoặc macOS)Chúng ta cần tìm mã định danh tiến trình (PID) đang chiếm dụng. Hãy mở terminal và chạy lệnh:
lsof -i :8080

Kết quả trả về sẽ trông như thế này:

COMMAND   PID   USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
main    45821   user    3u  IPv6 0x...      0t0  TCP *:http-alt (LISTEN)

Hãy chú ý vào cột PID—trong ví dụ này là 45821.

2. Đuổi "kẻ chiếm dụng"Bây giờ, hãy yêu cầu tiến trình đó dừng lại. Thay 45821 bằng PID thực tế của bạn:

kill -9 45821

Cờ -9 (SIGKILL) là một lệnh mạnh mẽ. Nó buộc tiến trình dừng ngay lập tức mà không cần hỏi ý kiến. Bây giờ, hãy thử chạy lại server của bạn.

3. Cách xử lý trên WindowsWindows có cách xử lý khác. Hãy mở PowerShell hoặc Command Prompt với quyền Administrator và chạy:

netstat -ano | findstr :8080

Tìm dòng kết thúc bằng một con số:

TCP    0.0.0.0:8080           0.0.0.0:0              LISTENING       7244

Số 7244 chính là PID. Hãy tắt nó bằng lệnh sau:

taskkill /F /PID 7244

Ngăn chặn lỗi lặp lại với Graceful ShutdownViệc tắt thủ công chỉ là giải pháp tạm thời. Nếu bạn gặp lỗi này thường xuyên, có khả năng code Go của bạn không tự dọn dẹp tài nguyên khi bạn nhấn Ctrl+C. Theo mặc định, http.ListenAndServe sẽ dừng đột ngột, thường để lại cổng ở trạng thái treo. Hãy sử dụng graceful shutdown để giải phóng tài nguyên một cách chuẩn xác.

Dưới đây là đoạn mã đã được kiểm chứng để đảm bảo server của bạn thoát một cách sạch sẽ:

package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	srv := &http.Server{
		Addr: ":8080",
	}

	go func() {
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("lỗi listen: %s\n", err)
		}
	}()

	// Lắng nghe tín hiệu Ctrl+C hoặc tín hiệu kill
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
	<-stop

	log.Println("Đang tắt server an toàn (graceful shutdown)...")

	// Cho server 5 giây để hoàn tất các request đang xử lý
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server bị buộc phải dừng:", err)
	}

	log.Println("Server đã thoát")
}

Kiểm tra lạiTrước khi khởi động lại, hãy kiểm tra chắc chắn rằng cổng đã trống. Chạy lại lệnh lsof -i :8080 một lần nữa. Nếu không có kết quả trả về, bạn đã sẵn sàng. Bạn cũng có thể kiểm tra bằng curl:

curl -I localhost:8080

Bạn nhận được lỗi 'Connection refused'? Tuyệt vời. Điều đó có nghĩa là cổng đã hoàn toàn trống và sẵn sàng cho server Go của bạn.

Mẹo nâng cao về mạngĐôi khi xung đột không chỉ do tiến trình cũ để lại. Nếu bạn đang quản lý các subnet phức tạp trong Docker hoặc Kubernetes, việc gỡ lỗi dải IP có thể rất rắc rối. Tôi thường sử dụng công cụ Subnet Calculator trên ToolCraft để kiểm tra CIDR mask và dải IP nội bộ. Đây là một cách tiện lợi để đảm bảo dịch vụ của bạn không cố gắng gán (bind) vào một địa chỉ đã được dành riêng bởi một network interface ảo.

Một mẹo cuối cùng: Trong quá trình test local hoặc chạy CI/CD, hãy sử dụng cổng :0. Go sẽ tự động gán một cổng ngẫu nhiên ở dải cao chắc chắn còn trống, giúp bạn tránh khỏi lỗi 'address already in use' khi chạy các bài test tự động.

Related Error Notes