SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-13

วิธีตั้งค่า firewalld บน Rocky Linux และ AlmaLinux

เรียนรู้วิธีเปิดพอร์ต SSH และเว็บเซิร์ฟเวอร์บน Rocky หรือ AlmaLinux ด้วย firewalld พร้อมคำอธิบายเรื่อง Zones และวิธีใช้แฟล็ก --permanent เพื่อให้กฎคงอยู่หลังรีบูตระบบ

firewalld คืออะไร และเหตุใด Rocky และ AlmaLinux จึงติดตั้งมาให้

firewalld คือตัวจัดการไฟร์วอลล์ที่ติดตั้งมาเป็นค่าเริ่มต้นบน Rocky Linux, AlmaLinux และระบบปฏิบัติการอื่นที่สร้างขึ้นจาก Red Hat Enterprise Linux (RHEL) ตัวมันเองไม่ได้ทำหน้าที่ตรวจสอบแพ็กเก็ตโดยตรง แต่จะเก็บการตั้งค่าไว้และแปลงการตั้งค่านั้นให้เป็นกฎของ nftables คำสั่ง firewall-cmd เพียงคำสั่งเดียวสามารถแก้ไขกฎได้ในขณะที่เซิร์ฟเวอร์ยังคงออนไลน์อยู่

หากคุณทราบ วิธีการทำงานของ ufw บน Ubuntu VPS อยู่แล้ว คุณจะเข้าใจหน้าที่ของมัน firewalld เพิ่มแนวคิดสองประการที่ ufw ไม่มี ประการแรกคือ zones ซึ่งเป็นนโยบายที่มีชื่อเรียกสำหรับจัดกลุ่มแพ็กเก็ต ประการที่สองคือการแยกกฎที่ใช้งานจริง (live rules) ออกจากกฎที่บันทึกไว้ (saved rules) ซึ่งควบคุมด้วยแฟล็ก --permanent และเป็นสาเหตุที่ทำให้เกิดความสับสนมากที่สุดในการใช้งานเครื่องมือนี้

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

เปิดใช้งาน SSH ก่อนดำเนินการอื่นใด

การติดตั้ง Rocky และ AlmaLinux ส่วนใหญ่จะมี firewalld ติดตั้งและทำงานอยู่แล้ว โดยการตั้งค่าเริ่มต้นจะอนุญาตให้ใช้งาน SSH ได้ แต่ cloud image แบบ minimal บางตัวอาจไม่มีมาให้ ดังนั้นควรตรวจสอบแทนการคาดเดา

sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --state

firewall-cmd --state จะแสดง running หาก service หยุดทำงาน คำสั่ง firewall-cmd อื่นๆ ทั้งหมดจะตอบกลับเป็น FirewallD is not running และจบการทำงานด้วยสถานะ non-zero นี่คือสิ่งแรกที่ควรตรวจสอบเมื่อคำสั่งดูเหมือนไม่ทำงานเลย

ตอนนี้ให้ตรวจสอบสิ่งที่อนุญาตอยู่ในปัจจุบัน

sudo firewall-cmd --list-all

ผลลัพธ์จริงจะมีบรรทัดอื่นเพิ่มเติมอีกเล็กน้อย แต่บรรทัดที่มีความสำคัญมีดังนี้:

public (active)
  target: default
  interfaces: eth0
  sources:
  services: cockpit dhcpv6-client ssh
  ports:
  rich rules:

ssh ในบรรทัด services: คือเหตุผลที่ session ของคุณยังคงใช้งานได้ หากไม่มีบรรทัดนี้ ให้เพิ่มเข้าไปก่อนที่คุณจะแก้ไขสิ่งอื่นใด เพราะการเริ่ม firewall โดยไม่มีกฎ SSH จะทำให้ session ของคุณถูกตัดและไม่สามารถกลับเข้ามาได้อีก

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload

target: default หมายความว่า packet ที่ไม่ตรงกับกฎใดๆ จะถูกปฏิเสธด้วยการตอบกลับแบบ ICMP (internet control message protocol) ชนิด host-prohibited ทำให้ client ที่พยายามเชื่อมต่อพอร์ตที่ปิดอยู่ได้รับข้อความ No route to host ทันที การตั้งค่า target เป็น DROP จะทำให้เซิร์ฟเวอร์เงียบแทน และเครื่องมือสแกนจะรอจนกว่าจะหมดเวลา (timeout)

