SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor

iptables กับ nftables บน Ubuntu ต่างกันอย่างไร

บน Ubuntu รุ่นใหม่คำสั่ง iptables จะเขียนกฎลงใน nftables โดยตรง ตรวจสอบการตั้งค่าบนเซิร์ฟเวอร์ของคุณ ดูวิธีอ่านกฎเนทีฟ และทำความเข้าใจปัญหาความขัดแย้งระหว่าง ufw กับ Docker

iptables กับ nftables บน Ubuntu: เซิร์ฟเวอร์ของคุณใช้งานตัวไหนอยู่?

บน Ubuntu 20.04 เป็นต้นไป คำสั่ง iptables ทำหน้าที่เป็นส่วนติดต่อผู้ใช้ (front end) ที่เขียนกฎลงใน nftables โดยมีตัวกรองแพ็กเก็ตที่ทำงานในระดับเคอร์เนลคือ nftables และมีคำสั่งในระดับ user space สองตัวที่คอยจัดการกฎเหล่านั้น บรรทัดคำสั่ง iptables -A INPUT ยังคงทำงานได้เหมือนเดิมทุกประการ และกฎที่สร้างขึ้นจะเป็นกฎของ nftables ซึ่ง nft สามารถแสดงผลออกมาได้

โปรดตรวจสอบข้อมูลนี้บนเซิร์ฟเวอร์ของคุณเองก่อนที่จะสรุปผล

iptables -V
sudo update-alternatives --display iptables
sudo nft list ruleset

บน Ubuntu 24.04 (iptables 1.8.10 ณ เดือนสิงหาคม 2026) คำสั่ง iptables -V จะแสดงผลเป็น iptables v1.8.10 (nf_tables) โดยชื่อที่อยู่ในวงเล็บคือ back end ของระบบ (nf_tables) หมายความว่าคำสั่งนี้กำลังสื่อสารกับ nftables ส่วน (legacy) หมายถึง back end แบบเก่าคือ x_tables ซึ่ง Ubuntu ยังคงมีให้ใช้งานในชื่อ iptables-legacy และเคอร์เนลยังคงแยกชุดกฎของมันไว้อย่างชัดเจน คำสั่ง update-alternatives จะแสดง symlink ที่อยู่เบื้องหลังการเลือกนี้คือ link currently points to /usr/sbin/iptables-nft

บน VPS ที่ติดตั้งใหม่และยังไม่ได้ตั้งค่าไฟร์วอลล์ คำสั่ง sudo nft list ruleset จะไม่แสดงผลลัพธ์ใดๆ ซึ่งผลลัพธ์ที่ว่างเปล่านี้คือค่าเริ่มต้นของคุณ ให้ลองเพิ่มกฎหนึ่งรายการด้วยวิธีแบบเก่าแล้วตรวจสอบอีกครั้ง

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo nft list ruleset
# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
  chain INPUT {
    type filter hook input priority filter; policy accept;
    tcp dport 22 counter packets 0 bytes 0 accept
  }
  chain FORWARD {
    type filter hook forward priority filter; policy accept;
  }
  chain OUTPUT {
    type filter hook output priority filter; policy accept;
  }
}

กฎ iptables ของคุณกลายเป็นกฎของ nftables ไปแล้ว iptables-nft จะทำเครื่องหมายที่ตารางซึ่งถูกสร้างขึ้น และ nft จะแสดงคำเตือนนั้นเมื่อพบเครื่องหมายดังกล่าว เนื่องจากการแก้ไขตารางด้วย nft จะทำให้เครื่องมือสองตัวเข้ามาจัดการกฎชุดเดียวกัน ให้สังเกตสิ่งที่คำสั่งหนึ่งสร้างขึ้นมา นั่นคือตารางที่คุณไม่ได้ตั้งชื่อ และ chain ที่คุณไม่ได้ร้องขอ นี่คือรูปแบบการทำงานแบบเก่า และเป็นสิ่งแรกที่จะเปลี่ยนไปเมื่อคุณเขียนกฎ nftables โดยตรง

สิ่งที่ iptables -L ซ่อนไว้จากคุณ

iptables -L จะแสดงเฉพาะตาราง filter เท่านั้น กฎ NAT (network address translation) จำเป็นต้องใช้ iptables -t nat -L และกฎ mangle จำเป็นต้องใช้ -t mangle สำหรับ IPv6 จะอยู่ในคำสั่งแยกต่างหากคือ ip6tables ซึ่งมีกฎทุกอย่างแยกเป็นของตัวเอง ดังนั้นเซิร์ฟเวอร์อาจดูเหมือนไม่มีกฎอะไรในรายการหนึ่ง ในขณะที่แพ็กเก็ตของคุณอาจถูกทิ้งหรือถูกเขียนทับจากตารางที่คุณไม่ได้ตรวจสอบ

