Cảnh báo php-fpm max_children: tìm nguyên nhân trong log trước khi tăng worker

Thông báo tiếp thị liên kết Ver 3.1
Thông báo tiếp thị liên kết Ver 3.2 (chỉ trên di động)
Môi trường thử nghiệm Họ RHELAlmaLinux 9.8Tài khoản rootKiểm tra ngày 2026-10-12

Cảnh báo php-fpm max_children là dấu hiệu cho thấy mọi tiến trình worker PHP đều đang xử lý yêu cầu nên yêu cầu mới phải vào hàng đợi. Trên một máy chủ WordPress chạy Nginx, Apache và php-fpm, nơi cảnh báo này tích tụ suốt nhiều tuần, các yêu cầu làm đầy worker đã được lọc ra từ log, và vấn đề được giải quyết bằng giới hạn tốc độ của nginx thay vì nâng giá trị cài đặt.

Trong đợt kiểm tra hằng tuần, cảnh báo được ghi 27–187 lần mỗi ngày, nhưng trong bốn ngày cùng kỳ, khách truy cập nhận 0 lỗi 502·504. Thứ làm đầy worker không phải khách truy cập mà là một bot liên tục yêu cầu địa chỉ quản trị và một máy quét yêu cầu những đường dẫn không tồn tại.

1️⃣ php-fpm max_children & seems busy

Khi cảnh báo tích lại 66 lần một ngày, rất dễ hiểu thành worker đã thiếu 66 lần. Log của php-fpm trộn lẫn hai loại cảnh báo khác nhau, và phần lớn là cảnh báo sớm được ghi trước khi chạm giới hạn.

[09-Oct-2026 00:16:18] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 0 idle, and 15 total children
[09-Oct-2026 13:02:05] WARNING: [pool www] server reached pm.max_children setting (26), consider raising it

[seems busy] là thông báo cho biết số worker rảnh đã về 0 và một tiến trình mới đang được tạo; nếu cách quản lý tiến trình là dynamic, chỉ cần yêu cầu tăng lên một chút cũng bị ghi lại. [server reached pm.max_children] là thời điểm đã chạm giới hạn 26 và không thể tạo thêm, từ lúc đó yêu cầu mới phải chờ.

Giống như sự khác nhau giữa việc gọi thêm một nhân viên trong nhà hàng và việc để khách đứng ngoài vì đã hết chỗ ngồi.

Đếm dữ liệu một ngày theo từng loại thì có 58 lần seems busy và 6 lần max_children. Quy trình kiểm tra cộng cả hai cảnh báo và báo cáo 66 lần, nhưng thực tế chỉ chạm giới hạn sáu lần.

grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'pool www' | grep -c 'seems busy'
58
grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'max_children' | cut -c2-21
09-Oct-2026 13:02:05
09-Oct-2026 13:52:10
09-Oct-2026 18:09:20
09-Oct-2026 18:42:17
09-Oct-2026 18:46:07
09-Oct-2026 19:12:44

Sáu thời điểm do lệnh thứ hai in ra là mốc để truy nguyên nhân, và bước tiếp theo chỉ dùng những thời điểm này.

2️⃣ Vì sao tăng max_children không phải là lời giải

Nâng giới hạn 26 có thể làm giảm số cảnh báo, nhưng không thay đổi việc yêu cầu nào đã làm đầy worker.

Cách tính thông thường là lấy bộ nhớ khả dụng chia cho bộ nhớ trung bình của một worker. Trên máy chủ được đo, 26 worker dùng trung bình 131MB, tối đa 214MB, và nếu tính theo bộ nhớ khả dụng còn lại thì về mặt con số vẫn còn dư để tạo thêm worker.

ps -o rss= -u www -C php-fpm | awk '{s+=$1; n++; if($1>m) m=$1} END{print n, s/n/1024, m/1024, s/1024}'
26 130.579 214.27 3395.05
free -m