sudo firewall-cmd --permanent --zone=public --set-target=DROP
sudo firewall-cmd --reload

โปรดทราบถึงผลกระทบก่อนดำเนินการดังกล่าว: DROP จะทำให้เซิร์ฟเวอร์ไม่ตอบสนองต่อ ping ด้วยเช่นกัน ซึ่งจะส่งผลให้ระบบตรวจสอบสถานะ (monitoring) ของคุณเงียบไปด้วย

ทำไมกฎของฉันถึงหายไป? แฟล็ก --permanent

firewalld เก็บการตั้งค่าไว้สองชุดพร้อมกัน การตั้งค่า runtime คือสิ่งที่เคอร์เนลบังคับใช้ในขณะนี้ ส่วนการตั้งค่า permanent คือสิ่งที่อยู่ใน /etc/firewalld/zones/public.xml และจะถูกนำกลับมาใช้หลังจากสั่ง reload หรือรีบูตเครื่อง

คำสั่งที่ไม่มี --permanent จะเปลี่ยนเฉพาะค่า runtime เท่านั้น กฎจะมีผลทันทีและหายไปเมื่อมีการ reload หรือรีบูตเครื่องครั้งถัดไป ส่วนคำสั่งที่มี --permanent จะเขียนลงในไฟล์แต่ไม่มีผลกับสิ่งที่กำลังรันอยู่ ดังนั้นพอร์ตจะยังคงปิดอยู่จนกว่าคุณจะสั่ง reload พฤติกรรมทั้งสองอย่างไม่ใช่บั๊ก แต่สร้างความประหลาดใจให้ผู้ใช้เพราะคำสั่งจะแสดงผล success เหมือนกันทั้งสองกรณี

ให้เขียนคู่กันเสมอทุกครั้ง

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload

คุณสามารถอ่านการตั้งค่าทั้งสองชุดได้ ซึ่งเป็นวิธีที่เร็วที่สุดในการตรวจสอบว่าคุณทำผิดพลาดในส่วนใด

sudo firewall-cmd --list-services
sudo firewall-cmd --permanent --list-services

คำสั่งแรกจะแสดงชุดกฎที่กำลังทำงานอยู่ ส่วนคำสั่งที่สองจะแสดงชุดกฎที่บันทึกไว้ หากชุดกฎที่กำลังทำงานอยู่มีบริการที่ชุดกฎที่บันทึกไว้ไม่มี กฎนั้นจะหายไปเมื่อมีการ reload ครั้งถัดไป หากชุดกฎที่บันทึกไว้มีบริการที่ชุดกฎที่กำลังทำงานอยู่ไม่มี แสดงว่าคุณลืมสั่ง reload ส่วน sudo firewall-cmd --runtime-to-permanent จะคัดลอกทุกอย่างที่กำลังทำงานอยู่ลงในไฟล์ที่บันทึกไว้ ซึ่งมีประโยชน์หลังจากที่คุณทดลองตั้งค่ามาสักพัก

--reload จะคงสถานะการติดตามการเชื่อมต่อไว้ ดังนั้นเซสชัน SSH ของคุณจะยังคงอยู่ ส่วน --complete-reload จะโหลดโมดูลเคอร์เนลใหม่ด้วยและทำให้สถานะดังกล่าวหายไป ซึ่งมักจะตัดการเชื่อมต่อทุกอย่างที่เปิดอยู่รวมถึงเซสชันของคุณด้วย ให้ใช้การ reload แบบปกติ

มีระบบป้องกันความปลอดภัยที่ติดตั้งมาให้ในตัว กฎแบบ runtime สามารถหมดอายุได้ด้วยตัวเอง

sudo firewall-cmd --add-service=http --timeout=5m

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

โซน และเหตุผลที่โซนเริ่มต้นมีความสำคัญที่สุดบน VPS

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

sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones

บน VPS ที่มีอินเทอร์เฟซเครือข่ายเดียว คำตอบแรกมักจะเป็น public เสมอ และนั่นคือโซนเดียวที่คุณจำเป็นต้องใช้งาน คำสั่ง firewall-cmd ที่ไม่มีอาร์กิวเมนต์ --zone= จะทำงานกับโซนเริ่มต้นโดยอัตโนมัติ ซึ่งเป็นเหตุผลว่าทำไมคำสั่งสั้นๆ ทุกคำสั่งในคู่มือนี้จึงทำงานได้โดยไม่ต้องระบุชื่อโซน

ข้อผิดพลาดที่ทำให้เสียเวลาไปทั้งบ่ายคือการที่อินเทอร์เฟซถูกผูกไว้กับโซนอื่น ทำให้กฎที่คุณเพิ่มเข้าไปไปอยู่ใน public ในขณะที่ทราฟฟิกถูกจัดการโดยโซนอื่น ส่งผลให้สิ่งที่คุณตั้งค่าไปไม่มีผลใดๆ และไม่มีการแจ้งเตือนใดๆ เกิดขึ้น คุณสามารถใช้ --get-active-zones เพื่อตรวจสอบการผูกอินเทอร์เฟซได้:

public
  interfaces: eth0

หากอินเทอร์เฟซปรากฏภายใต้ชื่อโซนอื่น ให้คุณเขียนกฎลงในโซนนั้นโดยใช้ --zone= หรือย้ายอินเทอร์เฟซไปยังโซนที่ถูกต้องแทน

sudo firewall-cmd --permanent --zone=public --change-interface=eth0
sudo firewall-cmd --reload

NetworkManager จะเป็นผู้จัดการอินเทอร์เฟซบน Rocky และ AlmaLinux และจะกำหนดโซนใหม่ทุกครั้งที่การเชื่อมต่อเริ่มทำงาน ดังนั้นคุณควรตั้งค่าใน NetworkManager ด้วยเพื่อป้องกันไม่ให้การรีบูตล้างค่าที่คุณตั้งไว้ โดยให้ใช้ชื่อการเชื่อมต่อจากคำสั่งแรก เนื่องจากชื่อการเชื่อมต่อมักจะไม่ตรงกับชื่ออุปกรณ์

sudo nmcli connection show
sudo nmcli connection modify "System eth0" connection.zone public

การจับคู่ด้วยที่อยู่ต้นทาง (Source matching) มีลำดับความสำคัญสูงกว่าการจับคู่ด้วยอินเทอร์เฟซ ซึ่งเป็นวิธีที่ทำให้ที่อยู่หนึ่งได้รับนโยบายที่แตกต่างออกไป โดยโซน trusted ที่มีมาให้ในตัวจะยอมรับการเชื่อมต่อทั้งหมด

sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.10/32
sudo firewall-cmd --reload

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

firewalld service คืออะไร

service คือชุดของพอร์ตที่ถูกจัดกลุ่มไว้ภายใต้ชื่อหนึ่ง โดยเก็บอยู่ในรูปแบบไฟล์ XML ตัวอย่างเช่น --add-service=https จะเปิดพอร์ต 443/tcp เนื่องจาก /usr/lib/firewalld/services/https.xml เป็นตัวกำหนดว่า https หมายถึงอะไร

sudo firewall-cmd --get-services
sudo firewall-cmd --info-service=https

--info-service จะแสดงรายการพอร์ตที่ซ่อนอยู่ภายใต้ชื่อนั้น:

https
  ports: 443/tcp

ควรใช้ชื่อเรียกแทนพอร์ตเมื่อมีชื่อนั้นกำหนดไว้ เพราะจะช่วยให้อ่านค่าใน --list-all ได้ง่ายขึ้นเมื่อกลับมาดูในอีก 6 เดือนข้างหน้า นอกจากนี้แพ็กเกจอย่าง Cockpit ยังติดตั้งไฟล์ service ของตนเองมาให้ด้วย ให้ใช้ --add-port สำหรับบริการใดก็ตามที่ยังไม่มีการกำหนดนิยามไว้

สิ่งที่ต้องระวังคือ: service ssh หมายถึงพอร์ต 22/tcp เท่านั้น หากคุณย้าย SSH ไปยังพอร์ตอื่นในระหว่างขั้นตอน การเพิ่มความปลอดภัยในการเข้าถึง SSH บนเซิร์ฟเวอร์ คำสั่ง --add-service=ssh จะไม่เปิดพอร์ตที่คุณใช้งานจริง

sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload

ในการติดตั้ง RHEL ใหม่ จะมีการล็อกชั้นที่สองไว้เสมอ นั่นคือ SELinux (security-enhanced Linux) ซึ่งทำหน้าที่กำหนด label ให้กับหมายเลขพอร์ต และ sshd จะไม่ได้รับอนุญาตให้ bind เข้ากับพอร์ตที่อยู่นอกเหนือจาก label ที่กำหนดไว้ ส่งผลให้ service ปฏิเสธที่จะเริ่มทำงานและใน log จะแสดงข้อความ error: Bind to port 2222 on 0.0.0.0 failed: Permission denied. คุณต้องทำการ label พอร์ตนั้นเสียก่อน

sudo dnf install -y policycoreutils-python-utils
sudo semanage port -a -t ssh_port_t -p tcp 2222

ฉันจะตรวจสอบได้อย่างไรว่าพอร์ตใดเปิดอยู่บ้างในขณะนี้?

sudo firewall-cmd --list-all
sudo firewall-cmd --list-rich-rules
sudo nft list table inet firewalld | head -n 40

คำสั่งสองรายการแรกจะแสดงข้อมูลตามที่ firewalld รับทราบ ส่วนคำสั่งที่สามจะอ่านกฎที่ kernel ใช้งานจริงในตารางที่ firewalld ดูแลอยู่ ซึ่งข้อมูลควรจะตรงกัน

อย่างไรก็ตาม ข้อมูลเหล่านี้ไม่ใช่เครื่องยืนยันที่สมบูรณ์ ควรทดสอบจากเครื่องอื่นแทน:

nc -zv 203.0.113.20 443

ห้ามรันการทดสอบบนตัวเซิร์ฟเวอร์เอง เนื่องจาก firewalld จะยอมรับการเชื่อมต่อทั้งหมดที่เข้ามาทาง loopback interface ดังนั้น curl http://localhost:8080 จะทำงานสำเร็จเสมอไม่ว่ากฎของคุณจะเป็นอย่างไร การทดสอบดังกล่าวบอกได้เพียงว่าบริการนั้นทำงานอยู่ แต่ไม่สามารถใช้ตรวจสอบสถานะของ firewall ได้เลย

การอนุญาตพอร์ตสำหรับเว็บ

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-services

คำสั่งสุดท้ายควรแสดงรายการ http https เพิ่มเข้ามาจากรายการเดิม หากเว็บไซต์ยังคงไม่ตอบสนอง ปัญหาอาจไม่ได้อยู่ที่ไฟร์วอลล์ กฎของไฟร์วอลล์ทำหน้าที่เพียงอนุญาตให้แพ็กเก็ตผ่านเข้ามาได้เท่านั้น แต่ตัวโปรเซสยังคงต้องเปิดพอร์ตเพื่อรอรับการเชื่อมต่ออยู่

sudo ss -tlnp

ซ็อกเก็ตที่แสดงเป็น 0.0.0.0:443 หรือ *:443 จะยอมรับการเชื่อมต่อจากทุกที่อยู่ ส่วนซ็อกเก็ตที่แสดงเป็น 127.0.0.1:443 จะตอบสนองเฉพาะบน loopback เท่านั้น ซึ่งไม่มีกฎไฟร์วอลล์ใดที่จะทำให้เข้าถึงจากภายนอกได้ พอร์ตและซ็อกเก็ตที่รอรับการเชื่อมต่อบน Linux อธิบายความแตกต่างนี้ไว้ในรายละเอียดเพิ่มเติม

ฉันจะปิดพอร์ตอีกครั้งได้อย่างไร

sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --reload

กฎ --permanent มีผลในกรณีนี้เช่นกัน และส่งผลกระทบที่รุนแรงกว่าในทิศทางนี้ หากคุณลบ service ออกจาก runtime เท่านั้น พอร์ตจะดูเหมือนถูกปิด แต่เมื่อมีการ reload หรือ reboot ครั้งถัดไป พอร์ตจะถูกเปิดขึ้นมาใหม่จากไฟล์ที่บันทึกไว้ นี่คือช่องโหว่ที่คุณอาจไม่ทันสังเกต เพราะการตรวจสอบที่คุณทำผ่านไปได้ด้วยดี