sudo nft list ruleset จะแสดงทุกตระกูล ทุกตาราง ทุกเชน และทุกกฎออกมาในผลลัพธ์เดียว บนเซิร์ฟเวอร์ที่คุณไม่ได้เป็นคนติดตั้งเอง คำสั่งนี้เป็นวิธีที่เร็วที่สุดในการดูว่ามีอะไรโหลดอยู่จริง ให้เพิ่ม -a เพื่อแสดงหมายเลข handle ของกฎ ซึ่งคุณจำเป็นต้องใช้หากต้องการลบกฎเพียงข้อเดียวแทนที่จะลบทั้งเชน

มีสองนิสัยที่ควรปรับปรุงในขณะที่คุณอยู่ที่นี่ iptables -L จะพยายามแปลงที่อยู่และพอร์ตให้เป็นชื่อ ดังนั้นบนเครื่องที่ระบบ resolver มีปัญหา คำสั่งอาจดูเหมือนค้าง ให้ใช้ iptables -nvL แทน และควรตรวจสอบให้แน่ใจว่า back end แบบเก่าว่างเปล่าด้วย sudo iptables-legacy -nvL เพราะหากมีกฎอยู่ในทั้งสอง back end เคอร์เนลจะประเมินกฎจากทั้งสองที่ และไม่มีรายการใดที่แสดงภาพรวมทั้งหมดให้คุณเห็น

ตารางและเชนที่คุณสร้างขึ้นเอง ไม่ใช่สิ่งที่สืบทอดมา

nftables เริ่มต้นด้วยความว่างเปล่า จะไม่มีตาราง filter จนกว่าคุณจะสร้างขึ้นมา และคำว่า filter เป็นเพียงชื่อที่คุณเลือกใช้เท่านั้น เชนจะมองเห็นแพ็กเก็ตก็ต่อเมื่อคุณกำหนดประเภท (type), ฮุก (hook) และลำดับความสำคัญ (priority) ให้กับมัน ซึ่งจะทำให้มันกลายเป็น base chain ส่วนเชนที่ไม่มีคุณสมบัติเหล่านี้จะถูกเข้าถึงได้ก็ต่อเมื่อมีการใช้คำสั่ง jump หรือ goto อย่างชัดเจนเท่านั้น ดังนั้นมันจึงไม่กินทรัพยากรจนกว่าจะมีสิ่งใดกระโดด (jump) ไปหามัน

การเปลี่ยนแปลงใหญ่อีกประการหนึ่งคือตระกูล inet โดยตาราง inet หนึ่งตารางสามารถจัดการทั้ง IPv4 และ IPv6 ได้ในกฎชุดเดียวกัน ซึ่งช่วยขจัดข้อผิดพลาดประเภทที่พอร์ตถูกปิดใน iptables แต่กลับเปิดกว้างใน ip6tables ความไม่สอดคล้องกันนี้พบได้บ่อยจนมี รูปแบบความล้มเหลวเฉพาะตัวบนเครื่องที่ใช้ ufw

นี่คือชุดกฎสำหรับเซิร์ฟเวอร์ที่สมบูรณ์ โดยให้ใส่ไว้ใน /etc/nftables.conf

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  set admin_ips {
    type ipv4_addr
    flags interval
    elements = { 203.0.113.5, 198.51.100.0/24 }
  }

  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    ip protocol icmp accept
    meta l4proto ipv6-icmp accept
    tcp dport 22 ip saddr @admin_ips counter accept
    tcp dport { 80, 443 } counter accept
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }
}

โปรดอ่านบรรทัดที่ 2 ซ้ำอีกครั้ง flush ruleset จะลบทุกตารางบนเครื่องทิ้ง รวมถึงตารางที่ ufw และ Docker สร้างขึ้นมาเอง โปรดอ่านให้จบก่อนที่จะรันคำสั่งนี้บนเซิร์ฟเวอร์ที่ใช้งานจริง

กฎข้อแรกในเชน input ทำหน้าที่ส่วนใหญ่ ct state established,related accept จะอนุญาตให้การตอบกลับของการเชื่อมต่อที่คุณเป็นผู้เริ่มสามารถผ่านเข้ามาได้ ดังนั้นกฎที่เหลือในเชนจึงมีหน้าที่ตัดสินใจเฉพาะการเชื่อมต่อใหม่เท่านั้น ct state invalid drop จะทิ้งแพ็กเก็ตที่ไม่ตรงกับการเชื่อมต่อที่รู้จักและไม่ใช่การเริ่มต้นที่ถูกต้อง ทุกอย่างหลังจากนั้นคือช่องโหว่ที่กำหนดไว้อย่างชัดเจน และ policy drop จะจัดการส่วนที่เหลือ