Bốn giá trị ở kết quả đầu tiên là số worker, bộ nhớ trung bình (MB), bộ nhớ tối đa (MB) và tổng (MB); cột available trong kết quả của free là bộ nhớ khả dụng.

Khi số worker đã nhiều hơn số lõi CPU, tăng worker không làm tăng số yêu cầu được xử lý đồng thời mà chỉ kéo dài thời gian cho mỗi yêu cầu. Khi khách truy cập nhận 0 lỗi 502·504, nâng giới hạn không đem lại gì, chỉ cấp thêm tiến trình cho bot và máy quét vốn đang chiếm worker.

Trước khi nâng giới hạn, cần kiểm tra xem các yêu cầu chiếm worker vào thời điểm cảnh báo được ghi có phải là khách truy cập hay không; trình tự kiểm tra điều đó bằng access.log được trình bày ở phần tiếp theo.

3️⃣ Truy nguyên nhân bằng access.log: thời điểm, IP, đường dẫn, mã phản hồi

Chọn một thời điểm có cảnh báo, câu trả lời nằm trong access.log của 1–2 phút đó. Trình tự là lọc các dòng theo thời điểm, đếm theo IP, rồi xem đường dẫn và mã phản hồi của các IP đứng đầu.

grep '09/Oct/2026:18:09' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
    115 140.245.x.x
     61 187.15.x.x
     17 3.36.x.x
      8 91.239.x.x
      4 79.127.x.x
      3 109.248.x.x
      3 109.248.x.x
      3 109.248.x.x
      2 79.127.x.x
      2 40.77.x.x
grep '09/Oct/2026:18:09' /var/log/nginx/access.log | awk '{print $7, $9}' | sort | uniq -c | sort -rn | head
     55 / 200
     13 /wp-login.php 404
     13 /wp-admin/plugins.php 302
     13 /wp-admin/index.php 302
     13 /wp-admin/edit.php 302
     12 /wp-admin/profile.php 302
      4 / 499
      3 /xmlrpc.php 302
      2 /xmlrpc.php 499
      2 /wp-content/themes/kinsta/images/duos/small/wheelchair.svg 404

Lúc 18:09 ngày 9 tháng 10, có 286 yêu cầu được ghi trong một phút; một IP chiếm 115 yêu cầu trong số đó, đường dẫn là địa chỉ quản trị như /wp-admin/profile.php, và mã phản hồi đều là 302.

Mã phản hồi cho biết yêu cầu có tới PHP hay không. Mã 200 do nginx trả thẳng HTML đã cache, cùng 444·400, không đi qua PHP; còn 302·404 và 200 chưa cache thì mỗi cái dùng một worker. Khi yêu cầu địa chỉ quản trị mà chưa đăng nhập, WordPress trả 302 về trang đăng nhập, và một 302 đó là một lần chạy PHP.

Nếu một máy chủ chạy từ hai website trở lên, các dòng của cả hai website được ghi chung vào access.log toàn cục. Ban đầu có vẻ con bot này tấn công website thứ nhất, nhưng khi đối chiếu với log của từng website thì 22.390 trong số 22.391 yêu cầu là của website thứ hai.

4️⃣ Ba loại yêu cầu làm đầy worker: wp-admin của bot, 404 của máy quét, tải trước cache

Áp dụng cùng trình tự cho mọi thời điểm có cảnh báo, các yêu cầu làm đầy worker thuộc ba loại.

Loại thứ nhất là một bot liên tục yêu cầu địa chỉ quản trị. Một IP gửi 22.391 yêu cầu trong ba giờ; 9.944 yêu cầu trong số đó nhắm vào bốn đường dẫn, gồm cả /wp-admin/profile.php, và vì mỗi 302 khi chưa đăng nhập là một lần chạy PHP nên worker đã bị dùng 9.944 lần.