การลบสิ่งที่ไม่มีอยู่จริงจะแสดงข้อความ Warning: NOT_ENABLED: http และยังคง exit ด้วยสถานะ 0 การเพิ่มสิ่งเดิมซ้ำสองครั้งจะแสดงข้อความ Warning: ALREADY_ENABLED: http ทั้งสองกรณีมีความปลอดภัย แต่การสะกดชื่อผิดนั้นต่างออกไป: Error: INVALID_SERVICE หมายความว่า firewalld ไม่มีนิยามของชื่อนั้นอยู่และไม่มีการเปลี่ยนแปลงใดๆ เกิดขึ้นเลย

หาก --list-all ของคุณแสดง cockpit และคุณไม่ได้ใช้งาน Cockpit web console บนพอร์ต 9090 ให้ลบออกเสีย พอร์ตที่เปิดอยู่ทุกพอร์ตคือบริการที่คุณต้องคอยดูแลให้เป็นเวอร์ชันล่าสุดอยู่เสมอ

การจำกัดพอร์ตให้รับเฉพาะที่อยู่ต้นทางเดียว

Rich rules เป็นรูปแบบคำสั่งแบบยาวสำหรับกรณีที่ชื่อ service ปกติไม่สามารถระบุเงื่อนไขที่ต้องการได้ การจำกัด SSH ให้รับเฉพาะที่อยู่ IP ของสำนักงานแห่งเดียวต้องใช้คำสั่ง 2 ชุด และคำสั่งที่สองมักเป็นสิ่งที่ผู้ใช้งานลืมทำ

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" service name="ssh" accept'
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload

Zone คือชุดของสิทธิ์ ไม่ใช่รายการลำดับเลขที่จะหยุดทำงานเมื่อพบเงื่อนไขแรกที่ตรงกัน Rich rule จะเพิ่มการอนุญาต (accept) สำหรับที่อยู่หนึ่งๆ แต่ไม่ได้ปฏิเสธ (deny) ใครทั้งสิ้น ในขณะที่ ssh ยังคงอยู่ในบรรทัด services: อินเทอร์เน็ตทั้งหมดยังคงเข้าถึงพอร์ต 22 ได้ และ rich rule จะไม่ส่งผลลัพธ์ใดๆ ที่คุณสามารถวัดผลได้ คุณต้องลบรายการที่อนุญาตแบบกว้างออก มิฉะนั้นรายการที่จำกัดไว้ก็จะเป็นเพียงการตกแต่งเท่านั้น

สำหรับพอร์ตที่ไม่มีชื่อ service ให้ระบุเป็นหมายเลขพอร์ตแทน

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" port port="5432" protocol="tcp" accept'

หากต้องการทิ้ง (drop) ทราฟฟิกจากเครือข่ายที่รบกวนและต้องการบันทึกข้อมูลไว้ ให้วาง element log ไว้ก่อนหน้า action ซึ่งเป็นลำดับที่ภาษาของ rich rule กำหนดไว้

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" log prefix="fw-drop " level="info" limit value="3/m" drop'
sudo firewall-cmd --reload
sudo journalctl -k -g fw-drop

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

คำสั่ง ufw และคำสั่งที่เทียบเท่าใน firewall-cmd

งานเดียวกันแต่ใช้เครื่องมือต่างกัน ทุกบรรทัด --permanent จำเป็นต้องมี sudo firewall-cmd --reload ตามหลัง ซึ่งเป็นสิ่งเดียวที่รายการลักษณะนี้ไม่สามารถแสดงให้คุณเห็นได้

  • sudo ufw enable เปลี่ยนเป็น sudo systemctl enable --now firewalld
  • sudo ufw disable เปลี่ยนเป็น sudo systemctl disable --now firewalld
  • sudo ufw status verbose เปลี่ยนเป็น sudo firewall-cmd --list-all
  • sudo ufw allow OpenSSH เปลี่ยนเป็น sudo firewall-cmd --permanent --add-service=ssh
  • sudo ufw allow 443/tcp เปลี่ยนเป็น sudo firewall-cmd --permanent --add-port=443/tcp
  • sudo ufw delete allow 443/tcp เปลี่ยนเป็น sudo firewall-cmd --permanent --remove-port=443/tcp
  • sudo ufw allow from 203.0.113.10 to any port 22 เปลี่ยนเป็น rich rule ตามที่แสดงไว้ด้านบน
  • sudo ufw reload เปลี่ยนเป็น sudo firewall-cmd --reload
  • sudo ufw default deny incoming คือลักษณะการทำงานของโซน public อยู่แล้ว และ --set-target=DROP คือเวอร์ชันที่ไม่มีการตอบกลับ (silent) ของโซนดังกล่าว
  • sudo ufw logging on เปลี่ยนเป็น sudo firewall-cmd --set-log-denied=all