ตรวจสอบไฟล์ก่อนที่คุณจะโหลดมัน และเปิดเซสชัน SSH สำรองไว้อีกหนึ่งเซสชันในขณะที่ดำเนินการ policy drop ร่วมกับการพิมพ์ผิดเพียงจุดเดียวในกฎ SSH อาจทำให้คุณถูกล็อกออกจากเซิร์ฟเวอร์ของตัวเองได้

sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo nft list ruleset

nft -c -f จะทำการตรวจสอบไวยากรณ์ของไฟล์และรายงานข้อผิดพลาดโดยที่ยังไม่มีการโหลดค่าใดๆ หากการตรวจสอบผ่านไปได้ด้วยดี จะไม่มีข้อความใดๆ แสดงออกมาเลย

การใช้ Sets เพื่อแทนที่รายการกฎจำนวนมาก

tcp dport { 80, 443 } คือ anonymous set ซึ่งช่วยให้ใช้กฎเพียงข้อเดียวและทำการค้นหาครั้งเดียว แทนที่จะต้องเขียนกฎแยกตามพอร์ต ส่วน named set เช่น admin_ips นั้นมีประสิทธิภาพมากกว่า เนื่องจากคุณสามารถเปลี่ยนแปลงข้อมูลภายในได้ในขณะที่ไฟร์วอลล์กำลังทำงานอยู่

sudo nft add element inet filter admin_ips { 203.0.113.9 }
sudo nft list set inet filter admin_ips
sudo nft delete element inet filter admin_ips { 203.0.113.9 }

คุณไม่จำเป็นต้องโหลดกฎใหม่หรือจัดลำดับเลขกฎใหม่ และการตรวจสอบเงื่อนไขยังคงเป็นการค้นหาครั้งเดียวเสมอ ไม่ว่าใน set จะมีที่อยู่ IP อยู่ 5 รายการหรือ 50,000 รายการก็ตาม flags interval คือแฟล็กที่อนุญาตให้ set เก็บข้อมูลแบบช่วง (ranges) และ CIDR (classless inter-domain routing) prefixes เช่น 198.51.100.0/24 ได้ หากไม่มีแฟล็กนี้ set จะรับได้เฉพาะที่อยู่ IP เดี่ยวเท่านั้น และการโหลด prefix จะล้มเหลว

นอกจากนี้ Sets ยังสามารถกำหนดให้ข้อมูลภายในหมดอายุได้ด้วยตนเอง

set banned {
  type ipv4_addr
  flags timeout
  timeout 1h
}

ด้วยกฎ ip saddr @banned drop ข้อมูลแต่ละรายการจะถูกลบออกโดยอัตโนมัติหลังจากถูกเพิ่มเข้าไปแล้ว 1 ชั่วโมง นี่คือวิธีการที่การทำงานของ nftables ใน fail2ban บน Ubuntu 24.04 ใช้ในการแบนที่อยู่ IP โดยการเพิ่มข้อมูลเข้าไปใน set แทนที่จะเพิ่มกฎใหม่ หากคุณยังไม่คุ้นเคยกับเรื่องพอร์ต ให้เริ่มต้นที่ พอร์ตคืออะไรบน Linux

มีข้อแตกต่างหนึ่งประการที่มักทำให้ผู้ใช้งานสับสนระหว่างการย้ายระบบ คือ nftables จะไม่นับจำนวนแพ็กเก็ตเว้นแต่คุณจะสั่งให้ทำ iptables -nvL จะแสดงตัวนับสำหรับทุกกฎเสมอ แต่ใน nftables จะมีเฉพาะกฎที่มีคีย์เวิร์ด counter เท่านั้นที่จะมีตัวเลขกำกับ ดังนั้นควรใส่ counter ไว้ในกฎใดก็ตามที่คุณคาดว่าจะต้องตรวจสอบในภายหลัง

วิธีที่ hook และ priority กำหนดลำดับการทำงาน

Base chain จะระบุ hook ซึ่งเป็นจุดในเส้นทางของแพ็กเก็ตที่ chain นั้นจะทำงาน prerouting จะทำงานก่อนการตัดสินใจเรื่อง routing input จะทำงานสำหรับแพ็กเก็ตที่ส่งมายังเครื่องนี้ forward จะทำงานสำหรับแพ็กเก็ตที่ถูกส่งผ่านเครื่องนี้ output จะทำงานสำหรับแพ็กเก็ตที่มาจากกระบวนการภายในเครื่อง postrouting จะทำงานเป็นลำดับสุดท้าย ก่อนที่แพ็กเก็ตจะออกจากเครื่อง