Loại thứ hai là một máy quét lỗ hổng yêu cầu các đường dẫn không tồn tại. Dù User-Agent ghi Googlebot, đó không phải Googlebot: IP gửi 14.672 yêu cầu mỗi ngày là một máy chủ ảo Google Cloud có DNS ngược kết thúc bằng googleusercontent.com, còn Googlebot kết thúc bằng googlebot.com. Cùng IP đó còn luân phiên dùng bingbot, Applebot, GPTBot và ClaudeBot.

Yêu cầu tệp như .env bị nginx chặn bằng 444 trong 0,04 giây, nhưng các đường dẫn thông thường không có trong quy tắc chặn như /userfiles hay /secrets.yml sẽ khiến WordPress tạo trang 404. Trang 404 này nặng 266KB, mất 1,4–2,9 giây, tốn như tạo một bài viết thường, và một IP đã yêu cầu nó 4.069 lần mỗi ngày.

Loại thứ ba là chính máy chủ. Khi lưu bài viết hoặc bản dịch, cache bị xóa và tính năng tải trước của WP Rocket yêu cầu lại mọi URL, 8.489 yêu cầu mỗi ngày, tối đa 60 yêu cầu mỗi phút. Việc kiểm tra trạng thái lập chỉ mục của Rank Math cũng dùng một worker 754 lần mỗi ngày thông qua Action Scheduler. Cả hai chỉ tập trung vào giờ làm việc và không gây lỗi cho khách truy cập, nên được loại khỏi danh sách cần xử lý.

Trong trình mô phỏng bên dưới, bạn có thể xem mỗi yêu cầu kết thúc ở lớp nào và có dùng worker PHP hay không.

🧭 Trình mô phỏng đường đi của yêu cầuTrang quản trị wp-adminTrình mô phỏng minh họa
🗂️ Các yêu cầu làm đầy worker
Yêu cầu wp-admin của bot
Khi yêu cầu địa chỉ quản trị mà chưa đăng nhập, WordPress trả 302 về trang đăng nhập. Một 302 đó là một lần chạy PHP.
Máy quét: kiểu .env
Yêu cầu tệp như .env bị quy tắc chặn của nginx cắt kết nối bằng 444. 0,04 giây, không đi qua PHP.
Máy quét: đường dẫn giả thông thường
Với đường dẫn thông thường không có trong quy tắc chặn như /userfiles hay /secrets.yml, WordPress sẽ tạo trang 404. 266KB, 1,4–2,9 giây, tốn như tạo một bài viết thường.
Tải trước cache
Khi lưu bài viết hoặc bản dịch, cache bị xóa và tính năng tải trước của WP Rocket yêu cầu lại mọi URL. Tối đa 60 yêu cầu mỗi phút, mỗi yêu cầu dùng một worker.
Truy cập bài đã cache
Nếu có tệp cache, nginx trả lời trực tiếp mà không qua PHP.
Truy cập bài chưa cache
Nếu không có tệp cache, PHP tạo trang rồi lưu HTML đó thành tệp cache.
Toàn bộ luồng
Yêu cầu đi từ trên xuống và dừng ở lớp nơi phản hồi kết thúc. Chỉ những yêu cầu đi xuống tới WordPress mới dùng worker PHP.
Yêu cầuDừng ở lớpPhản hồiWorker
wp-admin của botWordPress302 (đến trang đăng nhập)Dùng 1
Máy quét kiểu .envQuy tắc chặn của nginx444 (0,04 giây)Không dùng
Máy quét đường dẫn giả thông thườngWordPressTrang 404 (266KB · 1,4–2,9 giây)Dùng 1
Tải trước cacheWordPress200 · lưu thành tệp cacheDùng 1
Truy cập bài đã cacheTệp cache200 (nginx trả lời ngay)Không dùng
Truy cập bài chưa cacheWordPress200 · lưu thành tệp cacheDùng 1
Trình mô phỏng minh họa chỉ chạy trong trình duyệt. Phản hồi và thời gian lấy từ log và số đo thực tế của một máy chủ Nginx + Apache vào tháng 10 năm 2026.