มีข้อแตกต่างหนึ่งประการที่ควรระบุให้ชัดเจน ufw จะเก็บรายการกฎแบบมีหมายเลขกำกับและคุณสามารถแทรกกฎไว้ที่ตำแหน่งที่ 1 ได้ แต่ firewalld ไม่มีหมายเลขกฎ ดังนั้นคำสั่ง "ใส่กฎนี้ไว้ลำดับแรก" จึงไม่มีความหมายในที่นี้ เมื่อรายการของ firewalld สองรายการดูเหมือนจะขัดแย้งกัน กฎแบบอนุญาต (accept) ที่กว้างกว่าจะเป็นฝ่ายชนะ เนื่องจากไม่มีกฎใดในชุดที่สั่งปฏิเสธ (deny) คุณจะต้องลบรายการที่กว้างเกินไปออกด้วยตนเอง

ทำไม Docker container ของฉันถึงเข้าถึงได้ทั้งที่ firewall ดูเหมือนปิดอยู่?

เพราะพอร์ตของ container ที่ถูก publish ไม่เคยผ่านส่วนของ firewall ที่ zone ของคุณควบคุมอยู่ docker run -d -p 8080:80 nginx สั่งให้ Docker เขียนกฎ NAT (network address translation) และการส่งต่อ (forwarding) ของตัวเองขึ้นมา แพ็กเก็ตที่เข้ามาทาง 8080 จะถูกเขียนใหม่และส่งต่อไปยัง container ดังนั้นมันจึงเป็นการส่งต่อ (forward) แทนที่จะเป็นการส่งถึงตัว host โดยตรง กฎใน services: และ ports: ใน zone ของคุณจะควบคุมเฉพาะแพ็กเก็ตที่ส่งถึงตัว host เท่านั้น ส่วนกฎของ Docker จะควบคุมเส้นทางการส่งต่อ (forward path) และกฎเหล่านั้นตั้งค่าเป็นยอมรับ (accept)

ผลลัพธ์ที่ได้คือเซิร์ฟเวอร์ที่ sudo firewall-cmd --list-all ไม่แสดงพอร์ต 8080 แต่การใช้ nc -zv 203.0.113.20 8080 จากเครื่องอื่นกลับเชื่อมต่อได้อยู่ดี ให้ตรวจสอบสิ่งที่ Docker ติดตั้งไว้ดังนี้:

sudo iptables -t nat -L DOCKER -n

วิธีแก้ไขอยู่ที่ flag สำหรับการ publish ให้ bind พอร์ตเข้ากับ loopback แล้ววาง reverse proxy ไว้ด้านหน้าแทน

docker run -d -p 127.0.0.1:8080:80 nginx

ตอนนี้ container จะตอบสนองเฉพาะ curl http://127.0.0.1:8080 บนตัวเซิร์ฟเวอร์เท่านั้น และไม่มีการตอบสนองจากภายนอก ผู้ใช้ Ubuntu มักเจอปัญหานี้เช่นกัน ซึ่งอธิบายไว้ใน ทำไม Docker containers ถึง publish พอร์ตข้าม ufw ไปโดยตรง สำหรับ Podman แบบ rootful ซึ่งมีมาให้ใน base repository ของ Rocky และ AlmaLinux นั้น จะ publish พอร์ตด้วยวิธี NAT แบบเดียวกัน ดังนั้นควรทดสอบจากเครื่องอื่นแทนที่จะเชื่อรายการใน zone เพียงอย่างเดียว

การทำให้ระบบทำงานได้หลังรีบูตและข้อผิดพลาดที่คุณจะพบ