Priority จะกำหนดลำดับของ chain ที่อยู่ภายใน hook เดียวกัน โดยเริ่มจากตัวเลขที่น้อยที่สุดก่อน nftables ได้กำหนดชื่อให้กับค่ามาตรฐานต่างๆ ไว้ดังนี้: raw คือ -300, mangle คือ -150, dstnat คือ -100, filter คือ 0, srcnat คือ 100 การเขียน priority filter; มีค่าเท่ากับการเขียน priority 0;

ส่วนที่ตัดสินว่าการใช้เครื่องมือหลายตัวร่วมกันจะทำงานได้หรือไม่นั้นมีดังนี้: ทุก base chain ที่ลงทะเบียนไว้บน hook จะทำงานตามลำดับ priority แพ็กเก็ตที่ถูกยอมรับ (accept) ใน chain ของคุณยังไม่ถือว่าเสร็จสิ้น: accept จะยุติการทำงานเฉพาะใน chain นั้นเท่านั้น และแพ็กเก็ตจะเดินทางต่อไปยัง base chain ถัดไปที่อยู่บน hook เดียวกัน drop เป็นคำสั่งเด็ดขาดในทุกกรณีและจะหยุดแพ็กเก็ตทันที ดังนั้นกฎที่อนุญาต (permissive rule) ในตารางของคุณจึงไม่สามารถยกเลิกการทิ้งแพ็กเก็ต (drop) ในตารางของ ufw ได้ ไม่ว่าตารางใดจะทำงานก่อนก็ตาม และ accept ของคุณก็ไม่ได้ช่วยป้องกันคุณจาก chain ที่ทำงานในลำดับถัดไป

หากมี base chain สองรายการบน hook เดียวกันที่มี priority เท่ากัน ทั้งสองจะทำงานตามลำดับการลงทะเบียน ซึ่งขึ้นอยู่กับว่าบริการใดเริ่มทำงานก่อน ลำดับดังกล่าวอาจเปลี่ยนแปลงได้หลังจากรีบูตเครื่อง หากคุณจำเป็นต้องใช้งานตารางของคุณควบคู่ไปกับ ufw ให้กำหนด priority ที่แตกต่างกัน เพื่อให้ลำดับการทำงานถูกระบุไว้อย่างชัดเจนแทนที่จะปล่อยให้เป็นไปตามจังหวะการเริ่มทำงานของระบบ

เหตุใดจึงไม่มีกฎ reverse NAT ให้เขียน

นี่คือคำถามที่คนส่วนใหญ่มักเข้าใจผิด ดังนั้นนี่คือคำตอบโดยตรง ระบบ connection tracking จะเขียนการแปลกลับ (reverse translation) ให้คุณโดยอัตโนมัติ จึงไม่มีกฎข้อที่สองที่ต้องเพิ่มเข้าไป

ตาราง nat ที่ทำหน้าที่ทั้งสองส่วนของงาน VPS ทั่วไปจะมีลักษณะดังนี้

table inet nat {
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;
    iifname "enp1s0" tcp dport 8080 dnat ip to 10.0.0.5:80
  }

  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.0.0.0/24 oifname "enp1s0" masquerade
  }
}

มีเพียงแพ็กเก็ตแรกของแต่ละการเชื่อมต่อเท่านั้นที่จะถูกประเมินเทียบกับ nat chain เมื่อกฎตรงกัน เคอร์เนลจะจัดเก็บการแปลนั้นไว้ในตาราง connection tracking ควบคู่ไปกับรายการของการเชื่อมต่อนั้น แพ็กเก็ตถัดไปทั้งหมดในทั้งสองทิศทางจะถูกเขียนใหม่ตามรายการที่จัดเก็บไว้ และจะไม่มีการอ่านกฎซ้ำอีก ให้ติดตั้งเครื่องมือ conntrack เพื่อดูรายการที่กำลังทำงานอยู่

sudo apt install -y conntrack
sudo conntrack -L -p tcp
tcp 6 431999 ESTABLISHED src=198.51.100.20 dst=203.0.113.10 sport=54321 dport=8080 src=10.0.0.5 dst=198.51.100.20 sport=80 dport=54321 [ASSURED] mark=0 use=1

ให้อ่านเป็นสองทูเพิล (tuples) สี่ฟิลด์แรกคือการเชื่อมต่อในรูปแบบที่ไคลเอนต์ส่งมา ซึ่งระบุปลายทางไปยัง 203.0.113.10:8080 ที่เป็นที่อยู่สาธารณะของคุณ สี่ฟิลด์หลังคือการตอบกลับที่เคอร์เนลคาดหวัง ซึ่งถูกย้อนกลับและแปลเรียบร้อยแล้ว โดยมาจาก 10.0.0.5:80 ซึ่งเป็น backend จริง ทูเพิลที่สองนั้น คือ กฎย้อนกลับ (reverse rule) ที่เคอร์เนลเขียนขึ้นเมื่อแพ็กเก็ตแรกตรงกับเงื่อนไข

ดังนั้น อย่าเขียนกฎสำหรับทิศทางขาออก เพราะมันจะไม่ตรงกับเงื่อนไขใดๆ เนื่องจากแพ็กเก็ตขาออกเป็นส่วนหนึ่งของการเชื่อมต่อที่สร้างไว้แล้ว (established connection) และจะไม่ผ่าน nat chain อีก หากมันบังเอิญตรงกับกฎ คุณจะทำการแปลแพ็กเก็ตที่เคอร์เนลแก้ไขเรียบร้อยแล้วซ้ำอีกครั้ง

ตำแหน่งที่การเขียนใหม่ต้องเกิดขึ้นนั้นเป็นไปตามกลไกเดียวกัน การแปลปลายทาง (destination translation) ต้องทำงานใน prerouting ก่อนการตัดสินใจเรื่อง routing เพราะ routing ต้องเห็นปลายทางใหม่ มิฉะนั้นแพ็กเก็ตจะถูกส่งไปยังที่ผิด ส่วนทราฟฟิกที่ตัวเครื่องสร้างขึ้นเองจะถูกจัดการใน hook output ด้วยเหตุผลเดียวกัน การแปลต้นทาง (source translation) รวมถึงการเขียนพอร์ตต้นทางใหม่ ต้องทำงานใน postrouting หลังจากที่ routing เลือกอินเทอร์เฟซขาออกแล้ว masquerade จะดึงที่อยู่มาจากอินเทอร์เฟซนั้น ซึ่งอินเทอร์เฟซจะไม่เป็นที่ทราบแน่ชัดจนกว่า routing จะทำงานเสร็จสิ้น

นั่นคือเหตุผลว่าทำไมกฎลักษณะนี้จึงต้องอยู่ที่ปลายทางของเส้นทางเท่านั้นและห้ามอยู่ที่อื่น

ip saddr 10.0.0.0/24 oifname "enp1s0" snat ip to 203.0.113.10:20000-30000

ช่วงพอร์ต (port range) จะเขียนพอร์ตต้นทางใหม่พร้อมกับที่อยู่ต้นทาง ซึ่งเป็นสิ่งที่คุณต้องการเมื่อไคลเอนต์ภายในจำนวนมากใช้ที่อยู่สาธารณะเดียวกันและพอร์ตต้นทางเกิดชนกัน เมื่อการตอบกลับมาถึงโดยระบุพอร์ตในช่วงนั้น conntrack จะจับคู่มันเข้ากับรายการที่เก็บไว้ และพอร์ตต้นทางเดิมจะถูกกู้คืนก่อนที่แพ็กเก็ตจะถูกส่งต่อไป ย้ำอีกครั้งว่าไม่มีกฎข้อที่สอง

ผลลัพธ์ในทางปฏิบัติคือ การเปลี่ยนกฎ NAT จะไม่ส่งผลต่อการเชื่อมต่อที่มีอยู่แล้ว เพราะการแปลของมันถูกจัดเก็บไว้เรียบร้อยแล้ว การเชื่อมต่อเหล่านั้นจะยังคงทำงานตามพฤติกรรมเดิมจนกว่ารายการจะหมดอายุ sudo conntrack -D -p tcp --dport 8080 จะลบรายการที่ตรงกันออก และ sudo conntrack -F จะลบรายการทั้งหมด โปรดใช้ความระมัดระวังกับคำสั่งหลังบนเครื่อง NAT เพราะการแปลที่จัดเก็บไว้เหล่านั้นคือสิ่งที่รักษาการเชื่อมต่อปัจจุบันให้คงอยู่ ดังนั้นการล้างรายการทั้งหมดจะทำให้การเชื่อมต่อทุกรายการที่ผ่านเครื่องนี้ขาดหายไปในทันที

ufw และ Docker ต่างเขียนกฎของตนเอง

