PlusMagi's Blog By Pitt Phunsanit

nginx: ทำไมโดเมนยาว ๆ ถึงทำเซิร์ฟเวอร์พัง?

หากคุณเป็น System Analyst หรือ Developer ที่ต้องจัดการเว็บเซิร์ฟเวอร์ Nginx และชอบใช้เทคนิคความปลอดภัยประเภท Security through Obscurity เช่นการสุ่มชื่อ Subdomain ยาว ๆ เพื่อซ่อนแอปพลิเคชันหลังบ้าน (เช่น phpMyAdmin หรือคิวรี Extended Events ของฐานข้อมูล) วันดีคืนดีคุณอาจจะเจอ Error สยองโลกตอนสั่ง nginx -t ว่า:

[emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 64

nginx: configuration file /etc/nginx/nginx.conf test failed

หากลองคำนวณดู โดเมนสุ่มแบบ Hex สายยาวของคุณมีความยาว 61 ตัวอักษร ในขณะที่ค่าเริ่มต้นของระบบบอกว่ามีพื้นที่ 64 ไบต์ ในทางคณิตศาสตร์ $61 < 64$ มันควรจะใส่ได้พอดีและไม่เกิด Error ใช่ไหมครับ? แต่ทำไม Nginx ถึงสั่งระบบพังทันที? บทความนี้มีคำตอบเชิงลึกในระดับโครงสร้างข้อมูลมาเล่าให้ฟังครับ


Default ของ Nginx (ค่าเริ่มต้นมาจากไหน?)

ก่อนอื่นต้องเข้าใจก่อนว่า Nginx เป็นเว็บเซิร์ฟเวอร์ที่ขึ้นชื่อเรื่อง ความเร็วและการประหยัดหน่วยความจำ เวลาที่มี Request วิ่งเข้ามา Nginx จะต้องหาให้เจอเร็วที่สุดว่า Request นี้ต้องการคุยกับ server_name ตัวไหนในคอนฟิก

เพื่อความรวดเร็ว Nginx จะไม่ใช้วิธีไล่อ่านคอนฟิกทีละบรรทัดแบบเรียงลำดับ (Linear Search) แต่จะนำชื่อโดเมนทั้งหมดในระบบมาสร้างเป็น ตารางแฮช (Hash Table) เก็บไว้ในหน่วยความจำ CPU Cache


เจาะลึก 3 กลไกภายในของ Nginx (ทำไมยาว 64 พอดีถึงใส่ไม่ได้?)

สาเหตุที่ชื่อโดเมนยาว 64 ไบต์ ไม่สามารถลงกล่องหน่วยความจำขนาด 64 ไบต์ได้ เกิดจากกลไกการจัดการหน่วยความจำแฝงในภาษา C และสถาปัตยกรรมของ Nginx ดังนี้


🛠️ กลไกที่ 1: พื้นที่สำหรับอักขระปิดท้าย (Null Terminator)

Nginx ถูกพัฒนาขึ้นด้วยภาษา C ซึ่งโครงสร้างของข้อมูลประเภทข้อความสตริง (String) ทุกชุด จะต้องปิดท้ายด้วยอักขระพิเศษ \0 (Null Terminator) เสมอ เพื่อให้ระบบคอมไพเลอร์รู้ว่าข้อความสิ้นสุดตรงไหน


🛠️ กลไกที่ 2: ค่าธรรมเนียมแฝงในล็อกเกอร์ (Hash Bucket Overhead)

ในตารางแฮชของ Nginx ข้อมูลที่อยู่ในกล่อง (Bucket) ไม่ได้มีแค่ตัวอักษรของชื่อโดเมนโดด ๆ แต่ภายในแต่ละกล่องจะต้องแบ่งพื้นที่ (Overhead) ไปเก็บโครงสร้างข้อมูลสำคัญเพื่อใช้ในระบบควบคุมด้วย ได้แก่

เมื่อหักค่าธรรมเนียมแฝงของระบบออกไปประมาณ 12 – 16 ไบต์ ทำให้กล่องขนาด 64 ไบต์ เหลือพื้นที่ให้วางชื่อโดเมนจริง ๆ ไม่ถึง 50 ไบต์ ด้วยซ้ำครับ โดเมนยาว 61 ไบต์จึงชนเพดานทันที


🛠️ กลไกที่ 3: การจัดการข้อมูลชนกัน (Hash Collision Mitigation)

ตารางแฮชของ Nginx ใช้กลไกการกระจายข้อมูล ถ้าเกิดกรณีที่มีชื่อโดเมนอื่นในเซิร์ฟเวอร์มีค่าแฮชคำนวณออกมาแล้วตกอยู่กล่องเดียวกัน (Collision) Nginx จะใช้วิธีต่อท่อนข้อมูลเชื่อมกันในกล่องนั้นเลย Nginx จึงตั้งเงื่อนไขความปลอดภัยไว้ว่า หากความยาวข้อมูลรวมค่าแฮชแฝง มีขนาดเข้าใกล้หรือเกือบเต็มขนาดกล่อง ระบบจะสั่งบล็อกและฟ้อง Error ทันที เพื่อป้องกันไม่ให้เกิดอาการหน่วยความจำล้น (Buffer Overflow) ในระดับ CPU Cache


📊 ตารางเปรียบเทียบ: แต่ละขนาด Bucket รองรับโดเมนได้ยาวเท่าไหร่?

จากสูตรคำนวณพื้นที่จัดเก็บที่แท้จริงของ Nginx

$$\text{Max Domain Length} \approx \text{Bucket Size} - \text{System Overhead (ประมาณ 16-24 bytes)}$$

นี่คือระยะความยาวของชื่อโดเมนที่ปลอดภัยที่สุด (รวมซับโดเมนและนามสกุลท้ายแล้ว) ในแต่ละขนาดของ server_names_hash_bucket_size บนสถาปัตยกรรมเซิร์ฟเวอร์ 64-bit

Bucket Sizeความยาวชื่อโดเมนที่รองรับได้จริง (โดยไม่ Error)เหมาะสำหรับ
32 ไบต์ไม่เกิน 8 – 16 ตัวอักษรโดเมนสั้นมากระดับจดทะเบียนพิเศษ (มักมี Error ตั้งแต่สตาร์ท)
64 ไบต์ (Default)ไม่เกิน 40 – 48 ตัวอักษรโดเมนมาตรฐานทั่วไป เช่น [www.plusmagi.com](https://www.plusmagi.com)
128 ไบต์ไม่เกิน 104 – 112 ตัวอักษรโดเมนสุ่มสายยาวความปลอดภัยสูง (Hex/สุ่มสตริง)
256 ไบต์ไม่เกิน 232 – 240 ตัวอักษรระบบที่มี Subdomain ซ้อนกันหลายชั้น หรือใช้งานแบบ Dynamic

แนวทางการแก้ไขและผลกระทบต่อประสิทธิภาพ

การแก้ไขทำได้ง่ายมาก เพียงแค่ไปขยายขนาด “กล่องล็อกเกอร์” ในคอนฟิกหลักของ Nginx ให้กว้างขึ้น โดยขยับจากค่าเริ่มต้น 64 ไปเป็น 128

เปิดไฟล์คอนฟิกหลัก

sudo nano /etc/nginx/nginx.conf

แทรกหรือเปิดใช้งานคำสั่งนี้เข้าไปภายใต้บล็อก http { ... }

http {
    # Increase bucket size to support long/randomized subdomains
    server_names_hash_bucket_size 128; 
    
    # (Optional) Increase max size if managing thousands of subdomains
    # server_names_hash_max_size 1024;
}

🏎️ การขยายค่านี้ส่งผลต่อประสิทธิภาพ (Performance) ไหม?

การเพิ่ม server_names_hash_bucket_size จาก 64 เป็น 128 แทบไม่มีผลกระทบในเชิงลบต่อประสิทธิภาพของเซิร์ฟเวอร์เลย ในทางกลับกัน มันช่วยให้ Nginx สามารถค้นหาและจับคู่ชื่อโดเมนที่ยาวและสลับซับซ้อนได้แม่นยำขึ้น โดยแลกกับหน่วยความจำหน่วยประมวลผลเพิ่มขึ้นเพียงไม่กี่กิโลไบต์ (KB) ซึ่งคุ้มค่ามากเมื่อเทียบกับระดับความปลอดภัยขั้นสูงสุดที่ระบบได้รับจากการพรางโดเมนลับครับ


อ่านเพิ่มเติม

Exit mobile version