5️⃣ Giới hạn tốc độ của nginx: giới hạn yêu cầu chưa đăng nhập và khôi phục IP thật

Giới hạn tốc độ được chọn thay cho việc chặn. Chặn đường dẫn thì máy quét đổi đường dẫn, chặn IP thì hôm sau một IP khác lại vào. Chỉ những yêu cầu chưa đăng nhập đi tới PHP mới bị giới hạn, ở mức 30 yêu cầu mỗi phút và 60 yêu cầu tức thời trên mỗi IP, phần vượt quá được trả về 429.

Với máy chủ đứng sau CDN, chỉ cấu hình này thì chưa đúng. IP mà nginx thấy là địa chỉ edge của CDN, nên mọi khách truy cập đi qua edge đó bị gộp thành một IP; cần khai báo danh sách IP edge bằng set_real_ip_from để khôi phục IP thật từ X-Forwarded-For trước. Yêu cầu có cookie đăng nhập được miễn, còn User-Agent thì không được dùng để miễn.

# ngữ cảnh http
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 203.0.113.0/24;   # danh sách edge CDN
map $http_cookie $php_anon_key {
    default                 $binary_remote_addr;
    ~*wordpress_logged_in   '';
}
limit_req_zone $php_anon_key zone=php_anon:10m rate=30r/m;

# location chuyển tới PHP
location @apache {
    limit_req zone=php_anon burst=60 nodelay;
    limit_req_status 429;
    proxy_pass http://127.0.0.1:8080;
}

Trang đã cache được try_files trả lời ngay trong nginx nên không tới location này, còn admin-ajax.php nằm ở location riêng nên không bị tính vào giới hạn.

Kiểm chứng bằng yêu cầu đồng thời. Yêu cầu tuần tự không vượt quá 30 yêu cầu mỗi phút vì mỗi 404 mất 1,5 giây, nên không bị giới hạn; gửi 100 yêu cầu, mỗi lần 20 yêu cầu đồng thời, sẽ được 67 lần 404 và 33 lần 429, xác nhận giới hạn đang hoạt động.

seq 1 100 | xargs -P 20 -I{} curl -s -o /dev/null -w '%{http_code}\n' https://example.com/zz-{} | sort | uniq -c

Nếu kết quả có đếm được 429 thì giới hạn đang hoạt động. Nếu đã miễn IP của chính máy chủ, hãy tạm bỏ miễn trừ khi chạy hoặc chạy từ một mạng khác.

6️⃣ So sánh trước và sau khi áp dụng

Giới hạn địa chỉ quản trị khi chưa đăng nhập được áp dụng vào tối 9 tháng 10, còn giới hạn yêu cầu tới PHP vào tối ngày 11. Số phản hồi 302 của địa chỉ quản trị giảm từ 9.996 vào ngày 9 xuống 11 vào ngày 10 và 1 vào ngày 11, còn IP từng gửi 22.391 yêu cầu trong ba giờ thì từ hôm sau không còn yêu cầu nào.

Với máy quét, giới hạn vừa mới áp dụng nên chưa có số liệu trọn một ngày; việc đánh giá sẽ dựa vào số trang 404 do WordPress tạo ra (ngày 9: 2.563, ngày 10: 2.612, ngày 11: 1.630) giảm xuống tới đâu trong đợt kiểm tra tuần sau. Lỗi 502·504 của khách truy cập đều là 0 cả trước và sau khi áp dụng.

Các con số này được đếm trực tiếp từ log của một máy chủ WordPress chạy Nginx, Apache và php-fpm, và việc đánh giá dựa trên lỗi của khách truy cập cùng số yêu cầu tới PHP chứ không dựa trên số cảnh báo.