ufw เป็นส่วนติดต่อผู้ใช้งานสำหรับ iptables ซึ่งบน Ubuntu จะเป็นส่วนติดต่อสำหรับ nftables ดังนั้นเครื่องที่ใช้ ufw จึงมีตาราง ip filter ที่เต็มไปด้วย chain ชื่อ ufw-before-input, ufw-user-input และอื่นๆ รวมถึงสำเนา ip6 filter ของโครงสร้างเดียวกัน คุณสามารถตรวจสอบได้ด้วย sudo nft list ruleset | grep ufw chain เหล่านี้ถูกสร้างขึ้นจากไฟล์ใน /etc/ufw และ ufw reload จะเขียนทับไฟล์เหล่านั้นใหม่ทั้งหมดตั้งแต่ต้น ซึ่งเป็นเหตุผลว่าทำไมกฎ iptables ที่เขียนด้วยมือและเพิ่มเข้าไปภายหลังจึงหายไปเมื่อมีการโหลดใหม่ พื้นฐาน ufw สำหรับ VPS ได้อธิบายโครงสร้างไฟล์เหล่านั้นไว้

Docker จะตั้งค่าไฟร์วอลล์ด้วยตนเองและไม่ได้ตรวจสอบกับ ufw การเปิดพอร์ตด้วย -p 80:80 จะเขียนกฎ DNAT ลงในตาราง nat และเพิ่มการอนุญาต (accept) ในเส้นทางการส่งต่อ (forward path) ซึ่งทั้งสองอย่างนี้จะทำงานก่อน chain ของผู้ใช้ใน ufw ผลลัพธ์ที่เกิดขึ้นมักทำให้ทุกคนประหลาดใจ: แม้ว่า ufw deny 80 จะถูกโหลดแล้ว แต่คอนเทนเนอร์ก็ยังสามารถเข้าถึงได้จากอินเทอร์เน็ต วิธีแก้ไขอยู่ใน chain DOCKER-USER ที่ Docker เตรียมไว้ให้สำหรับกฎของคุณ และ เหตุใดคอนเทนเนอร์ Docker จึงเพิกเฉยต่อ ufw จะอธิบายขั้นตอนการจัดการเรื่องนี้ คุณสามารถดูสิ่งที่อยู่ในเครื่องของคุณได้ด้วย sudo nft list ruleset | grep -i docker

ตอนนี้ให้ลองอ่านบรรทัด flush ruleset จากการตั้งค่าด้านบนใหม่อีกครั้ง มันจะลบทุกตารางทิ้ง รวมถึงตารางที่เครื่องมือทั้งสองนี้จัดการอยู่ บนโฮสต์ที่รัน Docker พอร์ตที่เปิดไว้จะหยุดทำงานจนกว่า sudo systemctl restart docker จะสร้าง chain ขึ้นมาใหม่ บรรทัดเดียวนี้นับเป็นวิธีที่พบบ่อยที่สุดที่ทำให้ผู้คนทำบริการของตนเองออฟไลน์โดยไม่ตั้งใจในขณะที่กำลังจัดระเบียบไฟร์วอลล์

กฎที่คงอยู่หลังการรีบูต

กฎทั้งสองชุดไม่มีความคงทนในตัวเอง เคอร์เนลจะล้างข้อมูลทั้งหมดเมื่อปิดเครื่อง แต่ละฝั่งจึงแก้ปัญหานี้ด้วยแพ็กเกจแยกต่างหาก

สำหรับ nftables ไฟล์ /etc/nftables.conf จะถูกอ่านโดย nftables.service ทาง Ubuntu ปิดการทำงานของเซอร์วิสนี้ไว้โดยค่าเริ่มต้น ดังนั้นควรตรวจสอบให้แน่ใจก่อนที่จะเชื่อถือการทำงานของมัน

systemctl is-enabled nftables
sudo nft -c -f /etc/nftables.conf
sudo systemctl enable --now nftables

สำหรับ iptables แพ็กเกจที่ใช้คือ iptables-persistent ซึ่งจะติดตั้ง netfilter-persistent และบันทึกข้อมูลลงใน /etc/iptables/rules.v4 และ /etc/iptables/rules.v6

sudo apt install -y iptables-persistent
sudo netfilter-persistent save

ห้ามใช้งานทั้งสองอย่างพร้อมกัน ไฟล์สองชุดที่ต่างก็อ้างว่าเป็นตัวจัดการไฟร์วอลล์จะมีความคลาดเคลื่อนกัน และชุดที่โหลดทีหลังจะเป็นฝ่ายชนะ ซึ่งไม่มีใครสามารถคาดเดาผลลัพธ์ได้จากการอ่านไฟล์ใดไฟล์หนึ่งเพียงอย่างเดียว

