Common Ports
Port 21: Used for FTP (File Transfer Protocol).
Port 22: Used for SFTP (Secure File Transfer Protocol, not Simple File Trans. Protocol)
Port 22: Used for SSH (Secure Shell connections).
Port 25: Used for SMTP (Simple Mail Transfer Protocol).
Port 53: Used for DNS (Domain Name System).
Port 80: Used for HTTP (non-secure web traffic).
Port 143: Used for IMAP (Internet Message Access Protocol).
Port 161: Used for SNMP (Simple Network Management Protocol).
Port 389: Used for LDAP (Lightweight Directory Access Protocol).
Port 443: Used for the default for HTTPS.
Port 3000: Used for node.js
Port 3306: Used for MySQL (classic) and MariaDB
Port 3389: Used for RDP (Remote Desktop Protocol)
Port 33060: used for the newer MySQL X Protocol
Port 33062: used for MySQL administrative connections (classic)
Creating a firewall using nftables
Here is information from Chapters 21 to of "Firewalls (iptables, nftables, pfsnse) for Educators: A Complete Guide to Teaching Perimeter Security Step by Step" by Deigo S. (2025).
The firewall rules can lock up the system preventing access, including access using a keyboard plugged in to the computer with USB.
Essential Traffic
Allow established connections
Allow localhost
Allow SSH
Firewall rules are grouped together in Chains. Chains are grouped together in Tables.
Sets are lists surrounded by parentheses.
Here is a nftables object:
table inet filter
chain input
chain output
chain forward
input, output, forward chains attach rules to a kernel hook
Installation and status
Using bash:
sudo apt install nftables #Debian/Ubuntu
sudo systemctl enable nftables #Enable on boot
sudo systemctl start nftables #Start the service
sudo nft list ruleset
If no configuration, ".conf":
nftables v1.0.2 (Luna Moon)
Create a Table with the Three Chains
sudo nft add table inet myfirewall
sudo nft add chain inet myfirewall input {type filter hook input priority 0; policy drop;}
sudo nft add chain inet myfirewall output {type filter hook output priority 0; policy accept;}
sudo nft add chain inet myfirewall forward {type filter hook forward priority 0; policy drop;}
Add Rules
#Allow established connections
sudo nft add rule inet myfirewall input ct state established,related accept
#Allow localhost (loopback)
sudo nft add rule inet myfirewall input iif lo accept
#Allow SSH
sudo nft add rule inet myfirewall input tcp dport 22 ct state new accept
#Allow ping
sudo nft add rule inet myfirewall input icomp type echo-request accept
Save Configuration
#Save rules
sudo nft list ruleset > /etc/nftables.conf
#Load on startup. The file /etc/nftables.conf will load automatically
sudo systemctl enable nftables
Examples
#Blocking IPs
nft add set inet filter blacklist {type ipv4_addr; flags interval; }
nft add element inet filter blacklist { 192.168.1.10, 192.168.1.11, 192.168.1.12 }
nft add rule inet filter input ip saddr @blacklist drop
#Port Forwarding
nft add table ip nat
nft add chain ip nat prerouting {type nat hook prerouting priority 0; }
nft add rule ip nat prerouting tcp dport 8080 dnat to 192.168.1.100:80
Table, Chain, Policy, and Rules Concepts
Table is the main container
nft add table inet filter
# inet is either IPv4 and IPv6 rules.
# filter is the firewall filtering according to rules
input is incoming data to the local system.
output is outgoing data from the system.
forward is data traffic passing through the system such as a router and a firewall
nft add chain inet filter input { type filter hook input priority 0; policy drop; }
Policy is the behavior: accept or drop traffic
nft add rule inet filter input ip protocol icmp accept
#accepts incoming ICMP (like pings)
Sets allow multiple ports, addresses, or ranges in one list.
nft add set inet filter blacklist {type ipv4_addr; }
nft add element inet filter blacklist { 192.168.1.10, 192.168.1.20 }
nft add rule inet filter input ip saddr @blacklist drop
Three Step Syntax
nft add table inet mytable
nft add chain inet mytable mychain { type filter hook input priority 0; policy drop; }
nft add rule inet mytable mychain tcp dport 22 accept
(a) creates a table named mytable
(b) creates a chain in mytable named mychain
(c) drops all traffic
(d) adds a rule to accept port 22 (SSH) traffic
Component Explanation
inet is both IPv4 and IPv6
ip is only IPv4
Filtering 0
NAT postrouting 100
ip saddr is source IP address
ip daddr is destination IP address
tcp dport
udp sport
ct state is connection state, established, new
Complete Example
nft flush ruleset
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0; policy drop; }
nft add rule inet filter input ct state established, related accept
nft add rule inet filter input tcp dport 22 accept
nft add rule inet filter input tcp dport 80 accept
nft add rule inet filter input tcp dport 443 accept
#basic firewall to allow incoming traffic for SSH, HTTP, HTTPS, and drops everything else
Looking at the Rule Set
nft list ruleset
Console output:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
tcp dport 22 accept
tcp dport 80 accept
tcp dport 443 accept
}
}
Syntax
Creating Tables
nft add table
example:
nft add table inet myfirewall #myfirewall created in the inet family, both IPv4 and IPv6.
Chain Configuration
nft add chain <family> <table> <chain> {
type <type>;
hook <hook>;
priority <number>;
policy <accept|drop>;
}
nft add chain inet myfirewall input {
type filter hook input priority 0;
policy drop;
}
Adding Rules
have an existing chain, a condition which is what traffic to evaluate, an action which is what to do with the traffic.
nft add rule ≶family> ≶table> ≶chain> ≶condition> ≶action>
example:
nft add rule inet myfirewall input tcp dport 22 accept #allow SSH traffic
example:
nft add rule inet myfirewall input ct state established,related accept
# essential to not break valid connections
example:
nft add set inet myfirewall blacklist {type ipv4_addr;}
nft add element inet myfirewall blacklist { 192.168.1.100,192.168.1.101 }
nft add rule inet myfirewall input ip saddr @blacklist drop
#blocks a range of IPs
Basic Firewall with Allow, Block, and Log
b) Create the table
c) Create the input chain
d) Add rules
nft flush ruleset #clear previous rules
nft add table inet myfirewall
nft add chain inet myfirewall input {
type filter hook input priority 0;
policy drop;
}
nft add rule inet myfirewall input ct state established,related accept
nft add rule inet myfirewall input tcp dport 22 accept
nft add rule inet myfirewall input tcp dport 80 accept
nft add rule inet myfirewall input tcp dport 443 accept
nft add rule inet myfirewall input log prefix "DROP_LOG:" flags all
# Log and drop the rest
Save the rule set:
nft list ruleset > /etc/nftables.conf
Enable and load rule set:
systemctl enable nftables
systemctl start nftables
Best Practices
Use the inet family
Separate traffic by chains (input, output, forward)
Log what gets blocked
Save copies in .nft files for versioning with Git and reuse
ssh_web.nft
blacklist.nft
rule_explaination
nft_basic_config.md
long examples
Public Web Server with Restricted SSH Access
A server in the DMZ hosts a website accessible from the internet, but SSH access should only be allowed from the administrator's local network (192.168.1.0/24)
nft add table inet webdmz
nft add chain inet webdmz input { type filter hook input priority 0; policy drop; }
#Accept established traffic
nft add rule inet webdmz input ct state established,related accept
#Allow HTTP/HTTPS
nft add rule inet webdmz input tcp dport {80,443} accept #a set of ports
Allow SSH only from admin network
nft add rule inet webdmz input ip saddr 192.168.1.0/24 tcp dport 22 accept
#log and drop the rest
nft add rule inet webdmz input log prefix "DROP_WEB:" flags all
Student Desktop Firewall
A student workstation must be able to browse the internet, use SSH to class servers, and block any unsolicited incoming connections.
nft add table inet desktop
nft add chain inet desktop input {type filter hook input priority 0; policy drop; }
nft add chain inet desktop output { type filter hook output priority 0; policy accept; }
#Allow established or related
nft add rule inet desktop input ct state established,related accept
#Block the rest with logging
nft add rule inet desktop input log prefix "DROP_DESKTOP:" flags all
#no explicit rules are needed for output, as the policy is set to "accept." This promotes normal browsing and usage.
Router with NAT and Port Forwarding Scenario:
Perform NAT for the internal network (192.168.10.0/24)
Redirect external connections to port 8080 to an internal server at 192.168.10.10:80.
Log NAT packets.
nft add table ip nat
#Prerouting and postrouting chains
nft add chain ip nat prerouting { type nat hook prerouting priority -100; }
nft add chain ip nat postrouting { type nat hook postrouting priority 100; }
#Redirect external port to internal server
nft add rule ip nat prerouting iif "eth0" tcp dport 8080 dnat to 192.168.10.10:80
#NAT (masquerade) for internet outgoing
nft add rule ip nat postrouting oif "eth0" ip saddr 192.168.10.0/24 masquerade
Segmentation Between Student Groups
A test machine has several interfaces for different working groups: group1, group2, and the internet. The groups should not communicate with each other, but both must have access to the internet.
Allow access to the internet from both groups.
Allow responses from the internet to both groups.
nft add table inet campusfw
nft add chain inet campusfw forward { type filter hook forward priority 0; policy drop; }
#Allow responses
nft add rule inet campusfw forward ct state established, related accept
#Block traffic between groups
nft add rule inet campusfw forward iif "group1 oif "group2" drop
nft add rule inet campusfw forward iif "group2 oif "group1" drop
#Allow traffic to the internet
nft add rule inet campusfw forward iif "group1" oif "internet" accept
nft add rule inet campusfw forward iif "group2" oif "internet" accept
Security Policies
The policies should define what is allowed, is blocked, and what happens to transmissions not explicitly defined.
Least Privilege: allow only what is required.
Network Segmentation: isolate zones (DMZ, users, administration).
Event Logging: log rejected accesses for review
State Handling: accept established and related connections
Example
Web server with SSH access only from a specific IP.
# Create table and chain
nft add table inet servidor
nft add chain inet servidor input { type filter hook input priority 0; policy drop; }
# Allow established connections
nft add rule inet servidor input ct state established,related accept
# HTTP and HTTPS
nft add rule inet servidor input tcp dport {80,443} accept
#SSH from authorized IP
nft add rule inet servidor input ip saddr 192.168.1.100 tcp dport 22 accept
#Log everything else
nft add rule inet servidor input log prefix "Server Block:" flags all
Common Types of Policies
Allow only necessary services and use logging
Accept output, filter input
Forward filtering, and explicit NAT
Strict whitelist, no input, only outgoing connections
Each group must have access to the internet, but not to each other.
Only teachers can access via SSH.
Simplified Solution
# Table and chain
nft add table inet campus
nft add chain inet camput forward { type filter hook forward priority 0; policy drop; }
# Established and related
nft add rule inet camput forward ct state established, related accept
# Internet access
nft add rule inet campus forward iifname "vlan10" oifname "eth0" accept
nft add rule inet campus forward iifname "vlan20" oifname "eth0" accept
# Block between VLANs
nft add rule inet campus forward iifname "vlan10" oifname "vlan20" drop
nft add rule inet camput forward iifname "vlan20" oifname "vlan10" drop
# Allow SSH to servers only from the teacher VLAN
nft add rule inet camput forward iifname "vlan30" oifname "srv_net" tcp dport 22 accept
Design
Define which services to use (DNS, HTTPS,ping, SSH, etc)
Blocks everything not specified
Tests the rules with tools (ping, curl, nmap, etc)
Documents results and fexed errors
Use clear policies that are easier to maintain
Keep scripts under version control (git) and use comments with each rule
Using systemd to Automate Scripts
Use systemd to apply at boot
Manage securely with a controlled source
File structure
interfaces.nft #Rules per interface
services.nft #Rules per ports/services
logging.nft #Logging rules
apply-firewall.sh #Automated load script
Example
file: "apply-firewall.sh"
#!/bin/bash
# File: /etc/nftables/scripts/apply-firewall.sh
echo "Loading nftables rules..."
nft flush ruleset
nft -f /etc/nftables/firewall.nft
if [ $? -eq 0 ]; then
echo "checked Rules loaded successfully."
else
echo " Error applying rules."
exit 1
fi
Make the file executable:
chmod +x /etc/nftables/scripts/apply-firewall.sh
file for modular rules
#!/usr/sbin/nft -f
# /etc/nftables/firewall.nft
table inet myfirewall {
include "/etc/nftables/interfaces.nft"
include "/etc/nftables/services.nft"
include "/etc/nftables/logging.nft"
}
file for services
# /etc/nftables/services.nft
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iifname "lo" accept
tcp dport 22 accept #SSH
tcp dport 80 accept #HTTP
tcp dport 443 accept #HTTPS
}
Use with systemd
Avoid locking yourself out, especially in network or remote access environments.
Recommended for most environments
native nftables.service check if system already has the .conf file
/etc/nftables.conf
Review the original file permissions
Copy main rules to the .conf file
cp /etc/nftables/firewall.nft /etc/nftables.conf
Check permissions
Enable the service
systemctl enable nftables
systemctl start nftables
Create a Custom Service
new file: /etc/systemd/system/custom-nftables.service
[Unit]
Description=Custom Firewall with nftables
After=network.target
[Service]
Type-oneshot
ExecStart=/etc/nftables/scripts/apply-firewall.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Then activate service file:
systemctl daemon-reexec
systemctl enable custom-nftables
systemctol start custom-nftables
Larger Example
Control public and private services.
Inplement NAT and logging.
Automate the loading with systemd.
Validate, document and version the configuration.
eth1: Internal LAN (teachers/students).
eth2: DMZ (public services zone)
File Structure
interfaces.nft # Interfaces definitions
zones.nft # Zones and groups definitions
input-lan.nft # Input rules from LAN
input-dmz.nft # Input rules from DMZ
input-wan.nft # Input rules from WAN
forward.nft # Forwarding and NAT rules
logging.nft # Logging rules
sets.nft # Trusted IPs or blacklist
apply.sh # Load script with verification
Create Tables and Define Zones
file firewall.nft
#!/usr/sbin/nft -f
#file /etc/nftables/firewall.nft
flush ruleset
table inet myfw {
include "zones.nft"
include "interfaces.nft"
include "sets.nft"
include "input-lan.nft"
include "input-dmz.nft"
include "input-wan.nft"
include "forward.nft"
include "logging.nft"
}
Define Interfaces and Zones
file zones.nft
chain prerouting {
type filter hook prerouting priority -300;
}
chain input {
type filter hook input priority 0; policy drop;
}
chain forward {
type filter hook forward priority 0; policy drop;
}
file interfaces.nft
define wan = eth0
define lan = eth1
define dmz = eth2
Create Rules by Zone
file input-lan.nft
chain input {
iifname $lan ct state established, related accept
iifname $lan ip protocol icmp accept
iifname $lan tcp dport 22 accept
}
file input-dmz.nft
chain input {
iifname $dmz tcp dport { 80,443 } accept
}
file input-wan.nft
chain input {
iifname $wan ct state established, related accept
iifname $wan drop
}
NAT and Forwarding
file forward.nft
chain forward {
# Allow LAN to WAN
iifname $lan oifname $wan ct state new, established, related accept
# Allow LAN to DMZ
iifname $lan oifname $dmz ct state new, established, related accept
# Drop everything else
drop
}
chain postrouting {
type nat hook postrouting priority 100;
oifname $wan masquerade
}
Logging Suspicious Activity
file logging.nft
chain input {
log prefix "INPUT DROP: "flags all drop
}
chain forward {
log prefix "FORWARD DROP: flaggs all drop
}
Trust or Block Lists
file sets.nft
set blacklist {
type ipv4_addr;
flags timeout;
timeout 1h;
elements = { 192.168.1.100 }
}
chain input {
ip saddr @blacklist drop
}
Load and Verification Script
file scripts/apply.sh
#!/bin/bash
# /etc/nftables/scripts/apply.sh
echo "[INFO] Applying nftables rules..."
nft flush ruleset
nft -f /etc/nftables/firewall.nft
if [ $? -eq 0 ]; then
echo "[OK] Rules applied successfully."
else
echo "[ERROR] Failed to apply rules."
exit 1
fi
nft list ruleset
Assign execution permissions to apply.sh file
chmod +x /etc/nftables/scripts/apply.sh
Automate with systemd (method two, using a custom service
# /etc/systemd/sysstem/nftables-project.service
[Unit]
Description=Complete firewall with nftables
After=network.target
[Service]
Type=oneshot
ExecStart=/etc/nftables/scripts/apply.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
from bash terminal
systemctl daemon-reexec
systemctl enable nftables-project
systemctl start nftables-project
Verification
Test SSH, HTTP, HTTPS access
Check logs with journalctl, dmesg, /var/log or /var/log/syslog
Perform scans from LAN and WAN (for example with Nmap)
Attempt to use blocked IPs in the blacklist set
Debug rule behavior with tools like nft monitor, tcpdump, and nmap
Logging
Debian with nftables installed
Access as root and user
SSH, HTTP enabled
Editor like nano, vim or code
Create base file
sudo mkdir -p /etc/nftables/logging/
cd /etc/nftables/logging/
sudo nano debug-logging.nft
Syntax
log prefix "FIREWALL DROP:" group 0 flags all
prefix: Custom text that will appear in the logs.
group: Kernel logging group (default 0).
flags all: Includes MAC, IP, ports, interfaces, etc.
Example rule with logging
# file debug-logging.nft
table inet filter {
chain input {
type filter hook input priority 0;
policy drop;
ct state established, related accept
tcp dport 22 accept
ip protocol icmp accept
log prefix "INPUT REJECTED: " flags all drop
}
}
Capture Blocking Events
use the above debug-logging.nft file
sudo nft -f debug-logging.nft
In another terminal, try to ping from another host to this machine
Check the logs:
sudo journalctl -f | grep "INPUT REJECTED"
Alternative view in real-time:
sudo dmesg | grep "INPUT REJECTED"
Debugging with nft monitor
nftables allows monitoring rules, packets, and updates in real time.
The command shows the path of a packet through the rules
sudo nft monitor trace
Start nft monitor trace and from another terminal, ping or connect using SSH.
Limiting logging
log prefix "DOS ATTEMPT: " flags all limit rate 5/second
Logging to Files
By default, messages go to the kernel buffer. To send them to files:
Configure rsyslog:
sudo nano /etc/rsyslog.d/30-nftables.conf
Add into file:
:msg, contains, "FIREWALL" /var/log/nftables.log
& stop
Restart service
sudo sysstemctl restart rsyslog
View logs:
tail -f /var/log/nftables.log
Resources
VirtualBox with snapshots to facilitate recovery of previous states.
Git for versions and documentation of rules
Markdown to write guides with clear syntax