Sửa lỗi "Too many open files" (EMFILE) trên Server Traffic Cao

intermediate🌐 Networking2026-04-20| Linux (Ubuntu 20.04/22.04, CentOS 7/8, Debian), nginx, Node.js, Apache, mọi server TCP kết nối cao

Error Message

Error: accept(2) failed: Too many open files (EMFILE)
#linux#networking#ulimit#socket#server

Lỗi gặp phải

Error: accept(2) failed: Too many open files (EMFILE)

Server của bạn đã chạm ngưỡng file descriptor và không thể chấp nhận kết nối mới. Trên Linux, mỗi socket đang mở đều tính là một FD — kết nối TCP, UDP socket, unix socket và các file thông thường đều dùng chung một pool. Khi pool đó cạn kiệt, accept(2) trả về EMFILE và client bị từ chối.

Lỗi này thường xuất hiện khi chịu tải cao: stress test, traffic đột ngột tăng vọt, hoặc connection leak âm thầm tích lũy trong nhiều giờ.

Nguyên nhân gốc rễ

Linux áp đặt hai giới hạn cho file descriptor đang mở:

  • Giới hạn theo process (ulimit -n) — số FD tối đa mà một process có thể giữ. Mặc định là 1024 trên CentOS 7 và các distro cũ, 65536 trên Ubuntu 22.04+ và RHEL mới hơn.
  • Giới hạn toàn hệ thống (/proc/sys/fs/file-max) — tổng số FD trên tất cả process của host.

Giới hạn theo process hầu như luôn là thủ phạm. Một nginx worker xử lý 600 kết nối đồng thời sẽ vượt ngưỡng 1024 FD trong vài giây — và bạn sẽ không nhận ra cho đến khi client bắt đầu bị từ chối.

Chẩn đoán trước khi sửa

Trước khi thay đổi bất cứ điều gì, hãy kiểm tra giới hạn mà process bị ảnh hưởng thực sự thấy khi đang chạy:

# Tìm PID
pgrep nginx  # hoặc node, apache2, v.v.

# Kiểm tra giới hạn FD hiện tại của process
cat /proc/<PID>/limits | grep 'open files'

# Xem số FD đang dùng
ls /proc/<PID>/fd | wc -l

# Mức sử dụng toàn hệ thống so với giới hạn
cat /proc/sys/fs/file-nr
# Kết quả: [FD đang mở] [0] [FD tối đa]

Mức sử dụng hiện tại gần sát giới hạn hiển thị trong /proc/<PID>/limits? Đó là vấn đề của bạn. Kiểm tra thêm connection leak:

# Đếm socket theo trạng thái cho process của bạn
ss -s

# Xem tất cả kết nối cho một port cụ thể
ss -tnp | grep ':8080'

# Có bao nhiêu TIME_WAIT đang tích lũy?
ss -tan | grep TIME-WAIT | wc -l

Hàng nghìn kết nối TIME_WAIT hoặc CLOSE_WAIT giải thích tại sao FD bị giữ lâu hơn dự kiến. Hãy sửa connection leak trước — tăng giới hạn chỉ là giải pháp tạm thời.

Cách sửa 1: Tăng ulimit theo process (Tức thì)

Để sửa nhanh trên hệ thống đang chạy, đặt giới hạn cho shell hiện tại rồi khởi động lại service:

ulimit -n 65536

Cách này chỉ áp dụng cho phiên shell hiện tại. Để lưu vĩnh viễn qua các lần khởi động lại, chỉnh sửa /etc/security/limits.conf:

# /etc/security/limits.conf
*    soft    nofile    65536
*    hard    nofile    65536

# Hoặc nhắm đến user cụ thể (ví dụ: www-data cho nginx)
www-data    soft    nofile    65536
www-data    hard    nofile    65536

Đăng xuất rồi đăng nhập lại (hoặc khởi động lại service) để có hiệu lực. Xác nhận đã áp dụng:

su - www-data -s /bin/bash -c 'ulimit -n'

Cách sửa 2: Đặt giới hạn trong Unit File của systemd

Trên hầu hết các Linux server hiện đại, limits.conf thực ra không giúp ích gì. Các service của systemd thường bỏ qua PAM hoàn toàn, nên cài đặt theo user trong file đó không bao giờ được áp dụng cho process service. Hãy đặt giới hạn trực tiếp trong unit:

# Tạo file override
systemctl edit nginx

# Thêm nội dung này vào editor:
[Service]
LimitNOFILE=65536

Hoặc tạo file thủ công:

# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65536

Sau đó reload và restart:

systemctl daemon-reload
systemctl restart nginx

# Xác nhận giới hạn mới đã có hiệu lực
cat /proc/$(pgrep nginx | head -1)/limits | grep 'open files'