มีกับดักที่เกี่ยวข้องกับการดัมพ์กฎที่กำลังทำงานอยู่ คำสั่ง sudo nft -s list ruleset > /etc/nftables.conf จะจับภาพทุกอย่างที่โหลดอยู่ในขณะนั้น รวมถึงตารางของ ufw และตารางของ Docker หากคุณกู้คืนข้อมูลนั้นตอนบูต คุณจะได้สำเนาของกฎที่เครื่องมือเหล่านั้นคาดหวังว่าจะสร้างขึ้นเอง และจะได้สำเนาชุดที่สองซ้ำเข้ามาอีกครั้งเมื่อเครื่องมือเหล่านั้นเริ่มทำงาน ให้ดัมพ์เฉพาะตารางของคุณเองเท่านั้นด้วย sudo nft -s list table inet filter โดยแฟล็ก -s จะช่วยละเว้นค่าตัวนับ (counters) ซึ่งไม่ควรอยู่ในไฟล์คอนฟิกูเรชัน

คุณควรเปิดใช้งาน ufw บน VPS ของคุณหรือไม่?

ให้ปล่อย ufw ไว้ตามเดิมเว้นแต่คุณจะต้องการสิ่งที่ ufw ไม่สามารถกำหนดค่าได้ ufw ครอบคลุมงานทั่วไปของ VPS ได้ดีอยู่แล้ว นั่นคือการปฏิเสธการเชื่อมต่อทั้งหมดเป็นค่าเริ่มต้นและเปิดพอร์ตที่จำเป็นเพียงไม่กี่พอร์ต การเปลี่ยนไปใช้ชุดกฎที่เขียนขึ้นเองโดยไม่มีเหตุผลจำเป็น จะทำให้คุณได้ไฟร์วอลล์ที่มีประสิทธิภาพเท่าเดิม แต่เพิ่มภาระในการดูแลรักษาเข้ามาอีกหนึ่งอย่าง

ให้เลือกใช้เครื่องมือแบบ native เมื่อสิ่งที่คุณต้องการอยู่นอกเหนือรูปแบบการทำงานของ ufw เช่น การทำ NAT และ port forwarding, การจัดการชุดข้อมูล (sets) ที่คุณต้องการอัปเดตขณะรันไทม์, กฎเดียวที่ครอบคลุมทั้ง IPv4 และ IPv6 หรือการกำหนดลำดับความสำคัญของ chain ด้วยตนเอง สิ่งเหล่านี้คือเหตุผลที่แท้จริงซึ่ง ufw ไม่สามารถรองรับได้

หากคุณเลือกใช้แบบ native ให้เปลี่ยนมาใช้แบบ native โดยสมบูรณ์ ให้รัน sudo ufw disable และ sudo systemctl disable --now ufw จากนั้นตรวจสอบด้วย sudo nft list ruleset เพื่อยืนยันว่าตารางกฎของ ufw ถูกลบออกไปแล้ว แล้วจึงโหลดไฟล์กฎของคุณเอง เซิร์ฟเวอร์ที่รันทั้ง ufw และตารางกฎที่เขียนขึ้นเองพร้อมกันจะยังคงส่งผ่าน traffic ได้ แต่สถานะนโยบายความปลอดภัยที่แท้จริงจะเป็นผลรวมของชุดกฎทั้งสองชุดที่ประเมินตามลำดับการเริ่มทำงานของบริการ ซึ่งจะทำให้ไม่มีใครสามารถระบุได้ว่าเซิร์ฟเวอร์ของคุณมีพฤติกรรมการกรอง traffic อย่างไรจากการอ่านไฟล์ใดไฟล์หนึ่งเพียงอย่างเดียว

การย้ายชุดกฎ iptables เดิม

iptables-translate จะแปลงกฎหนึ่งรายการและแสดงผลลัพธ์ในรูปแบบของ nftables โดยไม่มีการเปลี่ยนแปลงใดๆ บนเซิร์ฟเวอร์

iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
nft add rule ip filter INPUT tcp dport 22 counter accept

iptables-restore-translate -f /etc/iptables/rules.v4 จะทำงานในลักษณะเดียวกันสำหรับชุดกฎที่บันทึกไว้ทั้งหมด ให้ถือว่าผลลัพธ์ที่ได้เป็นเพียงร่างแรกเท่านั้น การแปลงนี้เป็นกระบวนการทางกลไกที่ทำทีละกฎ คุณจะได้ชื่อตารางและชื่อ chain เดิมกลับมา รวมถึงชุดกฎที่แยกกันสำหรับ IPv4 และ IPv6 โดยที่ไม่มีการใช้ชุดข้อมูล (sets) ซึ่งเป็นจุดเด่นที่ทำให้การย้ายมาใช้ nftables นั้นคุ้มค่า ให้เขียนใหม่เป็นตาราง inet ตารางเดียวด้วยตนเอง จากนั้นตรวจสอบด้วย nft -c -f ก่อนที่จะนำไปใช้งานจริงบนเซิร์ฟเวอร์