sudo systemctl is-enabled firewalld
sudo systemctl status firewalld

enabled และ active (running) คือสิ่งที่คุณต้องการ ไฟร์วอลล์ที่กำลังทำงานอยู่แต่ไม่ได้เปิดใช้งาน (enabled) จะช่วยป้องกันคุณได้จนกว่าจะรีบูตครั้งแรก การตรวจสอบนี้ควรอยู่ในรายการที่คุณต้องทำใน สิบนาทีแรกบน VPS ใหม่ ควบคู่ไปกับการตั้งค่า SSH keys และการอัปเดตระบบ

คำสั่ง nftables ดิบและ firewalld ไม่สามารถใช้งานร่วมกันได้ firewalld เป็นเจ้าของตารางที่ชื่อว่า inet firewalld หากใช้ sudo nft flush ruleset ลบตารางดังกล่าว เซิร์ฟเวอร์จะเปิดรับการเชื่อมต่อทุกอย่าง และ firewall-cmd --list-all จะยังคงแสดงผลการตั้งค่าที่คุณต้องการอยู่ เพราะ firewalld รายงานสิ่งที่มันเชื่อว่าควรจะเป็น ไม่ใช่สิ่งที่อยู่ใน kernel จริงๆ การใช้ sudo firewall-cmd --reload จะเป็นการติดตั้งกฎใหม่ทั้งหมด คุณควรเขียนกฎด้วย firewall-cmd เพื่อให้กฎเหล่านั้นกลับมาทำงานใหม่หลังจากรีโหลด

การใช้ตัวจัดการไฟร์วอลล์สองตัวบนเซิร์ฟเวอร์เดียว การติดตั้ง ufw หรือ iptables-services ควบคู่ไปกับ firewalld จะทำให้คุณมีโปรแกรมสองตัวที่เขียนกฎโดยไม่รับรู้การทำงานของกันและกัน และตัวที่จะชนะคือบริการที่เริ่มทำงานหลังสุด ให้เลือกเพียงตัวเดียว สำหรับ Rocky และ AlmaLinux นั้น firewalld คือตัวที่มีการสนับสนุนจากตัวดิสทริบิวชันโดยตรง

ไฟร์วอลล์ของผู้ให้บริการที่อยู่หน้าเซิร์ฟเวอร์ แผงควบคุม VPS หลายแห่งมีไฟร์วอลล์เครือข่ายแยกต่างหาก หาก --list-all แสดงว่าพอร์ตเปิดอยู่แต่การเชื่อมต่อจากภายนอกยังคงล้มเหลว ให้ตรวจสอบที่แผงควบคุมก่อนที่จะแก้ไขสิ่งใดบนเซิร์ฟเวอร์ และในทางกลับกัน กฎที่เปิดไว้ในแผงควบคุมจะไม่มีผลหาก firewalld ปฏิเสธแพ็กเก็ตนั้น

การรัน firewall-cmd โดยไม่ใช้ sudo ทุกการเปลี่ยนแปลงจำเป็นต้องใช้สิทธิ์ root หากไม่มีสิทธิ์ดังกล่าว คำขอจะถูกปฏิเสธโดยการตรวจสอบสิทธิ์และไม่มีการแก้ไขใดๆ เกิดขึ้น ซึ่งอาจดูเหมือนว่าคำสั่งถูกเพิกเฉยในทันที

คำสั่งหกรายการครอบคลุมการใช้งานส่วนใหญ่ในแต่ละวัน: --list-all เพื่ออ่านสถานะ, --permanent --add-service หรือ --add-port เพื่อเปิดพอร์ต, --permanent --remove-service เพื่อปิดพอร์ต, --reload เพื่อนำไฟล์ที่บันทึกไว้มาใช้งาน และ --runtime-to-permanent หลังจากทดลองตั้งค่าเสร็จสิ้น โซนที่ใช้คือ public, แฟล็กที่ใช้คือ --permanent และการตรวจสอบที่เชื่อถือได้เพียงอย่างเดียวคือการตรวจสอบจากเครื่องอื่น

FAQ

ทำไมกฎ firewalld ของฉันถึงหายไปหลังจากรีบูตเครื่อง?