Cách sửa 3: Tăng giới hạn toàn hệ thống

Đang chạy nhiều service, hoặc giới hạn theo process đã cao nhưng vẫn gặp lỗi? Giới hạn toàn hệ thống có thể là điểm nghẽn:

# Kiểm tra mức tối đa toàn hệ thống hiện tại
cat /proc/sys/fs/file-max

# Tăng tạm thời
sysctl -w fs.file-max=2097152

# Lưu vĩnh viễn
echo 'fs.file-max = 2097152' >> /etc/sysctl.conf
sysctl -p

Cách sửa 4: Cấu hình riêng cho nginx

nginx có directive worker_rlimit_nofile riêng để ghi đè giới hạn OS cho các worker process:

# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65536;

events {
    worker_connections 4096;  # mỗi worker process
    use epoll;
    multi_accept on;
}

Công thức tính: worker_connections × worker_processes × 2 ≤ worker_rlimit_nofile. Mỗi kết nối dùng ít nhất 2 FD — một cho client socket, một cho upstream hoặc file đang được phục vụ.

Cách sửa 5: Máy chủ Node.js

Node.js kế thừa giới hạn FD từ process đã khởi chạy nó — không có cấu hình riêng cho Node. Một server xử lý 2.000 kết nối WebSocket đồng thời cần ít nhất 2.000 FD chỉ cho các socket đó, chưa tính kết nối DB, file handle log hay bất cứ thứ gì khác. Hãy đảm bảo shell hoặc systemd unit khởi chạy node có giới hạn đúng. Bạn có thể ghi log khi khởi động để xác nhận:

const { execSync } = require('child_process');

// Ghi log giới hạn FD khi khởi động
try {
  const limit = execSync('ulimit -n').toString().trim();
  console.log(`FD limit: ${limit}`);
} catch (e) {
  console.error('Could not read FD limit');
}

Đang dùng PM2? Cấu hình app thôi chưa đủ — cần đặt LimitNOFILE trong systemd service của PM2 nữa:

# ecosystem.config.js
module.exports = {
  apps: [{
    name: 'myapp',
    script: 'server.js',
    node_args: '--max-old-space-size=2048',
    env: {
      NODE_ENV: 'production'
    }
  }]
};

# Và trong /etc/systemd/system/pm2-root.service.d/override.conf:
# [Service]
# LimitNOFILE=65536

Xác nhận đã sửa thành công

Sau khi khởi động lại, kiểm tra giới hạn thực sự đang có hiệu lực — đừng giả định:

# Xác nhận giới hạn mới đã hoạt động
cat /proc/$(pgrep -f 'nginx: master' | head -1)/limits | grep 'open files'

# Theo dõi mức dùng FD theo thời gian thực khi chịu tải
watch -n1 'ls /proc/$(pgrep nginx | head -1)/fd | wc -l'

# Chạy load test nhanh để xác nhận không còn gặp EMFILE
# (cài bằng: sudo apt install apache2-utils)
ab -n 10000 -c 500 http://localhost/

# Kiểm tra error log — phải sạch
tail -f /var/log/nginx/error.log

Phòng ngừa

Một số biện pháp đã giúp tôi tránh gặp lại lỗi này:

  • Đặt giới hạn trong systemd unit, không chỉ trong limits.conf. Trên các hệ thống quản lý bằng systemd, file unit được ưu tiên. Luôn đặt LimitNOFILE ở đó.
  • Giám sát mức dùng FD. Cảnh báo khi bất kỳ process nào vượt 80% giới hạn FD. Prometheus node_exporter cung cấp node_filefd_allocatednode_filefd_maximum — một rule cảnh báo đơn giản như (node_filefd_allocated / node_filefd_maximum) > 0.8 cho bạn cảnh báo sớm trước khi hệ thống sập.
  • Bật SO_REUSEPORT trên server nhiều worker để phân phối tải accept giữa các worker, giảm khả năng một worker bị bão hòa FD.
  • Tinh chỉnh TIME_WAIT recycling nếu bạn thấy hàng nghìn kết nối lơ lửng chiếm dụng FD:

/etc/sysctl.conf

net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 15

  
  - **Kiểm tra cách xử lý kết nối trong ứng dụng.** HTTP client, DB connection pool và file handle cần được đóng tường minh. Connection leak sẽ làm cạn kiệt FD dù giới hạn rất cao — 65536 FD biến mất rất nhanh khi không có gì dọn dẹp.

Khi cần xác định dải IP nào đang tấn công server của bạn, [Subnet Calculator trên ToolCraft](https://toolcraft.app/en/tools/developer/ip-subnet-calculator) rất tiện để tính nhanh các dải CIDR — chạy hoàn toàn trên trình duyệt, không upload dữ liệu lên đâu cả.

Related Error Notes