คำเตือน php-fpm max_children: หาสาเหตุจากล็อกก่อนเพิ่มเวิร์กเกอร์

คำเตือน php-fpm max_children คือสัญญาณว่าโปรเซสเวิร์กเกอร์ของ PHP ทุกตัวกำลังประมวลผลคำขออยู่ คำขอใหม่จึงต้องเข้าคิว บนเซิร์ฟเวอร์ WordPress ที่ใช้ Nginx, Apache และ php-fpm ซึ่งมีคำเตือนนี้สะสมมาหลายสัปดาห์ เมื่อใช้ล็อกแยกคำขอที่ทำให้เวิร์กเกอร์เต็มออกมา ก็แก้ปัญหาได้ด้วยการจำกัดอัตราของ nginx แทนการเพิ่มค่าการตั้งค่า
ในการตรวจรายสัปดาห์ คำเตือนถูกบันทึกวันละ 27–187 ครั้ง แต่ในช่วงเดียวกันผู้เข้าชมได้รับข้อผิดพลาด 502·504 เป็น 0 ครั้งตลอดสี่วัน สิ่งที่ทำให้เวิร์กเกอร์เต็มไม่ใช่ผู้เข้าชม แต่เป็นบอตที่ขอที่อยู่หน้าผู้ดูแลซ้ำๆ และสแกนเนอร์ที่ขอพาธที่ไม่มีอยู่จริง
1️⃣ php-fpm max_children & seems busy
เมื่อคำเตือนสะสมวันละ 66 ครั้ง ก็ง่ายที่จะเข้าใจว่าเวิร์กเกอร์ไม่พอถึง 66 ครั้ง ในล็อกของ php-fpm มีคำเตือนสองแบบที่ต่างกันปนอยู่ และส่วนใหญ่เป็นคำเตือนล่วงหน้าที่บันทึกไว้ก่อนจะถึงขีดจำกัด
[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] เป็นการแจ้งว่าเวิร์กเกอร์ที่ว่างเหลือ 0 และกำลังสร้างโปรเซสใหม่ ถ้าตั้งการจัดการโปรเซสเป็น dynamic แม้คำขอเพิ่มขึ้นเพียงครู่เดียวก็จะถูกบันทึก [server reached pm.max_children] คือช่วงที่ถึงขีดจำกัด 26 ตัวแล้วสร้างเพิ่มไม่ได้อีก ตั้งแต่ตอนนั้นคำขอใหม่จะต้องรอ
เหมือนความต่างระหว่างการเรียกพนักงานเพิ่มอีกคนในร้านอาหาร กับการให้ลูกค้ายืนรอข้างนอกเพราะที่นั่งเต็มหมดแล้ว
เมื่อนับข้อมูลหนึ่งวันแยกตามประเภท ได้ seems busy 58 ครั้ง และ max_children 6 ครั้ง รูทีนการตรวจรวมคำเตือนทั้งสองแล้วรายงานเป็น 66 ครั้ง แต่ที่ถึงขีดจำกัดจริงมีเพียงหกครั้ง
grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'pool www' | grep -c 'seems busy'58grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'max_children' | cut -c2-2109-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เวลาหกจุดที่คำสั่งที่สองแสดงออกมาจะเป็นหลักในการไล่หาสาเหตุ และขั้นต่อไปจะใช้เฉพาะเวลาเหล่านี้
2️⃣ ทำไมการเพิ่ม max_children ไม่ใช่คำตอบ
ถ้าเพิ่มขีดจำกัด 26 จำนวนคำเตือนอาจลดลง แต่คำขอที่ทำให้เวิร์กเกอร์เต็มก็ยังเป็นคำขอเดิม
การคำนวณทั่วไปคือนำหน่วยความจำที่ใช้ได้มาหารด้วยหน่วยความจำเฉลี่ยของเวิร์กเกอร์หนึ่งตัว บนเซิร์ฟเวอร์ที่วัดจริง เวิร์กเกอร์ 26 ตัวใช้หน่วยความจำเฉลี่ย 131MB สูงสุด 214MB และถ้าคำนวณจากหน่วยความจำที่ยังใช้ได้ ในเชิงตัวเลขก็ยังมีที่ว่างพอจะสร้างเวิร์กเกอร์เพิ่ม
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.05free -mค่าสี่ค่าในผลลัพธ์แรกคือจำนวนเวิร์กเกอร์ หน่วยความจำเฉลี่ย (MB) หน่วยความจำสูงสุด (MB) และผลรวม (MB) ส่วนคอลัมน์ available ในผลลัพธ์ของ free คือหน่วยความจำที่ใช้ได้
ถ้ามีเวิร์กเกอร์มากกว่าจำนวนคอร์ CPU แล้ว การเพิ่มเวิร์กเกอร์จะไม่ทำให้คำขอที่ประมวลผลพร้อมกันเพิ่มขึ้น มีแต่เวลาที่ใช้ต่อคำขอที่ยาวขึ้น ในสภาพที่ผู้เข้าชมได้รับข้อผิดพลาด 502·504 เป็น 0 ครั้ง การเพิ่มขีดจำกัดจึงไม่ได้อะไร และเป็นการแบ่งโปรเซสให้บอตและสแกนเนอร์ที่ยึดเวิร์กเกอร์อยู่มากขึ้นเท่านั้น
ก่อนเพิ่มขีดจำกัด สิ่งที่ต้องตรวจคือคำขอที่ยึดเวิร์กเกอร์ในเวลาที่มีการบันทึกคำเตือนนั้นมาจากผู้เข้าชมหรือไม่ ขั้นตอนการตรวจด้วย access.log สรุปไว้ในหัวข้อถัดไป
3️⃣ ไล่หาสาเหตุใน access.log: เวลา IP พาธ และรหัสตอบกลับ ตามลำดับ
เลือกเวลาที่มีคำเตือนมาหนึ่งจุด คำตอบจะอยู่ใน access.log ช่วง 1–2 นาทีนั้น ลำดับคือดึงบรรทัดตามเวลา นับแยกตาม IP แล้วดูพาธและรหัสตอบกลับของ IP อันดับต้นๆ
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.xgrep '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กรณีวันที่ 9 ตุลาคม เวลา 18:09 น. มีการบันทึก 286 ครั้งในหนึ่งนาที ในจำนวนนี้ IP เดียวมี 115 ครั้ง พาธเป็นที่อยู่หน้าผู้ดูแลอย่าง /wp-admin/profile.php และรหัสตอบกลับเป็น 302 ทั้งหมด
รหัสตอบกลับบอกว่าคำขอไปถึง PHP หรือไม่ 200 ที่ nginx ตอบด้วย HTML ที่แคชไว้โดยตรง รวมถึง 444·400 ไม่ผ่าน PHP ส่วน 302·404 และ 200 ที่ไม่ได้แคชจะใช้เวิร์กเกอร์หนึ่งตัว เมื่อขอที่อยู่หน้าผู้ดูแลโดยไม่ได้ล็อกอิน WordPress จะตอบ 302 ไปยังหน้าล็อกอิน และ 302 หนึ่งครั้งนั้นก็คือการรัน PHP หนึ่งครั้ง
ถ้าเซิร์ฟเวอร์เดียวมีเว็บไซต์ตั้งแต่สองเว็บขึ้นไป บรรทัดของทั้งสองเว็บจะถูกบันทึกรวมกันใน access.log ส่วนกลาง ตอนแรกดูเหมือนบอตนี้โจมตีเว็บไซต์แรก แต่เมื่อเทียบกับล็อกของแต่ละเว็บไซต์ พบว่า 22,390 จาก 22,391 ครั้งเป็นคำขอของเว็บไซต์ที่สอง
4️⃣ คำขอสามแบบที่ทำให้เวิร์กเกอร์เต็ม: wp-admin ของบอต, 404 ของสแกนเนอร์ และการพรีโหลดแคช
เมื่อใช้ลำดับเดียวกันกับทุกช่วงเวลาที่มีการบันทึกคำเตือน พบว่าคำขอที่ทำให้เวิร์กเกอร์เต็มมีสามแบบ
แบบแรกคือบอตที่ขอที่อยู่หน้าผู้ดูแลซ้ำๆ IP เดียวส่งคำขอ 22,391 ครั้งในสามชั่วโมง ในจำนวนนี้ 9,944 ครั้งเป็นพาธสี่รายการรวมถึง /wp-admin/profile.php และเพราะ 302 แบบไม่ได้ล็อกอินหนึ่งครั้งคือการรัน PHP หนึ่งครั้ง จึงใช้เวิร์กเกอร์ไป 9,944 ครั้ง
แบบที่สองคือสแกนเนอร์หาช่องโหว่ที่ขอพาธที่ไม่มีอยู่จริง แม้ใน User-Agent จะเขียนว่า Googlebot ก็ไม่ใช่ Googlebot จริง IP ที่ส่งคำขอวันละ 14,672 ครั้งเป็นเซิร์ฟเวอร์เสมือนของ Google Cloud ที่ DNS ย้อนกลับลงท้ายด้วย googleusercontent.com ขณะที่ Googlebot จะลงท้ายด้วย googlebot.com และ IP เดียวกันนี้ยังสลับใช้ bingbot, Applebot, GPTBot และ ClaudeBot
คำขอไฟล์อย่าง .env จะถูก nginx บล็อกด้วย 444 ใน 0.04 วินาที แต่พาธธรรมดาที่ไม่อยู่ในกฎการบล็อก เช่น /userfiles หรือ /secrets.yml จะทำให้ WordPress สร้างหน้า 404 หน้า 404 นี้มีขนาด 266KB ใช้เวลา 1.4–2.9 วินาที เท่ากับการสร้างโพสต์ปกติ และ IP เดียวขอถึงวันละ 4,069 ครั้ง
แบบที่สามคือตัวเซิร์ฟเวอร์เอง เมื่อบันทึกโพสต์หรือคำแปล แคชจะถูกล้างและการพรีโหลดของ WP Rocket จะขอทุก URL ใหม่ วันละ 8,489 ครั้ง สูงสุดนาทีละ 60 ครั้ง การตรวจสถานะการจัดทำดัชนีของ Rank Math ก็ใช้เวิร์กเกอร์หนึ่งตัววันละ 754 ครั้งผ่าน Action Scheduler ทั้งสองอย่างเกิดเฉพาะช่วงเวลาทำงานและไม่ทำให้ผู้เข้าชมเจอข้อผิดพลาด จึงไม่ได้นำมาแก้ไข
ในตัวจำลองด้านล่าง ดูได้ว่าคำขอแต่ละแบบจบที่ชั้นไหนและใช้เวิร์กเกอร์ PHP หรือไม่
| คำขอ | ชั้นที่หยุด | การตอบกลับ | Worker |
|---|---|---|---|
| wp-admin ของบอต | WordPress | 302 (ไปหน้าล็อกอิน) | ใช้ 1 ตัว |
| สแกนเนอร์ไฟล์แบบ .env | กฎการบล็อกของ nginx | 444 (0.04 วินาที) | ไม่ได้ใช้ |
| สแกนเนอร์พาธปลอมธรรมดา | WordPress | หน้า 404 (266KB · 1.4–2.9 วินาที) | ใช้ 1 ตัว |
| การพรีโหลดแคช | WordPress | 200 · บันทึกเป็นไฟล์แคช | ใช้ 1 ตัว |
| เข้าชมโพสต์ที่แคชไว้ | ไฟล์แคช | 200 (nginx ตอบกลับทันที) | ไม่ได้ใช้ |
| เข้าชมโพสต์ที่ไม่ได้แคช | WordPress | 200 · บันทึกเป็นไฟล์แคช | ใช้ 1 ตัว |
5️⃣ การจำกัดอัตราของ nginx: จำกัดคำขอที่ไม่ได้ล็อกอินและกู้คืน IP จริง
เลือกใช้การจำกัดอัตราแทนการบล็อก เพราะถ้าบล็อกพาธ สแกนเนอร์ก็จะเปลี่ยนพาธ ถ้าบล็อก IP วันรุ่งขึ้นก็จะมี IP อื่นเข้ามา จึงจำกัดเฉพาะคำขอที่ไม่ได้ล็อกอินและไปถึง PHP ไว้ที่นาทีละ 30 ครั้ง และพร้อมกัน 60 ครั้งต่อ IP ส่วนที่เกินจะตอบกลับด้วย 429
สำหรับเซิร์ฟเวอร์ที่อยู่หลัง CDN การตั้งค่านี้อย่างเดียวยังไม่พอ เพราะ IP ที่ nginx เห็นคือที่อยู่ edge ของ CDN ผู้เข้าชมทั้งหมดที่ผ่าน edge นั้นจึงถูกรวมเป็น IP เดียว ต้องลงทะเบียนรายการ IP ของ edge ด้วย set_real_ip_from เพื่อกู้ IP จริงจาก X-Forwarded-For ก่อน คำขอที่มีคุกกี้ล็อกอินจะได้รับการยกเว้น แต่ไม่ยกเว้นตาม User-Agent
# บริบท http
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 203.0.113.0/24; # รายการ 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 ที่ส่งต่อไปยัง PHP
location @apache {
limit_req zone=php_anon burst=60 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
}หน้าที่แคชไว้จะถูก try_files ตอบกลับภายใน nginx จึงไม่มาถึง location นี้ และ admin-ajax.php อยู่ใน location แยก จึงไม่ถูกนับรวมในการจำกัด
ทดสอบด้วยคำขอพร้อมกัน คำขอแบบทีละรายการใช้เวลา 404 ละ 1.5 วินาที จึงไม่เกินนาทีละ 30 ครั้งและไม่ติดการจำกัด แต่ถ้าส่งพร้อมกันทีละ 20 รวม 100 ครั้ง จะได้ 404 จำนวน 67 ครั้ง และ 429 จำนวน 33 ครั้ง ซึ่งยืนยันว่าการจำกัดทำงาน
seq 1 100 | xargs -P 20 -I{} curl -s -o /dev/null -w '%{http_code}\n' https://example.com/zz-{} | sort | uniq -cถ้าผลลัพธ์นับ 429 ได้ แปลว่าการจำกัดทำงาน หากยกเว้น IP ของเซิร์ฟเวอร์เองไว้ ให้ยกเลิกการยกเว้นชั่วคราวแล้วรัน หรือรันจากเครือข่ายอื่น
6️⃣ เปรียบเทียบก่อนและหลังใช้งาน
การจำกัดที่อยู่หน้าผู้ดูแลแบบไม่ได้ล็อกอินเริ่มใช้เย็นวันที่ 9 ตุลาคม และการจำกัดคำขอที่ไปถึง PHP เริ่มใช้เย็นวันที่ 11 จำนวน 302 ของที่อยู่หน้าผู้ดูแลลดจาก 9,996 ครั้งในวันที่ 9 เหลือ 11 ครั้งในวันที่ 10 และ 1 ครั้งในวันที่ 11 ส่วน IP ที่ส่งคำขอ 22,391 ครั้งในสามชั่วโมง ไม่มีคำขอเลยตั้งแต่วันถัดมา
ฝั่งสแกนเนอร์เพิ่งเริ่มใช้การจำกัด จึงยังไม่มีตัวเลขครบหนึ่งวัน จะตัดสินจากว่าจำนวนหน้า 404 ที่ WordPress สร้าง (วันที่ 9 จำนวน 2,563 วันที่ 10 จำนวน 2,612 วันที่ 11 จำนวน 1,630) ลดลงได้แค่ไหนในการตรวจสัปดาห์หน้า ส่วนข้อผิดพลาด 502·504 ของผู้เข้าชมเป็น 0 ครั้งทั้งก่อนและหลังใช้งาน
ตัวเลขเหล่านี้นับจากล็อกของเซิร์ฟเวอร์ WordPress ที่ทำงานด้วย Nginx, Apache และ php-fpm โดยตรง และตัดสินจากข้อผิดพลาดของผู้เข้าชมและจำนวนคำขอที่ไปถึง PHP ไม่ใช่จากจำนวนคำเตือน
🔢 FAQ & เนื้อหาแนะนำ
แคช
แคชเว็บไซต์ & แคชภายในเซิร์ฟเวอร์
อธิบายทีละชั้นว่าทำไมคำขอที่แคชไว้จึงไม่ผ่าน PHP
ความปลอดภัย
ช่องโหว่ด้านความปลอดภัยของ wp-config.php
วิธีป้องกันไฟล์การตั้งค่าที่สแกนเนอร์เล็งไว้ด้วยสิทธิ์ไฟล์และกฎของเซิร์ฟเวอร์
Linux
คำสั่ง Linux: 15 หมวดหมู่
ตารางที่แบ่งคำสั่งออกเป็น 15 หมวด รวมถึง grep และ awk ที่ใช้นับล็อก
ℹ️ ประกาศเรื่องลิงก์แอฟฟิลิเอต
เนื้อหาของเว็บไซต์นี้มีลิงก์แอฟฟิลิเอต หากผู้เข้าชมซื้อสินค้าหรือบริการผ่านลิงก์เหล่านี้ เว็บไซต์นี้จะได้รับค่าคอมมิชชันจากผู้ขาย ในกระบวนการนี้ ราคาที่ผู้ซื้อจ่าย(ราคาจะลดลงเมื่อมีโปรโมชัน↓)จะไม่สูงขึ้นแต่อย่างใด ราคา ส่วนลด และข้อมูลสต็อกที่แสดงเป็นข้อมูล ณ เวลาที่เขียนและอาจต่างจากความเป็นจริง โปรดตรวจสอบกับผู้ขายอีกครั้งก่อนซื้อ การเลือกและการประเมินสินค้าเขียนตามเกณฑ์ของเราเอง และการได้รับค่าคอมมิชชันหรือไม่ไม่มีผลต่อลำดับการแนะนำหรือเนื้อหาการประเมิน