Operations 17 min read

Master nftables: Build a Simple Linux Firewall with Token Bucket Rate Limiting

This guide walks you through installing nftables on CentOS 7, creating a basic firewall with INPUT, FORWARD, and OUTPUT chains, leveraging built‑in sets and maps for efficient IP and port matching, implementing connection‑tracking, token‑bucket rate limiting for ICMP, handling TCP/UDP traffic, persisting rules, and configuring rsyslog logging.

Programmer DD
Programmer DD
Programmer DD
Master nftables: Build a Simple Linux Firewall with Token Bucket Rate Limiting

The previous article introduced the advantages and basic usage of nftables, which compiles network rules to bytecode in user space and executes them in the kernel virtual machine, offering greater flexibility than iptables while still using netfilter.

Unlike iptables, which often requires ipset for large data matching, nftables has built‑in sets and maps, making large‑scale matching much simpler.

Install nftables

$ yum install -y nftables

Uncomment the include line in /etc/sysconfig/nftables.conf to load example configurations: # include "/etc/nftables/inet-filter" Start the service: $ systemctl start nftables Listing the ruleset now shows a filter table with three default chains:

$ nft list ruleset

table inet filter {
	chain input { type filter hook input priority 0; policy accept; }
	chain forward { type filter hook forward priority 0; policy accept; }
	chain output { type filter hook output priority 0; policy accept; }
}

Loopback Interface

$ nft add rule inet filter input iif "lo" accept
$ nft add rule inet filter input iif != "lo" ip daddr 127.0.0.0/8 drop

With comments and counters:

$ nft add rule inet filter input \
   iif "lo" \
   accept \
   comment "Accept any localhost traffic"

$ nft add rule inet filter input \
   iif != "lo" ip daddr 127.0.0.0/8 \
   counter \
   drop \
   comment "drop connections to loopback not coming from loopback"

Connection Tracking Module

The conntrack module tracks connection states (NEW, ESTABLISHED, RELATED, INVALID) and is essential for NAT and stateful firewall rules.

$ nft add rule inet filter input \
   ct state invalid \
   log prefix "Invalid-Input: " level info flags all \
   counter \
   drop \
   comment "Drop invalid connections"

Token Bucket Rate Limiting

To mitigate ping‑flood attacks, a token‑bucket limiter can be applied to ICMP echo‑request packets:

$ nft add rule inet filter input \
   ip protocol icmp icmp type echo-request \
   limit rate 20 bytes/second burst 500 bytes \
   counter \
   accept \
   comment "No ping floods"

Packets that exceed the limit are dropped:

$ nft add rule inet filter input \
   ip protocol icmp icmp type echo-request \
   drop \
   comment "No ping floods"

ICMP & IGMP

$ nft add rule inet filter input \
   ip protocol icmp icmp type { destination-unreachable, router-advertisement, router-solicitation, time-exceeded, parameter-problem } \
   accept \
   comment "Accept ICMP"

$ nft add rule inet filter input \
   ip protocol igmp \
   accept \
   comment "Accept IGMP"

Separate TCP and UDP Handling

Create dedicated chains and a map to dispatch traffic based on the L4 protocol:

$ nft add chain inet filter TCP
$ nft add chain inet filter UDP
$ nft add map inet filter input_vmap { type inet_proto : verdict; }
$ nft add element inet filter input_vmap { tcp : jump TCP, udp : jump UDP }
$ nft add rule inet filter input meta l4proto vmap @input_vmap

SSH Rule (TCP)

$ nft add rule inet filter TCP \
   tcp dport 22 \
   ct state new \
   limit rate 15/minute \
   log prefix "New SSH connection: " \
   counter \
   accept \
   comment "Avoid brute force on SSH"

Web Service Rule (TCP)

Create a set for web ports and allow them:

$ nft add set inet filter web { type inet_service; flags interval; }
$ nft add element inet filter web { 80, 443 }
$ nft add rule inet filter TCP \
   tcp dport @web \
   counter \
   accept \
   comment "Accept web server"

Custom Service Example (TCP & UDP)

$ nft add set inet filter v-2-r-a-y { type inet_service; flags interval; }
$ nft add element inet filter v-2-r-a-y { 9000-9005, 9007 }
$ nft add rule inet filter TCP \
   tcp dport @v-2-r-a-y \
   counter \
   accept \
   comment "Accept v-2-r-a-y"

$ nft add rule inet filter UDP \
   udp dport @v-2-r-a-y \
   counter \
   accept \
   comment "Accept v-2-r-a-y"

Persisting Rules

$ echo "#! /usr/sbin/nft -f" > /etc/nftables/inet-filter
$ nft list ruleset >> /etc/nftables/inet-filter
$ systemctl enable nftables

Logging with rsyslog

Create a directory for nftables logs and configure rsyslog to write specific messages to separate files:

$ mkdir /var/log/nftables
$ touch /var/log/nftables/{ssh.log,invalid.log}
:msg,regex,"Invalid-Input: " -/var/log/nftables/invalid.log
:msg,regex,"New SSH connection: " -/var/log/nftables/ssh.log

Log Rotation

/var/log/nftables/* {
    rotate 5
    daily
    maxsize 50M
    missingok
    notifempty
    delaycompress
    compress
    postrotate
        invoke-rc.d rsyslog rotate > /dev/null
    endscript
}

Conclusion

The article demonstrates how to set up a simple firewall with nftables, use sets and maps for modular rule management, apply token‑bucket rate limiting, handle TCP/UDP traffic, persist configurations, and configure dedicated logging. Future articles will cover advanced DDoS protection techniques.

firewallToken Bucketconnection trackingiptables alternativenftables
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.