คำตอบสั้น ๆ: เพิ่มรหัสผ่านอีกชั้นให้หน้า Admin ด้วย Basic Auth และจำกัดจำนวนคำขอต่อ IP ด้วย limit_req ของ Nginx เพื่อลดการเดารหัสผ่านและภาระของเซิร์ฟเวอร์
คู่มือนี้อธิบาย Basic Auth ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย
หน้า Admin ของเว็บไซต์ เช่น /wp-login.php ของ WordPress, /admin ของระบบหลังบ้าน หรือเครื่องมืออย่าง phpMyAdmin เป็นเป้าหมายหลักของบอทที่สุ่มเดารหัสผ่านตลอดทั้งวัน แม้รหัสผ่านจะแข็งแรงพอ การโดนยิงคำขอเข้ามาจำนวนมากก็ยังกิน CPU ของ PHP-FPM และฐานข้อมูลจนเว็บไซต์ช้าลงได้
บทความนี้แสดงวิธีเพิ่มการป้องกันสองชั้นที่ระดับ Nginx ก่อนคำขอจะไปถึงแอปพลิเคชัน ชั้นแรกคือ Basic Auth ให้เบราว์เซอร์ถามชื่อผู้ใช้และรหัสผ่านอีกชุดก่อนแสดงหน้า Admin ชั้นที่สองคือ Rate Limit ด้วยโมดูล limit_req เพื่อจำกัดจำนวนคำขอต่อ IP ทั้งสองอย่างมีมากับ Nginx อยู่แล้ว ไม่ต้องติดตั้งโมดูลเพิ่ม
สิ่งที่ต้องเตรียม
- Linux Server ที่ติดตั้ง Nginx แล้ว (Ubuntu 22.04/24.04 หรือ AlmaLinux/Rocky Linux 8-9) หากยังไม่มี ดู ติดตั้ง Nginx, PHP-FPM และ MariaDB (LEMP) บน Ubuntu
- สิทธิ์ root หรือผู้ใช้ที่ใช้
sudoได้ - เว็บไซต์ควรเปิดใช้ HTTPS แล้ว เพราะ Basic Auth ส่งรหัสผ่านแบบเข้ารหัส Base64 เท่านั้น ไม่ได้เข้ารหัสจริง ดู ติดตั้ง SSL ฟรีจาก Let's Encrypt ด้วย Certbot บน Nginx
- ทราบไฟล์ Server Block ของเว็บไซต์ เช่น
/etc/nginx/sites-available/example.com(Ubuntu) หรือ/etc/nginx/conf.d/example.com.conf(AlmaLinux/Rocky)
ขั้นตอนที่ 1: ติดตั้งเครื่องมือ htpasswd
คำสั่ง htpasswd ใช้สร้างไฟล์เก็บชื่อผู้ใช้และรหัสผ่านที่เข้ารหัสแล้ว อยู่คนละแพ็กเกจในแต่ละ Distro
Ubuntu:
sudo apt update
sudo apt install apache2-utils
AlmaLinux / Rocky Linux:
sudo dnf install httpd-tools
แพ็กเกจทั้งสองติดตั้งเฉพาะเครื่องมือ ไม่ได้ติดตั้ง Apache Web Server ลงมาด้วย
ขั้นตอนที่ 2: สร้างไฟล์ผู้ใช้สำหรับ Basic Auth
สร้างผู้ใช้คนแรกด้วยตัวเลือก -c (create) ระบบจะถามรหัสผ่านสองครั้ง
sudo htpasswd -c /etc/nginx/.htpasswd adminuser
เพิ่มผู้ใช้คนถัดไป ห้ามใส่ -c อีก เพราะจะเขียนทับไฟล์เดิมและผู้ใช้เก่าหายทั้งหมด
sudo htpasswd /etc/nginx/.htpasswd seconduser
ลบผู้ใช้ด้วย -D
sudo htpasswd -D /etc/nginx/.htpasswd seconduser
ตั้งสิทธิ์ไฟล์ให้ Nginx อ่านได้แต่ผู้ใช้อื่นอ่านไม่ได้ บน Ubuntu Nginx ทำงานในกลุ่ม www-data ส่วน AlmaLinux/Rocky ใช้กลุ่ม nginx
# Ubuntu
sudo chown root:www-data /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd
# AlmaLinux / Rocky Linux
sudo chown root:nginx /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd
เมื่อเปิดไฟล์ดู จะเห็นบรรทัดลักษณะ adminuser:$apr1$... ซึ่งเป็นรหัสผ่านที่ผ่านการ Hash แล้ว (รูปแบบ apr1 ที่ Nginx รองรับโดยตรง) ไม่มีใครอ่านรหัสผ่านจริงจากไฟล์นี้ได้ แต่ก็ไม่ควรวางไฟล์ไว้ในโฟลเดอร์เว็บที่เปิดดาวน์โหลดได้
ขั้นตอนที่ 3: ใส่ Basic Auth ให้เส้นทาง Admin
แก้ไฟล์ Server Block ของเว็บไซต์ แล้วเพิ่ม auth_basic ไว้ใน location ที่ต้องการป้องกัน ตัวอย่างนี้ป้องกันโฟลเดอร์ /admin/ ของแอปทั่วไป
location /admin/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ /index.php?$args;
}
สำหรับ WordPress ต้องระวังเป็นพิเศษ เพราะไฟล์ /wp-admin/admin-ajax.php ถูกเรียกจากหน้าเว็บฝั่งผู้เข้าชมด้วย (เช่น ฟอร์มหรือปลั๊กอินตะกร้าสินค้า) ถ้าล็อกไว้ ผู้เข้าชมทั่วไปจะเจอกล่องถามรหัสผ่าน วิธีที่ปลอดภัยกว่าคือล็อกเฉพาะ wp-login.php ซึ่งเป็นประตูเข้าสู่ระบบ
location = /wp-login.php {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
สังเกตว่า location = /wp-login.php เป็นการจับคู่แบบตรงตัว Nginx จะไม่ส่งต่อไปยัง location ~ \.php$ อีก จึงต้องใส่คำสั่ง FastCGI ซ้ำไว้ในบล็อกนี้ด้วย ไม่เช่นนั้นเบราว์เซอร์จะดาวน์โหลดไฟล์ PHP แทนการรัน ปรับค่าให้ตรงกับเครื่องของคุณ
| ระบบ | บรรทัด include | fastcgi_pass |
|---|---|---|
| Ubuntu (PHP 8.3) | include snippets/fastcgi-php.conf; | unix:/run/php/php8.3-fpm.sock |
| Ubuntu 22.04 (PHP 8.1) | include snippets/fastcgi-php.conf; | unix:/run/php/php8.1-fpm.sock |
| AlmaLinux/Rocky | include fastcgi_params; และ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | unix:/run/php-fpm/www.sock |
ดูค่าที่ถูกต้องได้จากบล็อก location ~ \.php$ ที่มีอยู่แล้วในไฟล์เดียวกัน แล้วคัดลอกมาใช้
อนุญาต IP ของสำนักงานโดยไม่ต้องใส่รหัสผ่าน
ถ้าผู้ดูแลเข้าจาก IP คงที่ ใช้ satisfy any เพื่อให้ผ่านได้ด้วยเงื่อนไขใดเงื่อนไขหนึ่ง คือมาจาก IP ที่อนุญาต หรือใส่รหัสผ่านถูก
location /admin/ {
satisfy any;
allow 203.0.113.10;
deny all;
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ /index.php?$args;
}
ถ้าต้องการให้ต้องผ่าน ทั้งสอง เงื่อนไข ให้ใช้ satisfy all; (ค่าเริ่มต้น) ซึ่งเข้มงวดกว่าแต่เข้าจากที่อื่นไม่ได้เลย
ขั้นตอนที่ 4: ตั้งค่า Rate Limit
Rate Limit แบ่งเป็นสองส่วน ส่วนแรกคือประกาศ Zone ในบล็อก http ส่วนที่สองคือเรียกใช้ Zone ใน location ทั้ง Ubuntu และ AlmaLinux/Rocky โหลดไฟล์ใน /etc/nginx/conf.d/*.conf ภายในบล็อก http อยู่แล้ว จึงสร้างไฟล์ใหม่ได้เลย
sudo nano /etc/nginx/conf.d/ratelimit.conf
ใส่เนื้อหา
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
limit_req_status 429;
$binary_remote_addrนับตาม IP ของผู้เรียกzone=login:10mตั้งชื่อ Zone ว่า login และใช้หน่วยความจำ 10 MB ซึ่งเก็บสถานะได้ราว 160,000 IPrate=10r/mอนุญาต 10 คำขอต่อนาทีต่อ IP (ใช้r/sสำหรับต่อวินาที)limit_req_status 429ตอบกลับ 429 Too Many Requests แทนค่าเริ่มต้น 503 ซึ่งทำให้อ่าน Log ง่ายกว่า
จากนั้นเพิ่มบรรทัด limit_req ใน location ของหน้า Login
location = /wp-login.php {
limit_req zone=login burst=5 nodelay;
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
burst=5 ยอมให้มีคำขอเกินอัตราได้ชั่วคราว 5 ครั้ง เพื่อรองรับการโหลดหน้าแล้วกดส่งฟอร์มติดกัน ส่วน nodelay ทำให้คำขอที่อยู่ใน burst ถูกประมวลผลทันทีแทนการหน่วงเวลา คำขอที่เกินจากนั้นจะได้ 429 ทันที
หมายเหตุ: ถ้าเซิร์ฟเวอร์อยู่หลัง Cloudflare หรือ Load Balancer ค่า
$binary_remote_addrจะเป็น IP ของ Proxy ทำให้ผู้ใช้ทุกคนถูกนับรวมกันและโดนบล็อกพร้อมกัน ต้องตั้งset_real_ip_fromและreal_ip_headerให้ Nginx รู้ IP จริงของผู้ใช้ก่อน