Khi website hoặc dịch vụ API trên VPS đón nhận lượng truy cập tăng đột biến, máy chủ có thể gặp hiện tượng treo kết nối, khách truy cập nhận phản hồi chậm hoặc trình duyệt báo lỗi Connection refused. Nếu mở công cụ giám sát kiểm tra (như cách chẩn đoán trong bài viết phân tích tiến trình CPU 100%), bạn thấy tỷ lệ sử dụng CPU và dung lượng RAM vẫn còn rất dồi dào. Trong nhiều trường hợp, điểm nghẽn không nằm ở mã nguồn hay cấu hình Nginx, mà xuất phát từ các giá trị mặc định của nhân hệ điều hành Linux.
Nhân Linux được biên dịch để tương thích với nhiều mục đích khác nhau, từ máy tính cá nhân đến máy chủ cỡ lớn, nên các thông số mạng mặc định thường được đặt ở mức an toàn tối thiểu. Khi máy chủ phải tiếp nhận hàng nghìn kết nối đồng thời, việc chủ động tinh chỉnh các tham số mạng và socket qua /etc/sysctl.conf—từ việc mở rộng hàng đợi kết nối, chuyển sang thuật toán BBR đến giải phóng an toàn các cổng TIME_WAIT—sẽ giúp hệ thống khai thác tối đa băng thông mà không rơi vào tình trạng nghẽn cổ chai.
1. Nút thắt cổ chai ở hàng đợi kết nối: somaxconn và tcp_max_syn_backlog
Để một trình duyệt web có thể gửi request HTTP đến Nginx, hai bên phải hoàn tất quy trình bắt tay 3 bước TCP (Three-Way Handshake): SYN -> SYN-ACK -> ACK. Trong nhân Linux, quy trình này được điều tiết thông qua hai hàng đợi riêng biệt.
Cơ chế hoạt động của hai hàng đợi TCP
- Hàng đợi nửa mở (SYN Queue -
net.ipv4.tcp_max_syn_backlog):
- Khi client gửi gói tin
SYNđầu tiên đến cổng 443 của máy chủ, kernel tạo một trạng thái kết nối nửa mở (Half-open connection) và đặt vào hàng đợi này trong khi chờ client gửi lại gói tinACKcuối cùng. - Nếu có một đợt tấn công SYN Flood hoặc hàng ngàn người dùng cùng truy cập trong một giây, hàng đợi này sẽ đầy rất nhanh.
- Hàng đợi hoàn tất (Accept Queue -
net.core.somaxconn):
- Sau khi nhận được gói
ACKtừ client, kết nối chính thức được thiết lập (Established) và được chuyển sang hàng đợi Accept Queue. - Tại đây, kết nối nằm chờ tiến trình ứng dụng (Nginx worker) gọi hàm hệ thống
accept()để tiếp nhận và bắt đầu xử lý request HTTP.
Vấn đề với giá trị mặc định của hệ điều hành
Trên hầu hết các bản phân phối Linux như Ubuntu Server hoặc Debian, giá trị mặc định của net.core.somaxconn theo lịch sử thường chỉ là 128 (hoặc 4096 trên một số bản mới).
Khi lưu lượng truy cập tăng vọt đột ngột (ví dụ khi chạy chiến dịch quảng cáo hoặc có sự kiện bán hàng):
- Tốc độ kết nối ập đến vượt quá tốc độ gọi hàm
accept()của Nginx trong vài phần trăm giây. - Hàng đợi Accept Queue bị tràn (Overflow).
- Hành vi mặc định nguy hiểm của Linux Kernel: Kernel sẽ âm thầm vứt bỏ (drop) các gói tin kết nối mới mà không gửi bất kỳ phản hồi nào, hoặc trả về lỗi
Connection reset by peer. Người dùng sẽ thấy trình duyệt xoay vòng tròn chờ đợi rồi báo lỗi không thể kết nối tới website, trong khi biểu đồ CPU và RAM của VPS trên trang quản trị vẫn ở mức rất thấp (chỉ 10% đến 20%).
Tinh chỉnh mở rộng hàng đợi
Bạn cần nâng dung lượng của cả hai hàng đợi lên mức đủ an toàn để tiếp nhận các đỉnh xung đột lưu lượng:
# Mở rộng hàng đợi kết nối nửa mở (SYN Queue)
net.ipv4.tcp_max_syn_backlog = 65535
# Mở rộng hàng đợi kết nối hoàn tất chờ ứng dụng xử lý (Accept Queue)
net.core.somaxconn = 65535
# Tăng hàng đợi tiếp nhận gói tin ở tầng giao tiếp mạng (NIC Driver)
net.core.netdev_max_backlog = 10000Lưu ý đồng bộ quan trọng: Nếu bạn chỉ tăng net.core.somaxconn = 65535 trong kernel nhưng trong file cấu hình Nginx bạn vẫn để chỉ thị mặc định listen 80;, Nginx sẽ sử dụng giá trị backlog mặc định cũ (thường là 511). Bạn bắt buộc phải khai báo rõ ràng tham số backlog trong nginx.conf:
server {
listen 80 backlog=65535;
listen 443 ssl backlog=65535;
# ...
}2. Thuật toán kiểm soát tắc nghẽn BBR: Tăng tốc độ truyền tải trên mạng thực tế
Mặc định, nhân Linux sử dụng thuật toán kiểm soát tắc nghẽn CUBIC (hoặc Reno). Mặc dù hoạt động tốt trên các mạng nội bộ ổn định, CUBIC bộc lộ nhược điểm rất lớn trên Internet công cộng.
Hạn chế của thuật toán kiểm soát tắc nghẽn CUBIC
CUBIC là thuật toán dựa trên phát hiện mất gói (Loss-based Congestion Control):
- CUBIC giả định rằng: Bất kỳ khi nào có một gói tin bị mất (Packet Loss), điều đó đồng nghĩa với việc đường truyền mạng đã bị nghẽn hoàn toàn.
- Ngay khi phát hiện mất gói, CUBIC sẽ chủ động cắt giảm cửa sổ truyền tải (Congestion Window) xuống 50%, sau đó tăng tốc độ lại một cách chậm chạp.
- Trong thực tế tại Việt Nam và kết nối quốc tế, việc mất một vài gói tin ngẫu nhiên trên mạng Wi-Fi hoặc mạng di động 4G/5G là điều rất phổ biến (do nhiễu sóng chứ không phải do đường truyền bị nghẽn). Việc CUBIC liên tục "hãm phanh" khiến tốc độ tải trang web của người dùng bị gián đoạn và chậm chạp.
Bước đột phá từ thuật toán Google BBR (Bottleneck Bandwidth and RTT)
Thuật toán BBR thay đổi hoàn toàn cách tiếp cận:
- Thay vì nhìn vào việc mất gói tin, BBR chủ động đo lường hai thông số vật lý cốt lõi của đường truyền: Băng thông nút thắt tối đa (Bottleneck Bandwidth) và Thời gian trễ truyền gói tối thiểu (Round-Trip Propagation Time - RTT).
- BBR luôn duy trì tốc độ truyền dữ liệu ở mức tối ưu nhất mà đường truyền có thể tiếp nhận. Ngay cả khi có tỷ lệ mất gói ngẫu nhiên từ 1% đến 5%, BBR vẫn duy trì băng thông tối đa mà không bị sụt giảm tốc độ.
- Các bài kiểm thử thực tế cho thấy BBR giúp giảm thời gian phản hồi trang web và tăng tốc độ tải các file ảnh, CSS, JavaScript lên từ 15% đến 40%, đặc biệt vượt trội trên các kết nối mạng di động hoặc qua cáp quang quốc tế.
Cách kích hoạt BBR chuẩn trên Linux Kernel hiện đại
Kể từ kernel 4.9 trở lên, BBR đã được tích hợp sẵn trong Linux. Để kích hoạt BBR hoạt động chính xác, bạn bắt buộc phải đi kèm với thuật toán lập lịch hàng đợi công bằng Fair Queueing (fq):
Thêm hai dòng sau vào file cấu hình sysctl:
# Đặt thuật toán xếp hàng gói tin mặc định sang Fair Queueing
net.core.default_qdisc = fq
# Kích hoạt thuật toán điều khiển tắc nghẽn BBR
net.ipv4.tcp_congestion_control = bbrSau khi áp dụng cấu hình, bạn kiểm tra lại bằng lệnh:
sysctl net.ipv4.tcp_congestion_control
# Kết quả trả về phải là: net.ipv4.tcp_congestion_control = bbr
lsmod | grep bbr
# Kết quả hiển thị module tcp_bbr đang được nạp vào nhân3. Xử lý hàng ngàn kết nối TIME_WAIT an toàn
Khi máy chủ VPS của bạn vận hành Nginx làm Reverse Proxy đứng trước các backend như PHP-FPM, Node.js, Go hay Python Gunicorn:
- Mỗi khi có một người dùng truy cập, Nginx tiếp nhận kết nối phía ngoài, đồng thời mở một kết nối mạng TCP nội bộ mới (Local Loopback) tới backend.
- Sau khi trả xong dữ liệu, kết nối TCP nội bộ này được đóng lại và chuyển sang trạng thái chờ
TIME_WAIT.
Hiện tượng cạn kiệt dải cổng tạm thời (Ephemeral Port Exhaustion)
Mặc định, nhân Linux giữ các kết nối ở trạng thái TIME_WAIT trong vòng 60 giây (tcp_fin_timeout = 60) để đảm bảo không có gói tin nào bị thất lạc trên mạng.
Nếu trang web của bạn có 5.000 request trong một phút:
- Hàng ngàn kết nối
TIME_WAITtích lũy đồng thời trên máy chủ. - Linux sử dụng dải cổng tạm thời (Ephemeral Ports) để mở các kết nối đi ra này.
- Khi toàn bộ các cổng trong dải bị chiếm dụng hết bởi các socket đang chờ
TIME_WAIT, Nginx sẽ không thể mở thêm bất kỳ kết nối nào tới backend nữa. Người dùng sẽ nhận ngay mã lỗiHTTP 502 Bad GatewayhoặcCannot assign requested address.
Cảnh báo lỗi thời: Tuyệt đối không bật tcp_tw_recycle
Trong nhiều bài viết hướng dẫn cũ trên mạng, người ta thường khuyên bật tham số: net.ipv4.tcp_tw_recycle = 1.
Đây là cấu hình cực kỳ nguy hiểm:
tcp_tw_recycledựa vào nhãn thời gian TCP Timestamps để tái sử dụng socket một cách hung hãn.- Khi người dùng ở mạng văn phòng, quán cà phê hoặc mạng di động 4G/5G cùng đi qua một cổng NAT (cùng chung một địa chỉ IP public), nhãn thời gian từ các thiết bị khác nhau gửi tới máy chủ sẽ bị lệch nhau.
- Kernel Linux hiểu lầm rằng các gói tin này đến sai thứ tự và âm thầm thả rơi (drop) các gói SYN hợp lệ. Khách hàng sẽ bị ngắt kết nối ngẫu nhiên một cách khó hiểu.
- Chính vì lỗi kiến trúc này, nhân Linux đã chính thức gỡ bỏ hoàn toàn tham số
tcp_tw_recycletừ phiên bản Kernel 4.12. Nếu bạn cố tình cấu hình dòng này trên các bản Linux hiện đại (Ubuntu 20.04/22.04/24.04, Debian 11/12), hệ thống sẽ báo lỗicannot stat /proc/sys/net/ipv4/tcp_tw_recycle: No such file or directory.
Bộ ba tham số chuẩn mực để xử lý TIME_WAIT
Giải pháp chuẩn xác và an toàn cho môi trường sản xuất hiện nay là sử dụng bộ ba tham số sau:
# Cho phép kernel tái sử dụng an toàn các socket TIME_WAIT cho kết nối mới
net.ipv4.tcp_tw_reuse = 1
# Rút ngắn thời gian chờ đóng socket từ 60 giây xuống 15 giây
net.ipv4.tcp_fin_timeout = 15
# Mở rộng dải cổng tạm thời từ cổng 10240 đến 65535 (hơn 55.000 cổng)
net.ipv4.ip_local_port_range = 10240 65535Cấu hình này giải phóng các cổng mạng nhanh hơn gấp 4 lần và mở rộng kho cổng khả dụng, giúp máy chủ xử lý hàng chục ngàn kết nối đồng thời mà không bao giờ bị nghẽn cổng kết nối nội bộ.
4. Đồng bộ 3 tầng giới hạn File Descriptors (nofile)
Một nguyên lý nền tảng trong hệ điều hành Linux: Tất cả mọi thứ đều là tệp tin (Everything is a file). Mỗi kết nối mạng TCP socket, mỗi file ảnh tĩnh được mở, mỗi kết nối tới cơ sở dữ liệu đều tiêu tốn đúng một thẻ mô tả tệp tin (File Descriptor - FD).
Khi Nginx đóng vai trò là reverse proxy, một request từ khách hàng thường tiêu tốn tối thiểu 2 File Descriptors:
- Một FD cho kết nối giữa trình duyệt khách hàng và Nginx.
- Một FD cho kết nối giữa Nginx và ứng dụng backend (hoặc file tĩnh trên ổ đĩa).
Nếu giới hạn file descriptor của hệ điều hành quá thấp (mặc định cho mỗi tiến trình thường chỉ là 1024), Nginx sẽ lập tức bị chặn lại khi lượng kết nối đồng thời vượt quá 500, ghi nhận lỗi nghiêm trọng trong log:
2026/04/12 14:10:05 [alert] 1234#1234: *5678 socket() failed (24: Too many open files) while connecting to upstreamĐể giải quyết triệt để lỗi này, bạn phải đồng bộ giới hạn trên cả 3 tầng của hệ thống:
Tầng 1: Giới hạn toàn hệ thống trong Kernel (sysctl)
Khai báo tổng số file descriptor tối đa mà toàn bộ hệ điều hành được phép mở:
fs.file-max = 2097152Tầng 2: Giới hạn dịch vụ trong Systemd
Trên các bản phân phối Linux hiện đại, Systemd quản lý dịch vụ Nginx và hoàn toàn bỏ qua các file cấu hình cũ /etc/security/limits.conf. Bạn phải gán giới hạn trực tiếp cho dịch vụ Nginx trong systemd:
Tạo file ghi đè cấu hình dịch vụ:
sudo systemctl edit nginxDán nội dung sau vào cửa sổ soạn thảo:
[Service]
LimitNOFILE=65535Lưu file và tải lại dịch vụ:
sudo systemctl daemon-reload
sudo systemctl restart nginxKiểm tra giới hạn thực tế của tiến trình Nginx đang chạy:
cat /proc/$(cat /var/run/nginx.pid)/limits | grep "Max open files"Kết quả phải hiển thị cả Soft Limit và Hard Limit ở mức 65535.
Tầng 3: Khai báo giới hạn trong Nginx (nginx.conf)
Tại dòng đầu tiên của file /etc/nginx/nginx.conf (nằm ngoài khối events), khai báo chỉ thị:
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535; # Khớp với giới hạn của systemd
events {
worker_connections 16384; # Số lượng kết nối tối đa cho mỗi worker
use epoll;
multi_accept on;
}5. Tệp cấu hình mẫu hoàn chỉnh và Quy trình áp dụng an toàn
Thay vì sửa đổi trực tiếp vào file gốc /etc/sysctl.conf, phương pháp quản trị chuẩn mực hiện nay là tạo một file cấu hình riêng biệt trong thư mục /etc/sysctl.d/. Cách làm này giúp cấu hình của bạn không bị ghi đè khi cập nhật hệ điều hành và dễ dàng sao lưu hoặc gỡ bỏ khi cần.
Tạo file cấu hình tại /etc/sysctl.d/99-web-performance.conf:
# ==============================================================================
# HTS VIET - TOI UU HIEU NANG LINUX SYSCTL CHO WEB SERVER TRAFFIC CAO
# ==============================================================================
# 1. HANG DOI KET NOI MANG (CHONG NGHIEN SYN FLOOD VA TRAFFIC SPIKE)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 10000
# 2. THUAT TOAN KIEU SOAT TAC NGHEN (BBR + FAIR QUEUEING)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# 3. XU LY TRANG THAI TIME_WAIT VA EPHEMERAL PORTS
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.ip_local_port_range = 10240 65535
# 4. GIOI HAN FILE DESCRIPTORS HE THONG
fs.file-max = 2097152
# 5. TOI UU BO DEM SOCKET (SOCKET BUFFER MEMORY)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 6. PHONG VE AN NINH MANG CO BAN
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1Quy trình áp dụng ngay lập tức mà không cần Reboot
Áp dụng toàn bộ cấu hình mới vào kernel:
sudo sysctl --systemLệnh này sẽ quét toàn bộ các file trong /etc/sysctl.d/ và nạp trực tiếp vào kernel của hệ điều hành đang chạy mà không làm gián đoạn bất kỳ kết nối mạng nào của khách hàng.
Kiểm tra và giám sát kết quả
Sau khi áp dụng, bạn có thể kiểm tra xem máy chủ có còn gặp hiện tượng nghẽn hàng đợi hay không bằng lệnh:
# Kiểm tra số lượng kết nối đang ở trạng thái TIME_WAIT và ESTABLISHED
ss -s
# Kiểm tra xem có kết nối nào bị drop do tràn Accept Queue không
netstat -s | grep -i "listen"Nếu dòng times the listen queue of a socket overflowed không còn tăng số đếm sau các đợt cao điểm truy cập, hệ thống của bạn đã được giải phóng khỏi các nút thắt cổ chai ở tầng kernel và sẵn sàng phục vụ lượng truy cập lớn một cách mượt mà và bền bỉ.
Tuy nhiên, tối ưu tầng mạng và socket chỉ là bước đầu tiên trong việc khai thác tối đa tài nguyên Single-VPS. Để ngăn chặn tình trạng máy chủ sập nguồn khi bộ nhớ RAM đạt ngưỡng giới hạn, bạn nên tiếp tục thiết lập bộ đệm an toàn trong bài viết cấu hình Swap và vm.swappiness chống OOM, đồng thời kết hợp giải phóng tải CPU cho backend thông qua Nginx FastCGI microcaching.