ที่อยู่ IP ในตัวอย่างเหล่านี้มาจากช่วงที่ใช้ในเอกสารประกอบคือ 203.0.113.0/24 และ 198.51.100.0/24 ส่วน enp1s0 คือชื่ออินเทอร์เฟซเครือข่าย ให้ใช้ค่าของคุณจาก ip route show default และ ip -br addr แทนการคัดลอกค่าของผม เนื่องจากอิมเมจ Ubuntu ในปัจจุบันแทบไม่มีการเรียกชื่ออินเทอร์เฟซว่า eth0 แล้ว

FAQ

iptables ถูกเลิกใช้งานบน Ubuntu แล้วหรือไม่?

คำสั่งนี้ไม่ได้ถูกยกเลิกและยังคงใช้งานได้บน Ubuntu 24.04 สิ่งที่เปลี่ยนไปคือกลไกเบื้องหลัง: iptables เป็นส่วนติดต่อผู้ใช้ที่เขียนกฎ nftables ผ่านแบ็กเอนด์ iptables-nft ตรวจสอบการตั้งค่าของคุณด้วย iptables -V ซึ่งจะแสดงผลเป็น iptables v1.8.10 (nf_tables) บนเวอร์ชัน 24.04 ส่วนแบ็กเอนด์ x_tables แบบเดิมยังคงมีให้ใช้งานในชื่อ iptables-legacy และเก็บชุดกฎแยกต่างหากโดยสิ้นเชิง ดังนั้นควรเลือกใช้แบ็กเอนด์เพียงอย่างเดียว ไม่ควรใช้ทั้งสองอย่างพร้อมกัน

ฉันจำเป็นต้องสร้างกฎที่สองเพื่อยกเลิก NAT ในขาตอบกลับหรือไม่?

ไม่จำเป็น ระบบติดตามการเชื่อมต่อ (connection tracking) จะจัดเก็บการแปลงข้อมูลไว้เมื่อแพ็กเก็ตแรกของการเชื่อมต่อตรงกับกฎ nat และแพ็กเก็ตถัดไปทั้งหมดในทั้งสองทิศทางจะถูกเขียนใหม่ตามรายการที่จัดเก็บไว้นั้น sudo conntrack -L จะแสดงข้อมูลเป็นสองทูเพิลต่อการเชื่อมต่อ: ทิศทางเริ่มต้น และทิศทางตอบกลับที่ถูกย้อนกลับเรียบร้อยแล้ว กฎที่เขียนขึ้นสำหรับทิศทางตอบกลับจะไม่ช่วยอะไร เพราะแพ็กเก็ตขาตอบกลับจะไม่ผ่าน chain ของ nat

ฉันสามารถรัน ufw และกฎ nftables ของตัวเองพร้อมกันได้หรือไม่?

ทำได้ แต่จะนำไปสู่ปัญหาในภายหลัง เนื่องจาก base chain ทุกตัวที่ hook จะทำงาน ทำให้ policy ที่ใช้งานจริงคือการรวมกันของทั้งสองชุดกฎ โดยเรียงลำดับตามลำดับความสำคัญ และหากลำดับความสำคัญเท่ากัน จะขึ้นอยู่กับว่าบริการใดเริ่มทำงานก่อน การใช้ drop ในชุดใดชุดหนึ่งถือเป็นที่สิ้นสุด และการใช้ accept ในกฎของคุณจะไม่สามารถหยุดอีกฝั่งจากการดรอปแพ็กเก็ตเดียวกันได้ ควรเลือกใช้เครื่องมือเพียงอย่างเดียว หากเลือกใช้ nftables ให้ปิดการใช้งาน ufw ก่อนและตรวจสอบให้แน่ใจว่าตารางของ ufw หายไปจาก sudo nft list ruleset แล้ว

ฉันจะทำให้กฎ nftables คงอยู่หลังการรีบูตบน Ubuntu ได้อย่างไร?

ให้ใส่ชุดกฎไว้ใน /etc/nftables.conf ตรวจสอบความถูกต้องด้วย sudo nft -c -f /etc/nftables.conf จากนั้นรัน sudo systemctl enable --now nftables บริการนี้ไม่ได้ถูกเปิดใช้งานโดยค่าเริ่มต้น ดังนั้นการรัน systemctl is-enabled nftables จึงเป็นสิ่งที่ควรทำหนึ่งครั้ง เมื่อคุณสร้างไฟล์ดังกล่าว ให้ดัมพ์เฉพาะตารางของคุณเองด้วย sudo nft -s list table inet filter เนื่องจากการดัมพ์แบบเต็มด้วย list ruleset จะรวมตารางที่ ufw และ Docker จัดการด้วยตัวเองเข้ามาด้วย