กฎดังกล่าวถูกเพิ่มเข้าไปใน configuration ขณะรันไทม์เท่านั้น sudo firewall-cmd --add-service=http จะมีผลทันทีแต่จะถูกลบออกเมื่อมีการโหลดใหม่หรือรีบูตเครื่อง เนื่องจาก configuration ที่บันทึกไว้ใน /etc/firewalld/zones/public.xml ไม่ได้ถูกแก้ไข ให้เพิ่ม --permanent แล้วรัน sudo firewall-cmd --reload หากต้องการเก็บกฎที่คุณเพิ่มด้วยตนเองไปแล้ว ให้รัน sudo firewall-cmd --runtime-to-permanent ซึ่งจะเป็นการคัดลอกกฎที่ใช้งานอยู่ลงในไฟล์ที่บันทึกไว้

ทำไมไม่มีอะไรเปลี่ยนแปลงหลังจากฉันเพิ่มกฎด้วย --permanent?

เพราะ --permanent จะเขียนไฟล์ลงดิสก์แต่ไม่ได้ส่งผลต่อ firewall ที่กำลังทำงานอยู่ พอร์ตจะยังคงปิดอยู่จนกว่า sudo firewall-cmd --reload จะโหลด configuration ที่บันทึกไว้เข้าสู่ kernel ให้เปรียบเทียบ sudo firewall-cmd --list-services กับ sudo firewall-cmd --permanent --list-services หากรายการที่บันทึกไว้มีรายการที่รายการที่ใช้งานอยู่ไม่มี แสดงว่าคุณยังไม่ได้ทำการโหลดใหม่ (reload)

ฉันควรใช้ --add-service หรือ --add-port?

ให้ใช้ --add-service เมื่อมีชื่อบริการที่ตรงกับสิ่งที่คุณรันอยู่ เพราะเป็นการระบุความต้องการที่ชัดเจน และ sudo firewall-cmd --info-service=https จะแสดงให้เห็นว่าชื่อบริการนั้นครอบคลุมพอร์ตใดบ้าง ให้ใช้ --add-port เมื่อไม่มีการกำหนดบริการของคุณไว้ หรือเมื่อบริการนั้นฟังพอร์ตที่ไม่ใช่ค่ามาตรฐาน บริการ ssh หมายถึงพอร์ต 22/tcp เท่านั้น ดังนั้นหากย้าย SSH ไปที่ 2222 จำเป็นต้องใช้ --add-port=2222/tcp ร่วมกับการกำหนด SELinux label สำหรับพอร์ตนั้นด้วย

ทำไม Docker container ของฉันถึงเข้าถึงได้ทั้งที่ firewall-cmd แสดงว่าพอร์ตปิดอยู่?

พอร์ตที่ถูก publish จะถูกเขียนทับด้วยกฎ NAT ของ Docker เองและส่งต่อไปยัง container ดังนั้นแพ็กเก็ตจึงไม่ถูกส่งมาที่โฮสต์โดยตรง และรายการบริการหรือพอร์ตของ zone จะครอบคลุมเฉพาะแพ็กเก็ตที่ส่งมายังโฮสต์เท่านั้น container จึงตอบสนองจากอินเทอร์เน็ตได้ในขณะที่ --list-all ไม่แสดงข้อมูลใดๆ ให้เปลี่ยนไป publish ที่ loopback แทนด้วย docker run -d -p 127.0.0.1:8080:80 nginx และใช้ reverse proxy วางไว้ด้านหน้าแทน

ฉันสามารถติดตั้ง ufw บน Rocky Linux แทน firewalld ได้หรือไม่?

การมีตัวจัดการ firewall สองตัวบนเซิร์ฟเวอร์เดียวจะทำให้เกิดการเขียนกฎโดยที่อีกตัวไม่รับรู้ และกฎชุดใดจะคงอยู่ขึ้นอยู่กับว่าบริการใดเริ่มทำงานทีหลัง firewalld เป็นเครื่องมือที่รองรับบน Rocky Linux และ AlmaLinux โดยมีการติดตั้งมาให้แล้ว และมันทำงานบน backend เดียวกันกับ nftables ที่ ufw ใช้ ให้เรียนรู้เรื่อง default zone และแฟล็ก --permanent เพียงครั้งเดียว คุณก็จะเข้าใจการทำงานของเครื่องมือนี้ทั้งหมด