📊Trước và sau khi áp dụng giới hạn tốc độTheo ngày · thống kê log
TrướcSau
Phản hồi 302 của địa chỉ quản trị
302 do WordPress gửi cho các yêu cầu wp-admin chưa đăng nhập. Giới hạn địa chỉ quản trị khi chưa đăng nhập áp dụng từ tối 9 tháng 10.
02,4994,9987,4979,996
10/99,996Trước
10/1011Sau
10/111Sau
Số yêu cầu từ IP bot
Số yêu cầu mỗi ngày của một IP từng gửi 22.391 yêu cầu trong ba giờ.
05,59811,19616,79322,391
10/922,391Trước
10/100Không có yêu cầu
10/110Không có yêu cầu
Trang 404 do WordPress tạo
Yêu cầu đường dẫn giả thông thường của máy quét. Giới hạn yêu cầu tới PHP áp dụng từ tối 11 tháng 10 nên sẽ đánh giá bằng số liệu kiểm tra tuần sau.
06531,3061,9592,612
10/92,563Trước
10/102,612Trước
10/111,630Trước · áp dụng tối ngày 11
Số liệu là thống kê theo ngày từ nginx access.log từ ngày 9 đến ngày 11 tháng 10 năm 2026.

🔢 FAQ & nội dung đề xuất

dynamic tạo và dừng worker theo lượng yêu cầu, và ghi cảnh báo seems busy khi số worker rảnh về 0. static luôn giữ đủ số worker bằng max_children nên không có độ trễ khi tạo và không có cảnh báo, nhưng bộ nhớ luôn bị chiếm. Máy chủ trong bài này giữ dynamic 26; nếu còn dư bộ nhớ và lượng yêu cầu đều đặn suốt ngày thì static là phù hợp.

Bật khi có vẻ một số yêu cầu chạy lâu vào thời điểm cảnh báo. Khi chỉ định đường dẫn [slowlog] và [request_slowlog_timeout] trong cấu hình pool, vị trí thực thi của những yêu cầu vượt quá thời gian đó sẽ được ghi lại. Trường hợp trong bài có nguyên nhân là số lượng yêu cầu, nên chỉ cần thống kê access.log là xác định được, và slowlog không được dùng.

Giới hạn chỉ áp dụng cho các yêu cầu chưa đăng nhập không đi qua cache mà tới thẳng PHP, và chỉ phần vượt quá mới nhận 429 khi một IP vượt 30 yêu cầu mỗi phút hoặc 60 yêu cầu tức thời. Trong thống kê ngày 11 tháng 10, IP duy nhất vượt ngưỡng này là một máy quét, và trạng thái vượt ngưỡng hết trong vòng 1 phút, nên từ yêu cầu tiếp theo phản hồi trở lại bình thường.

Không giảm. Tải trước chỉ tập trung vào giờ làm việc khi lưu bài viết và bản dịch, và không gây lỗi cho khách truy cập. Tiêu chí đánh giá không phải số cảnh báo mà là khách truy cập có nhận lỗi 502·504 hay không; nếu con số đó là 0 thì cứ để nguyên tải trước.

ℹ️ Thông báo tiếp thị liên kết
Nội dung trên trang này có chứa liên kết affiliate (tiếp thị liên kết). Khi khách truy cập mua sản phẩm hoặc dịch vụ qua các liên kết này, trang sẽ nhận hoa hồng từ bên bán. Trong quá trình đó, số tiền người mua phải trả (sẽ giảm khi có chương trình khuyến mãi ↓) không hề tăng lên. Giá, khuyến mãi và tình trạng hàng được đăng theo thời điểm viết bài và có thể khác với thực tế, vì vậy hãy kiểm tra lại lần cuối với bên bán trước khi mua. Việc chọn và đánh giá sản phẩm dựa trên tiêu chí riêng của trang; việc có nhận hoa hồng hay không không ảnh hưởng đến thứ tự giới thiệu hay nội dung đánh giá.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Prove your humanity: 7   +   5   =