Basic Nftables configuration in Linux with a Bash script
Greetings!

Automating nftables configuration in Linux with a universal Bash script using a basic rule set for filtering network traffic.

The nftables.sh script proposed in this article was created under a long-standing impression from an article on serveradmin.ru dedicated to configuring iptables.

I will say right away that this is not a manual for working with nftables, but only an example of using this program for quick and convenient configuration of a basic firewall in Linux.

It is assumed that you have some idea of what Netfilter is and how tools for configuring it work, such as iptables, nftables, ufw, firewald.

If you previously worked with iptables but have only heard about nftables, I recommend a useful introductory article by my colleague: 🔗 Firewall configuration - Nftables.

Preface

Like many other Linux system administrators, I somewhat “slept through” the moment when popular distributions switched to using nftables as the default backend for firewall management.

Yes, yes, if you use iptables:

BASH
sudo iptables -L -n
Click to expand and view more

Then, with a high degree of probability, your rules have already been translated into nftables:

BASH
sudo nft list ruleset
Click to expand and view more

The developers tried to make the transition as smooth as possible and created a tool for compatibility of iptables rules with nftables. At the moment, many distributions make a symlink for the iptables command that points to a special nftables binary.

For example, in Linux Mint Debian Edition 7, the iptables command is a symbolic link to the xtables-nft-multi utility.

You can determine the final symlink target with this scary command:

BASH
current="$(command -v iptables)"; while [[ -L "$current" ]]; do target=$(readlink "$current"); echo "$current -> $target"; if [[ "$target" == /* ]]; then current="$target"; else current="$(dirname "$current")/$target"; fi; done; echo "Final: $current"
Click to expand and view more

This is also done so as not to break the operation of many network programs in Linux that still use iptables. For example, the ufw firewall or Docker:

A bit about nftables specifics

Most often, when reading materials about nftables, you will see the following diagram:

It visualizes the operation of the Linux firewall, Netfilter, and the paths packets take through it.

Today, there is no need to learn the entire diagram; we will talk only about the IPv4 and IPv6 families and, respectively, the IP layer and Application layer. This includes the following filtering hooks (entry points, see the diagram): prerouting, input, output, forward, postrouting.

Before starting the configuration, you need to understand the main entities of a modern Linux firewall:

And the key differences and innovations of nftables compared to iptables:

I think I have covered everything I wanted to say. The topic of firewalls in Linux is very large. It is difficult to cover everything at once. Let’s not bloat the article; move on to the script and its description.

nftables configuration script

The purpose of writing such a script is to have a universal tool at hand for quickly configuring a local Linux firewall, with the ability to easily customize the filtering rule set for different needs. This set is built according to the principle “Everything is forbidden except what is explicitly allowed”. It is well suited for protecting hosts that have a public IP with services running on it.

The script defines the NFT_RULES array, which contains rule commands for nftables. If necessary, edit/change/add the required rules (following the syntax and escaping double quotes inside rules).

When started, the script creates a temporary file with a rule set in nft format (its command variant), and then works with that file: checks syntax/applies rules. The script also provides flags for backing up the current configuration file (specified in the NFT_CONFIG variable) and saving the current rule set (nft -s list table inet filter) to this configuration file (overwriting it).

The script is run as root with one or more parameters:

The script is intended to automate full firewall configuration: creating tables, address sets, chains, and filtering rules, including handling exceptions for container, virtualization, and VPN interfaces, plus a bit of NAT (port forwarding and masquerading) as an example.

BASH
nvim ./nftables.sh
Click to expand and view more
nftables.sh
  1#!/usr/bin/env bash
  2
  3# Script security parameters
  4set -Eeuo pipefail
  5
  6# Explicit PATH definition
  7export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
  8
  9# =============================================================
 10# ========== BEGINNING OF USER CONFIGURATION SECTION ==========
 11
 12NFT="$(command -v nft)"
 13NFT_CONFIG="/etc/nftables.conf"
 14NFT_RULES=(
 15# =====================
 16#     TABLES & SETS
 17# =====================
 18# "flush ruleset"
 19"add table inet filter"
 20"flush table inet filter"
 21"add table inet nat"
 22"flush table inet nat"
 23
 24"add set inet filter lan4 { type ipv4_addr; flags interval; elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 } }"
 25"add set inet filter lan6 { type ipv6_addr; flags interval; elements = { fd00::/8, fe80::/10 } }"
 26"add set inet filter trusted { type ipv4_addr; elements = { 123.34.56.78 } }"
 27
 28# =====================
 29#        CHAINS
 30# =====================
 31"add chain inet filter input { type filter hook input priority 0; policy drop; }"
 32"add chain inet filter forward { type filter hook forward priority 50; policy drop; }"
 33"add chain inet filter output { type filter hook output priority -200; policy accept; }"
 34"add chain inet nat prerouting { type nat hook prerouting priority dstnat; policy accept; }"
 35"add chain inet nat postrouting { type nat hook postrouting priority srcnat; policy accept; }"
 36
 37"add chain inet filter input_wan"
 38"add chain inet filter input_lan"
 39"add chain inet filter log_drop"
 40
 41# =====================
 42#      INPUT BASE
 43# =====================
 44"add rule inet filter input ct state invalid drop comment \"Drop invalid connections\""
 45"add rule inet filter input ct state { established, related } accept comment \"Allow established connections\""
 46"add rule inet filter input iif lo accept comment \"Allow loopback\""
 47"add rule inet filter input ip saddr @trusted accept comment \"Allow trusted IPs\""
 48
 49# ICMP
 50"add rule inet filter input meta l4proto icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept comment \"ICMP rate limited\""
 51"add rule inet filter input meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, mld2-listener-report } accept comment \"Necessary IPv6 ICMP\""
 52
 53# UDP traceroute
 54"add rule inet filter input_wan udp dport 33434-33534 reject comment \"Allow UDP traceroute\""
 55
 56# LAN/WAN
 57"add rule inet filter input ip saddr @lan4 jump input_lan comment \"LAN IPv4 processing\""
 58"add rule inet filter input ip6 saddr @lan6 jump input_lan comment \"LAN IPv6 processing\""
 59"add rule inet filter input ip saddr != @lan4 jump input_wan comment \"WAN IPv4 processing\""
 60"add rule inet filter input ip6 saddr != @lan6 jump input_wan comment \"WAN IPv6 processing\""
 61"add rule inet filter input jump log_drop comment \"Default drop\""
 62
 63# =====================
 64#       INPUT LAN
 65# =====================
 66# Allow all
 67"add rule inet filter input_lan meta l4proto { tcp, udp } accept comment \"Allow all TCP/UDP from LAN\""
 68# Or allow selected and drop others
 69# "add rule inet filter input_lan tcp dport { 80, 443 } accept comment \"Allowed TCP ports from LAN\""
 70# "add rule inet filter input_lan udp dport { 53, 123 } accept comment \"Allowed UDP ports from LAN\""
 71# "add rule inet filter input_lan ct state new jump log_drop comment \"Drop all from LAN with log\""
 72
 73# =====================
 74#       INPUT WAN
 75# =====================
 76"add rule inet filter input_wan tcp dport 22 accept comment \"Allow SSH from WAN\"" 
 77"add rule inet filter input_wan tcp dport { 80, 443 } accept comment \"Allowed TCP ports from WAN\""
 78"add rule inet filter input_wan udp dport { 53, 123 } accept comment \"Allowed UDP ports from WAN\""
 79"add rule inet filter input_wan iifname \"eth0\" tcp dport 443 accept comment \"DNAT: 443->43443\""
 80"add rule inet filter input_wan iifname \"eth0\" ct status dnat tcp dport 43443 accept comment \"DNAT: 443->43443\""
 81"add rule inet filter input_wan ct state new jump log_drop comment \"Drop all from WAN\""
 82
 83# =====================
 84#        FORWARD
 85# =====================
 86"add rule inet filter forward ct state established,related accept"
 87"add rule inet filter forward iifname \"cni*\" accept comment \"Allow K8s forward in\""
 88"add rule inet filter forward oifname \"cni*\" accept comment \"Allow K8s forward out\""
 89"add rule inet filter forward iifname \"flannel.*\" accept comment \"Allow K8s forward in\""
 90"add rule inet filter forward oifname \"flannel.*\" accept comment \"Allow K8s forward out\""
 91"add rule inet filter forward iifname \"vxlan.calico\" accept comment \"Allow K8s forward in\""
 92"add rule inet filter forward oifname \"vxlan.calico\" accept comment \"Allow K8s forward out\""
 93"add rule inet filter forward iifname \"br-*\" accept comment \"Allow Docker forward in\""
 94"add rule inet filter forward oifname \"br-*\" accept comment \"Allow Docker forward out\""
 95"add rule inet filter forward iifname \"virbr*\" accept comment \"Allow VMs forward in\""
 96"add rule inet filter forward oifname \"virbr*\" accept comment \"Allow VMs forward out\""
 97"add rule inet filter forward iifname \"tun*\" accept comment \"Allow OC forward in\""
 98"add rule inet filter forward oifname \"tun*\" accept comment \"Allow OC forward out\""
 99"add rule inet filter forward iifname \"wg*\" accept comment \"Allow WG forward in\""
100"add rule inet filter forward oifname \"wg*\" accept comment \"Allow WG forward out\""
101"add rule inet filter forward ct state new jump log_drop comment \"Drop all forward\""
102
103# =====================
104#      LOG & DROP
105# =====================
106"add rule inet filter log_drop limit rate 5/second log prefix \"NFT-DROP: \" flags all counter comment \"Drop logging\""
107# "add rule inet filter input meta l4proto tcp reject with tcp reset comment \"Reject TCP\""
108# "add rule inet filter input meta l4proto udp reject comment \"Reject UDP\""
109# "add rule inet filter input counter reject with icmpx type port-unreachable comment \"Reject other protocols\""
110# "add rule inet filter input pkttype host limit rate 5/second counter reject with icmpx type admin-prohibited comment \"Protection from port scanning\""
111"add rule inet filter log_drop drop comment \"Drop all\""
112
113# =====================
114#         NAT
115# =====================
116"add rule inet nat prerouting iifname \"eth0\" tcp dport 443 redirect to 43443 comment \"DNAT: 443->43443\""
117# "add rule inet nat prerouting iifname \"eth0\" tcp dport 443 dnat to :43443 comment \"DNAT:  443->43443 \""
118# "add rule inet nat postrouting oifname != lo masquerade comment \"SNAT: NAT processing for all\""
119# "add rule inet nat postrouting oifname \"eth0\" masquerade comment \"SNAT: NAT procesing for eth0\""
120"add rule inet nat postrouting oifname \"tun*\" masquerade comment \"SNAT: NAT procesing for VPN\""
121)
122
123# ========== END OF USER CONFIGURATION SECTION ==========
124# =======================================================
125
126SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd -P)
127SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
128SCRIPT_TMP=$(mktemp "${SCRIPT_DIR}"/"${SCRIPT_NAME}"_XXXXXXXX)
129
130cleanup() {
131    trap - SIGINT SIGTERM SIGHUP SIGQUIT ERR EXIT
132
133    rm -f "$SCRIPT_TMP"
134}
135
136trap cleanup SIGINT SIGTERM SIGHUP SIGQUIT ERR EXIT
137
138
139usage() {
140    cat <<EOF
141Usage: $SCRIPT_NAME OPTIONS
142
143Description:
144  Script to configure the firewall via nftables.
145
146Options:
147  -h, --help            Show this help message and exit
148  -a, --apply           *Apply new rules from this script
149  -s, --save            *Save current ruleset to /etc/nftables.conf
150  -b, --backup          *Create a backup of /etc/nftables.conf
151  -c, --check           *Dry-run mode (check syntax without applying changes)
152
153Note:
154  One of the * options is required.
155
156Examples:
157  nftables.sh -a
158  nftables.sh --check --apply
159  nftables.sh --apply --save --backup
160EOF
161}
162
163
164parse_params() {
165    APPLY=0 SAVE=0 BACKUP=0 CHECK=0
166
167    while [[ $# -gt 0 ]]; do
168      case $1 in
169        -h|--help) usage; exit 0 ;;
170        -a|--apply) APPLY=1 ;;
171        -s|--save) SAVE=1 ;;
172        -b|--backup) BACKUP=1 ;;
173        -c|--check) CHECK=1 ;;
174        *) usage; exit 1 ;;
175      esac
176      shift
177    done
178
179    (( APPLY || SAVE || BACKUP || CHECK )) || { usage; exit 1; }
180
181    # echo "apply=$APPLY save=$SAVE backup=$BACKUP"
182}
183
184
185save_current_rules() {
186    echo -e "#!${NFT} -f\n\nflush ruleset\n\n$($NFT -s list table inet filter)" > "$NFT_CONFIG"
187    chmod 644 "$NFT_CONFIG"
188	echo "Rules successfully saved at $NFT_CONFIG"
189}
190
191
192backup_current_config() {
193    local datetime
194    datetime="$(date +%Y-%m-%d_%H-%M-%S)"
195
196	if [[ -f "$NFT_CONFIG" ]]; then
197		cp "${NFT_CONFIG}"{,_"${datetime}"}
198		echo "Backup created: ${NFT_CONFIG}_${datetime}"
199	fi
200}
201
202
203# =====================
204#   Main script flow
205# =====================
206parse_params "$@"
207
208# Check for root privileges
209if [[ $EUID -ne 0 ]]; then
210    echo "Please run as root"
211    exit 1
212fi
213
214if (( BACKUP )); then backup_current_config; fi
215
216if (( CHECK )); then
217    for rule in "${NFT_RULES[@]}"; do
218        echo "$rule" >> "$SCRIPT_TMP"
219    done
220
221    "$NFT" -c -f "$SCRIPT_TMP"|| { echo "Syntax - error"; exit 1; }
222    cat "$SCRIPT_TMP"
223    echo -e "-----------\nSyntax - ok"
224fi
225
226if (( APPLY )); then
227    for rule in "${NFT_RULES[@]}"; do
228        echo "$rule" >> "$SCRIPT_TMP"
229    done
230
231    if ! (( CHECK )); then
232        "$NFT" -c -f "$SCRIPT_TMP" || { echo "Syntax - error"; exit 1; }
233    fi
234
235    "$NFT" -f "$SCRIPT_TMP" && echo "Rules applied"
236fi
237
238if (( SAVE )); then save_current_rules; fi
Click to expand and view more

Rule breakdown in order:

Tables and sets

BASH
add table inet filter
Click to expand and view more

Creates the filter table for packet filtering. The table name is arbitrary.

BASH
flush table inet filter
Click to expand and view more

Flushes the filter table before adding new rules.

BASH
add table inet nat
Click to expand and view more

Creates the nat table for NAT translation.

BASH
flush table inet nat
Click to expand and view more

Flushes the nat table.

BASH
add set inet filter lan4 { type ipv4_addr; flags interval; elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 } }
Click to expand and view more

Creates a set of LAN IPv4 networks.

BASH
add set inet filter lan6 { type ipv6_addr; flags interval; elements = { fd00::/8, fe80::/10 } }
Click to expand and view more

Creates a set of LAN IPv6 networks.

BASH
add set inet filter trusted { type ipv4_addr; elements = { 123.34.56.78 } }
Click to expand and view more

Defines a list of trusted IP addresses.

Chains

BASH
add chain inet filter input { type filter hook input priority 0; policy drop; }
Click to expand and view more

Creates the input chain with the default DROP policy.

BASH
add chain inet filter forward { type filter hook forward priority 50; policy drop; }
Click to expand and view more

Creates the forward chain with the DROP policy.

BASH
add chain inet filter output { type filter hook output priority -200; policy accept; }
Click to expand and view more

Creates the output chain, allowing outgoing connections.

BASH
add chain inet nat prerouting { type nat hook prerouting priority dstnat; policy accept; }
Click to expand and view more

Creates the prerouting chain for incoming NAT (DNAT).

BASH
add chain inet nat postrouting { type nat hook postrouting priority srcnat; policy accept; }
Click to expand and view more

Creates the postrouting chain for outgoing NAT (SNAT).

BASH
add chain inet filter input_wan
add chain inet filter input_lan
add chain inet filter log_drop
Click to expand and view more

Creates user-defined chains for traffic separation and logging.

Basic INPUT rules

BASH
add rule inet filter input ct state invalid drop
Click to expand and view more

Drops invalid connections.

BASH
add rule inet filter input ct state { established, related } accept
Click to expand and view more

Allows established and related connections (the contrack mechanism).

BASH
add rule inet filter input iif lo accept
Click to expand and view more

Allows local traffic (loopback).

BASH
add rule inet filter input ip saddr @trusted accept
Click to expand and view more

Immediately allows access for trusted IPs.

ICMP

BASH
add rule inet filter input meta l4proto icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept
Click to expand and view more

Allows the main ICMP IPv4 types with rate limiting (minor protection against flood and DoS attacks).

BASH
add rule inet filter input meta l4proto ipv6-icmp icmpv6 type { ... } accept
Click to expand and view more

Allows required ICMPv6 packets (ND, MLD, and others).

Thanks to user Comm from the Raven chat for the ICMP block.

Traceroute

BASH
add rule inet filter input_wan udp dport 33434-33534 reject
Click to expand and view more

Allows UDP traceroute (through reject notifications).

LAN/WAN separation

BASH
add rule inet filter input ip saddr @lan4 jump input_lan
add rule inet filter input ip6 saddr @lan6 jump input_lan
Click to expand and view more

Passes traffic from LAN to the input_lan chain.

BASH
add rule inet filter input ip saddr != @lan4 jump input_wan
add rule inet filter input ip6 saddr != @lan6 jump input_wan
Click to expand and view more

Passes external traffic to the input_wan chain.

BASH
add rule inet filter input jump log_drop
Click to expand and view more

Passes remaining traffic to logging and blocking.

INPUT LAN chain

BASH
add rule inet filter input_lan meta l4proto { tcp, udp } accept
Click to expand and view more

Allows all TCP/UDP traffic from LAN.

INPUT WAN chain

BASH
add rule inet filter input_wan tcp dport 22 limit rate 20/minute accept
Click to expand and view more

Allows SSH.

BASH
add rule inet filter input_wan tcp dport { 80, 443 } accept
Click to expand and view more

Allows HTTP(S).

BASH
add rule inet filter input_wan udp dport { 53, 123 } accept
Click to expand and view more

Allows DNS and NTP.

BASH
add rule inet filter input_wan iifname "eth0" tcp dport 443 accept
add rule inet filter input_wan iifname "eth0" ct status dnat tcp dport 43443 accept
Click to expand and view more

Handles DNAT redirection from port 443 to port 43443 in the input hook. For correct operation, it also requires a rule in the forward hook (see below).

BASH
add rule inet filter input_wan ct state new jump logdrop
Click to expand and view more

Blocks new unauthorized connections from WAN.

FORWARD chain

BASH
add rule inet filter forward ct state established,related accept
Click to expand and view more

Allows established connections.

BASH
add rule inet filter forward iifname "cni*" accept
add rule inet filter forward oifname "cni*" accept
Click to expand and view more

Allows routing for Kubernetes.

BASH
add rule inet filter forward iifname "flannel.*" accept
add rule inet filter forward oifname "flannel.*" accept
Click to expand and view more

Allows traffic through Flannel.

BASH
add rule inet filter forward iifname "vxlan.calico" accept
add rule inet filter forward oifname "vxlan.calico" accept
Click to expand and view more

Allows Calico traffic.

BASH
add rule inet filter forward iifname "br-*" accept
add rule inet filter forward oifname "br-*" accept
Click to expand and view more

Allows Docker networks.

BASH
add rule inet filter forward iifname "virbr*" accept
add rule inet filter forward oifname "virbr*" accept
Click to expand and view more

Allows virtual machine traffic.

BASH
add rule inet filter forward iifname "tun*" accept
add rule inet filter forward oifname "tun*" accept

add rule inet filter forward iifname "wg*" accept
add rule inet filter forward oifname "wg*" accept
Click to expand and view more

Allows VPN traffic (OpenConnect, WireGuard).

BASH
add rule inet filter forward ct state new jump log_drop
Click to expand and view more

Blocks unknown forwarding.

Logging and drop

BASH
add rule inet filter log_drop limit rate 5/second log prefix "NFT-DROP:" flags all counter
Click to expand and view more

Enables logging of dropped packets with the “NFT-DROP:” prefix, limits logging rate to 5 packets per second, and counts dropped packets. flags all specifies logging all packet flags.

BASH
add rule inet filter log_drop drop
Click to expand and view more

Finally blocks all remaining traffic.

NAT

BASH
add rule inet nat prerouting iifname "eth0" tcp dport 443 redirect to 43443
Click to expand and view more

Redirects port 443 to 43443 (DNAT) and requires an allow rule in the input hook to work (see above).

BASH
add rule inet nat postrouting oifname "tun*" masquerade
Click to expand and view more

Performs SNAT (masquerading) for VPN interfaces.

Short summary of the rules:

  1. The script creates isolated filter and nat tables, fully flushing old rules;
  2. Incoming connections are allowed only from trusted networks, LAN, or for specific WAN ports;
  3. A strict default policy is applied: drop for incoming and forwarding chains;
  4. ICMP limiting is implemented;
  5. Routing is allowed for Docker, KVM, Kubernetes, and VPN (WireGuard, OpenConnect);
  6. All unauthorized packets are logged and blocked;
  7. NAT is configured for redirection and VPN masquerading (tun* interfaces).

Demonstrating how nftables.sh works

Make the script executable:

BASH
chmod +x ./nftables.sh

./nftables.sh --help
Click to expand and view more

Check rule syntax:

BASH
sudo ./nftables.sh -c
Click to expand and view more

As responsible users, make a backup of the current configuration:

BASH
sudo ./nftables.sh -b
Click to expand and view more

Apply the rules:

BASH
sudo ./nftables.sh -a
Click to expand and view more

View the list of all nftables rules:

BASH
sudo nft list ruleset
Click to expand and view more

Check the current session to make sure our SSH was not dropped:

BASH
w

date
Click to expand and view more

Now open a neighboring tab and check that we can connect to the server:

So far, everything is OK. Continue checking the firewall operation.

ICMP - run from the client machine:

BASH
ping -c3 nftables.r4ven.me
Click to expand and view more

Trace (UDP):

BASH
traceroute nftables.r4ven.me
Click to expand and view more

Everything passes.

Now check blocking ICMP above 10 packets per second.

The rule set includes one rule for logging: it writes events to the system journal.

On the server, view the drop log filtered by ICMP:

BASH
sudo journalctl -k -f -g 'NFT-DROP' -g 'ICMP'
Click to expand and view more

On the client, start 20 parallel ping processes:

BASH
seq 20 | xargs -n1 -P20 sh -c 'ping -c5 nftables.r4ven.me'
Click to expand and view more

Drop messages will be visible in the log:

And packet loss will be visible on the client:

For the following tests, install the socat utility for working with network connections on the client and on the server:

BASH
sudo apt update && sudo apt install -y socat
Click to expand and view more

Now, with its help, on the host with the configured firewall, start a TCP server on port 443, access to which we allowed when configuring the firewall.

On the server, start a process that sends pong when it receives a TCP packet:

BASH
sudo socat -v TCP-LISTEN:443,fork SYSTEM:"echo 'pong'"
Click to expand and view more

Send a test request from the client:

BASH
echo "ping" | socat - TCP:nftables.r4ven.me:443
Click to expand and view more

It works. Now check some unopened port.

On the server:

BASH
sudo socat -v TCP-LISTEN:444,fork SYSTEM:"echo 'pong'"
Click to expand and view more

On the client:

BASH
echo "ping" | socat - TCP:nftables.r4ven.me:444
Click to expand and view more

As we can see, the connection is not established.

View the nftables drop log:

BASH
sudo journalctl -k -f -g 'NFT-DROP' -g '12.34.56.78'
Click to expand and view more

Where 12.34.56.78 is your external IP from which you perform the client connection.

Great, the firewall works as expected😅.

Now check port forwarding. In our rules, port 443 was redirected in the prerouting hook to port 43443 (and there is an allow rule in the input hook).

On the server, listen on TCP port 43443:

BASH
sudo socat -v TCP-LISTEN:43443,fork SYSTEM:"echo 'pong'"
Click to expand and view more

On the client, send a request to TCP port 443:

BASH
echo "ping" | socat - TCP:nftables.r4ven.me:443
Click to expand and view more

It works!

Check UDP operation in a similar way. In our rules, we opened port 123.

Run on the server:

BASH
sudo socat -v UDP-RECVFROM:123,fork SYSTEM:"echo 'pong'"
Click to expand and view more

And on the client:

BASH
echo "ping" | socat - UDP:nftables.r4ven.me:123
Click to expand and view more

Everything is good. Also check drop for unauthorized UDP:

On the server:

BASH
sudo socat -v UDP-RECVFROM:12345,fork SYSTEM:"echo 'pong'"
Click to expand and view more

On the client:

BASH
echo "ping" | socat - UDP:nftables.r4ven.me:12345
Click to expand and view more

Empty. But when viewing the journal:

BASH
sudo journalctl -k -f -g 'NFT-DROP' -g '12345'
Click to expand and view more

We see the corresponding records:

After making sure that the firewall configuration is correct, save the rules:

BASH
sudo ./nftables.sh -s

cat /etc/nftables.conf
Click to expand and view more

Configuring nftables autostart at OS startup

The nftables package includes a Systemd service unit, which essentially runs the command to apply rules from the main configuration file: nft -f /etc/nftables.conf (in deb-based distributions; in rpm-based ones, /etc/sysconfig/nftables.conf).

Enable autostart:

BASH
sudo systemctl enable --now nftables

sudo systemctl status nftables

sudo nft list ruleset
Click to expand and view more

For complete peace of mind and to check firewall operation, it is recommended to perform a preventive server reboot:

BASH
sudo reboot
Click to expand and view more

Useful nftables commands

BASH
# Show the entire ruleset
sudo nft list ruleset

# Show all tables
sudo nft list tables

# Show the contents of the filter table
sudo nft list table inet filter

# Show the input chain of the filter table
sudo nft list chain inet filter input

# Show rules with handle - rule sequence numbers (for replacement/deletion)
sudo nft -a list ruleset

# Flush all rules
sudo nft flush ruleset

# Flush only the filter table (CAUTION! Does not delete default policies)
sudo nft flush table inet filter

# Delete the filter table
sudo nft delete table inet filter

# Add a rule (example)
sudo nft add rule inet filter input tcp dport 22 accept

# Show a chain with handle
sudo nft -a list chain inet filter input

# Delete a rule by handle
sudo nft delete rule inet filter input handle 15

# Replace a rule by handle
sudo nft replace rule inet filter input handle 15 tcp dport 2222 accept

# Load config from a file
sudo nft -f /etc/nftables.conf

# Check syntax without applying
sudo nft -c -f /etc/nftables.conf

# Save current rules to the config
sudo nft -s list ruleset > /etc/nftables.conf

# Track changes in real time
sudo nft monitor

# Trace packet traversal
sudo nft monitor trace

# Detailed debug output on load
sudo nft --debug=netlink -f myrules.nft

# View the nftables journal
sudo journalctl -k -f -g 'NFT-DROP'
Click to expand and view more

Afterword

I tested this script on several local and public (with an external IP) servers. So far, the flight is normal, but there may still be shortcomings somewhere (I am not a network engineer😑). Let me remind you once again that you perform all actions at your own risk. If you still have questions on the topic, you can ask them in our Raven chat. In my free time, I will try to answer you👨‍💻.

Thank you for reading!

Materials Used

Copyright Notice

Author: Ivan Cherniy

Link: https://r4ven.me/en/networking/bazovaya-nastroyka-nftables-v-linux-s-pomoshchyu-bash-skripta/

License: CC BY-NC-SA 4.0

Blog materials may be used with attribution to the author and source, for non-commercial purposes, and under the same license.

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut