{"id":281,"date":"2026-04-29T13:44:00","date_gmt":"2026-04-29T13:44:00","guid":{"rendered":"https:\/\/sunpathservers.net\/news\/?p=281"},"modified":"2026-05-25T18:12:26","modified_gmt":"2026-05-25T18:12:26","slug":"mastering-the-firewall-a-guide-to-nftables","status":"publish","type":"post","link":"https:\/\/sunpathservers.net\/blog\/mastering-the-firewall-a-guide-to-nftables\/","title":{"rendered":"Mastering the Firewall: A Guide to nftables"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">If you&#8217;ve been managing Linux servers for a while, you&#8217;ve likely cross paths with <code>iptables<\/code>. For years, it was the undisputed king of Linux packet filtering. But times change, and so does the Linux kernel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Enter <strong><code>nftables<\/code><\/strong>, the modern successor designed to replace <code>iptables<\/code>, <code>ip6tables<\/code>, <code>arptables<\/code>, and <code>ebtables<\/code> with a single, unified framework. It brings better performance, a much cleaner syntax, and a more efficient way to handle rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s break down how <code>nftables<\/code> works and how to configure a robust, production-ready firewall.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Make the Switch?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before diving into the syntax, it helps to understand why <code>nftables<\/code> is a massive upgrade over its predecessor:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Unified Syntax:<\/strong> No more switching between <code>iptables<\/code> and <code>ip6tables<\/code>. A single rule can handle both IPv4 and IPv6.<\/li>\n\n\n\n<li><strong>Less Kernel Overhead:<\/strong> Instead of compiling specific code for every single match case, <code>nftables<\/code> uses a lightweight virtual machine inside the kernel that executes bytecode. It\u2019s faster and leaner.<\/li>\n\n\n\n<li><strong>Atomic Updates:<\/strong> You can apply an entire configuration file at once. If there&#8217;s a syntax error, the whole thing fails, preventing you from accidentally locking yourself out with a half-applied rule set.<\/li>\n\n\n\n<li><strong>Native Sets:<\/strong> You can group IP addresses, ports, or interfaces into sets natively, allowing a single rule to match multiple elements efficiently.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Core Concepts: Tables, Chains, and Rules<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Unlike <code>iptables<\/code>, which comes with predefined tables (like <code>filter<\/code>, <code>nat<\/code>, and <code>mangle<\/code>), <code>nftables<\/code> starts as a <strong>blank slate<\/strong>. You define exactly what you need.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Tables<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Tables are the highest-level containers. They hold your chains. You must specify an address family for each table, such as:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ip<\/code> (IPv4)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ip6<\/code> (IPv6)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>inet<\/code> (Both IPv4 and IPv6 \u2014 <strong>highly recommended<\/strong>)<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Chains<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Chains live inside tables and actually see the packets. There are two types:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Regular Chains:<\/strong> Used to organize your rules (similar to a custom subroutine).<\/li>\n\n\n\n<li><strong>Base Chains:<\/strong> The entry points into the netfilter framework. They require a <strong>type<\/strong>, <strong>hook<\/strong>, and <strong>priority<\/strong> so the kernel knows exactly when to trigger them (e.g., when a packet enters the network card).<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Rules<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Rules are the individual instructions inside chains that inspect packets and take action (e.g., <code>accept<\/code>, <code>drop<\/code>, <code>reject<\/code>).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step-by-Step: Building a Basic Firewall<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s configure a standard stateful firewall for a server. This setup will block all incoming traffic by default, except for SSH, HTTP, HTTPS, and ping, while allowing all outbound traffic.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Clean the Slate<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">First, let&#8217;s flush any existing <code>nftables<\/code> configurations to make sure we&#8217;re starting fresh.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft flush ruleset<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Create the Table<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add table inet my_firewall<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Create the Base Chains<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now, we create the <code>input<\/code>, <code>forward<\/code>, and <code>output<\/code> chains. Notice how we define their hooks and set the default policies.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"># Drop all incoming traffic by default<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add chain inet my_firewall input { type filter hook input priority 0 \\; policy drop \\; }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"># Drop all forwarded traffic (good for non-routers)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add chain inet my_firewall forward { type filter hook forward priority 0 \\; policy drop \\; }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"># Allow all outbound traffic by default<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add chain inet my_firewall output { type filter hook output priority 0 \\; policy accept \\; }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Note: The backslashes (<code>\\;<\/code>) are required in terminal commands to prevent your shell from misinterpreting the semicolons.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Add the Rules<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now, let&#8217;s populate our <code>input<\/code> chain with rules to keep the server functional but secure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>1. Allow Loopback Traffic:<\/strong><br>Essential for local services communicating with each other.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add rule inet my_firewall input iifname \"lo\" accept<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>2. Allow Established and Related Connections:<\/strong><br>This makes the firewall &#8220;stateful.&#8221; It ensures that if your server initiates a connection (like a system update), the returning traffic is allowed back in.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add rule inet my_firewall input ct state established,related accept<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>3. Allow Essential Services (SSH, HTTP, HTTPS):<\/strong><br>Instead of writing three separate rules, we can use an <code>nftables<\/code> set (enclosed in curly braces) to handle them in one go.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add rule inet my_firewall input tcp dport { 22, 80, 443 } accept<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>4. Allow Ping (ICMP &amp; ICMPv6):<\/strong><br>Crucial for network diagnostics.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft add rule inet my_firewall input icmp type echo-request accept\nsudo nft add rule inet my_firewall input icmpv6 type echo-request accept<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Reviewing and Saving Your Work<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To see the ruleset you just built in its clean, human-readable format, run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft list ruleset<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your output will look remarkably clean compared to old <code>iptables<\/code> listings:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>table inet my_firewall {\n\tchain input {\n\t\ttype filter hook input priority filter; policy drop;\n\t\tiifname \"lo\" accept\n\t\tct state established,related accept\n\t\ttcp dport { 22, 80, 443 } accept\n\t\ticmp type echo-request accept\n\t\ticmpv6 type echo-request accept\n\t}\n\n\tchain forward {\n\t\ttype filter hook forward priority filter; policy drop;\n\t}\n\n\tchain output {\n\t\ttype filter hook output priority filter; policy accept;\n\t}\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Making it Permanent<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Runtime changes will vanish if the server reboots. To make them permanent, save them to your system&#8217;s <code>nftables<\/code> configuration file (usually located at <code>\/etc\/nftables.conf<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On Debian\/Ubuntu systems:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo sh -c \"nft list ruleset &gt; \/etc\/nftables.conf\"\nsudo systemctl enable nftables\nsudo systemctl start nftables<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On RHEL\/Rocky Linux systems:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo sh -c \"nft list ruleset &gt; \/etc\/sysconfig\/nftables.conf\"\nsudo systemctl enable nftables<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Pro-Tip: Atomic Reloads<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you ever need to update your firewall in the future, don&#8217;t run commands line-by-line. Instead, edit your <code>\/etc\/nftables.conf<\/code> file directly, and then reload it atomically:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nft -f \/etc\/nftables.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If there is a typo on, for example, line 15, <code>nftables<\/code> will reject the entire file, and your active firewall will remain completely untouched\u2014saving you from a stressful trip to the recovery console.<\/p>\n\n\n\n<div style=\"background-color: #121212; border-left: 4px solid #FFCF4D; padding: 25px 30px; margin-top: 40px; border-radius: 0 8px 8px 0; font-family: sans-serif;\">\n    <h4 style=\"color: #FFCF4D; margin-top: 0; margin-bottom: 14px; font-size: 1.5rem; letter-spacing: 1px; text-transform: uppercase; font-weight: 700;\">\n        \ud83d\udee1\ufe0f Edge-Defended Dedicated Hardware\n    <\/h4>\n    <p style=\"color: #e0e0e0; font-size: 1.5rem; line-height: 1.6; margin-bottom: 18px;\">\n        Migrating to nftables grants you incredible, high-performance control over packet filtering and stateful tracking at the kernel level. Yet, even the most optimized nftables ruleset requires local CPU cycles to evaluate and drop malicious traffic. When a massive volumetric DDoS assault targets your infrastructure, upstream hardware mitigation is the only way to keep your pipes clear.\n    <\/p>\n    <p style=\"color: #e0e0e0; font-size: 1.5rem; line-height: 1.6; margin-bottom: 0;\">\n        \ud83d\udc49 <a href=\"https:\/\/sunpathservers.net\/sunpath-inventory.html\" style=\"color: #40FFFF; text-decoration: none; border-bottom: 1px dashed #40FFFF;\">\n            View Our Live Unmanaged Server Inventory\n        <\/a> \n        to deploy dedicated hardware inherently protected by automated inline DDoS mitigation, massive port capacities, and premium network routing.\n    <\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>If you&#8217;ve been managing Linux servers for a while, you&#8217;ve likely cross paths with iptables. For years, it was the undisputed king of Linux packet filtering. But times change, and so does the Linux kernel. Enter nftables, the modern successor designed to replace iptables, ip6tables, arptables, and ebtables with a single, unified framework. It brings [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":537,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[450,448,447,186,68,446,451,106,449],"class_list":["post-281","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-server-hardening","tag-infrastructure-security-2","tag-linux-firewall-2","tag-linux-kernel-networking-2","tag-netfilter","tag-nftables","tag-packet-filtering-2","tag-ports-and-protocols-2","tag-server-hardening-2","tag-stateful-tracking-2"],"_links":{"self":[{"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/posts\/281","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/comments?post=281"}],"version-history":[{"count":1,"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/posts\/281\/revisions"}],"predecessor-version":[{"id":655,"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/posts\/281\/revisions\/655"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/media\/537"}],"wp:attachment":[{"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/media?parent=281"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/categories?post=281"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sunpathservers.net\/blog\/wp-json\/wp\/v2\/tags?post=281"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}