ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

Home ป้องกันหน้า Admin ด้วย Basic Auth และ Rate Limit บน Nginx

ป้องกันหน้า Admin ด้วย Basic Auth และ Rate Limit บน Nginx

หน้า 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 แทนการรัน ปรับค่าให้ตรงกับเครื่องของคุณ

ระบบบรรทัด includefastcgi_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/Rockyinclude 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 IP
  • rate=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 จริงของผู้ใช้ก่อน

ขั้นตอนที่ 5: ทดสอบไฟล์คอนฟิกและ Reload

sudo nginx -t

ผลลัพธ์ที่ถูกต้องคือ

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

จากนั้นโหลดคอนฟิกใหม่โดยไม่ตัดการเชื่อมต่อเดิม

sudo systemctl reload nginx

ถ้า nginx -t แจ้ง Error ห้าม Reload ให้แก้ตามบรรทัดที่แจ้งก่อน เพราะ Nginx จะยังทำงานด้วยคอนฟิกเดิมต่อไปได้ตามปกติ

ตรวจสอบผลลัพธ์

ทดสอบ Basic Auth ด้วย curl คำขอที่ไม่มีรหัสผ่านต้องได้ 401

curl -I https://example.com/wp-login.php
HTTP/2 401
www-authenticate: Basic realm="Restricted Area"

เมื่อส่งรหัสผ่านถูกต้อง ต้องได้ 200

curl -I -u adminuser https://example.com/wp-login.php

ทดสอบ Rate Limit ด้วยการยิงคำขอติดกัน 20 ครั้ง แล้วดูรหัสสถานะ

for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-login.php; done

ช่วงแรกจะเห็น 401 (เพราะไม่ได้ส่งรหัสผ่าน) จากนั้นจะเปลี่ยนเป็น 429 เมื่อเกินอัตรา ใน Error Log จะมีบรรทัดลักษณะ limiting requests, excess: 5.xxx by zone "login"

sudo tail -f /var/log/nginx/error.log

ปัญหาที่พบบ่อย

ใส่รหัสผ่านถูกแต่ยังได้ 500 Internal Server Error

มักเกิดจาก Nginx อ่านไฟล์ .htpasswd ไม่ได้ ดู Error Log จะพบ open() "/etc/nginx/.htpasswd" failed (13: Permission denied) ให้ตรวจเจ้าของกลุ่มและสิทธิ์ตามขั้นตอนที่ 2 บน AlmaLinux/Rocky หากย้ายไฟล์ไปไว้ที่อื่นนอก /etc/nginx อาจติด SELinux ด้วย ดู ใช้งาน SELinux บน AlmaLinux และ Rocky Linux โดยไม่ต้องปิด

เปิด wp-login.php แล้วเบราว์เซอร์ดาวน์โหลดไฟล์แทน

บล็อก location = ไม่มีคำสั่ง FastCGI ให้คัดลอก include และ fastcgi_pass จากบล็อก PHP เดิมมาใส่ตามขั้นตอนที่ 3

ผู้ใช้ทั่วไปโดน 429 ทั้งที่ไม่ได้ทำอะไรผิด

อาจตั้ง rate ต่ำเกินไป ใช้ Zone เดียวกับหน้าที่ถูกเรียกบ่อย หรือ IP ที่เห็นเป็นของ Proxy ให้ใช้ Rate Limit เฉพาะหน้า Login ไม่ใช่ทั้งเว็บ และตั้ง Real IP ให้ถูกต้อง

Error "zero size shared memory zone" หรือ "unknown limit_req_zone"

เรียกใช้ limit_req zone=login ก่อนประกาศ Zone หรือประกาศ limit_req_zone ผิดที่ (ต้องอยู่ในบล็อก http ไม่ใช่ใน server) ตรวจว่าไฟล์ใน conf.d ถูกโหลดจริงด้วย sudo nginx -T | grep limit_req_zone

Basic Auth และ Rate Limit เป็นชั้นป้องกันเสริม ไม่ได้แทนที่รหัสผ่านที่แข็งแรงและการยืนยันตัวตนสองขั้นตอน สำหรับ WordPress ดูเพิ่มเติมที่ เปิด Two-Factor Authentication สำหรับหน้า Login WordPress และ เช็กลิสต์ความปลอดภัยเว็บไซต์ WordPress หากติดปัญหาในการตั้งค่า ติดต่อทีมงาน THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security