Learn how to configure nftables firewall with advanced security rules, rate limiting, and fail2ban integration. This tutorial covers migration from iptables, logging configuration, and production-grade security hardening for modern Linux systems.
Prerequisites
- Root or sudo access
- Basic understanding of networking concepts
- SSH access to the server
What this solves
nftables is the modern replacement for iptables in Linux, offering improved performance, better syntax, and advanced filtering capabilities. This tutorial shows you how to configure a production-ready firewall with nftables, including port-based access control, rate limiting, logging, and fail2ban integration for comprehensive security hardening.
Step-by-step configuration
Update system and install nftables
Start by updating your system and installing nftables. Most modern distributions include nftables by default, but we'll ensure it's properly installed and configured.
sudo apt update && sudo apt upgrade -y
sudo apt install -y nftables
Stop and disable iptables services
Before configuring nftables, disable any existing iptables services to prevent conflicts. This ensures nftables has full control over packet filtering.
sudo systemctl stop ufw
sudo systemctl disable ufw
sudo systemctl mask iptables
sudo systemctl mask ip6tables
Create basic nftables configuration
Create a comprehensive nftables configuration file with separate tables for filtering and NAT. This configuration includes basic security rules, logging, and connection tracking.
#!/usr/sbin/nft -f
Clear existing rules
flush ruleset
Define variables for common ports and networks
define SSH_PORT = 22
define HTTP_PORT = 80
define HTTPS_PORT = 443
define TRUSTED_NETS = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 }
Main filter table
table inet filter {
# Chain for input packets
chain input {
type filter hook input priority filter; policy drop;
# Accept loopback traffic
iif "lo" accept
# Accept established and related connections
ct state established,related accept
# Drop invalid connections
ct state invalid drop
# Accept ICMP (ping)
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 5/second accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, time-exceeded, nd-neighbor-solicit, nd-neighbor-advert } limit rate 5/second accept
# SSH with rate limiting
tcp dport $SSH_PORT ct state new limit rate 3/minute burst 3 packets accept
# HTTP and HTTPS
tcp dport { $HTTP_PORT, $HTTPS_PORT } ct state new accept
# Log dropped packets (rate limited)
limit rate 10/minute burst 5 packets log prefix "nftables-dropped: "
# Default drop (implicit due to policy)
}
# Chain for forward packets (for routers/gateways)
chain forward {
type filter hook forward priority filter; policy drop;
# Accept established and related connections
ct state established,related accept
# Log dropped forwards
limit rate 5/minute burst 3 packets log prefix "nftables-forward-drop: "
}
# Chain for output packets
chain output {
type filter hook output priority filter; policy accept;
# Generally allow all outbound traffic
# Add restrictions here if needed
}
}
NAT table for network address translation
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
# Example: masquerade outbound traffic (uncomment if needed)
# oifname "eth0" masquerade
}
}
Enable and start nftables service
Enable nftables to start automatically on boot and load the configuration. The service will parse and apply the rules from the configuration file.
sudo systemctl enable nftables
sudo systemctl start nftables
sudo systemctl status nftables
Create advanced security rules with rate limiting
Add sophisticated rate limiting and security rules to protect against common attacks. Create a separate configuration file for advanced rules.
#!/usr/sbin/nft -f
Advanced security rules - append to existing configuration
Create sets for dynamic blocking
table inet security {
set blocked_ips {
type ipv4_addr
flags timeout
timeout 1h
}
set rate_limit_ips {
type ipv4_addr
flags timeout
timeout 10m
}
# Security chain with advanced rules
chain security_input {
type filter hook input priority -10; policy accept;
# Block IPs in blocked set
ip saddr @blocked_ips drop
# Rate limiting per IP
tcp dport { 80, 443 } add @rate_limit_ips { ip saddr limit rate over 50/minute burst 100 packets } drop
# Protect against port scanning
tcp flags & (fin|syn|rst|ack) == syn ct state new limit rate 10/second burst 20 packets accept
# Block TCP flood attacks
tcp flags & (fin|syn|rst|ack) == syn limit rate 25/second burst 50 packets accept
# Block UDP flood
meta l4proto udp limit rate 25/second burst 50 packets accept
# Log suspicious activity
tcp flags & (fin|syn|rst|ack) == (fin|syn) log prefix "TCP-scan: " drop
tcp flags & (fin|syn|rst|ack) == (fin|rst) log prefix "TCP-scan: " drop
}
}
Configure logging and monitoring
Set up proper logging for nftables events. Configure rsyslog to handle nftables logs separately from other system logs.
# nftables logging configuration
:msg,contains,"nftables" /var/log/nftables.log
& stop
Separate file for dropped packets
:msg,contains,"nftables-dropped" /var/log/nftables-dropped.log
& stop
Log rotation will be handled by logrotate
Create logrotate configuration
Configure log rotation for nftables logs to prevent disk space issues. This ensures logs are compressed and rotated regularly.
/var/log/nftables*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 root adm
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
Restart logging services
Restart rsyslog to apply the new logging configuration and create the initial log files.
sudo systemctl restart rsyslog
sudo touch /var/log/nftables.log /var/log/nftables-dropped.log
sudo chown root:adm /var/log/nftables*.log
sudo chmod 640 /var/log/nftables*.log
Install and configure fail2ban with nftables
Install fail2ban and configure it to work with nftables for automatic IP blocking based on log analysis.
sudo apt install -y fail2ban
Configure fail2ban for nftables
Create fail2ban configuration that integrates with nftables. This configuration monitors SSH attempts and adds repeat offenders to the blocked IPs set.
[DEFAULT]
Default settings
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd
Use nftables for banning
banaction = nftables-multiport
banaction_allports = nftables-allports
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600
[nginx-http-auth]
enabled = false
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
[nginx-limit-req]
enabled = false
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 600
Create nftables action for fail2ban
Create a custom fail2ban action that adds banned IPs to our nftables blocked set. This integrates fail2ban seamlessly with our nftables configuration.
[Definition]
Fail2ban nftables action for blocked IPs set
actionstart =
actionstop =
actioncheck =
actionban = nft add element inet security blocked_ips { timeout 1h }
actionunban = nft delete element inet security blocked_ips { }
[Init]
name = blocked-ips
table = inet security
timeout = 3600
Enable and start fail2ban
Enable fail2ban service and verify it's working correctly with nftables integration.
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo systemctl status fail2ban
Create backup and restore scripts
Create scripts to backup and restore your nftables configuration. This is essential for production environments and configuration management.
#!/bin/bash
nftables backup script
BACKUP_DIR="/var/backups/nftables"
DATE=$(date +"%Y%m%d-%H%M%S")
BACKUP_FILE="$BACKUP_DIR/nftables-$DATE.conf"
Create backup directory
mkdir -p "$BACKUP_DIR"
Export current ruleset
sudo nft list ruleset > "$BACKUP_FILE"
Compress older backups
find "$BACKUP_DIR" -name "*.conf" -mtime +7 -exec gzip {} \;
Remove backups older than 30 days
find "$BACKUP_DIR" -name "*.conf.gz" -mtime +30 -delete
echo "Backup created: $BACKUP_FILE"
Create restore script
Create a restoration script that can quickly restore nftables configuration from backup files.
#!/bin/bash
nftables restore script
if [ $# -eq 0 ]; then
echo "Usage: $0 "
echo "Available backups:"
ls -la /var/backups/nftables/
exit 1
fi
BACKUP_FILE="$1"
if [ ! -f "$BACKUP_FILE" ]; then
echo "Backup file not found: $BACKUP_FILE"
exit 1
fi
echo "Restoring nftables configuration from: $BACKUP_FILE"
Clear current rules and restore from backup
sudo nft flush ruleset
sudo nft -f "$BACKUP_FILE"
echo "Configuration restored successfully"
Make scripts executable and create cron job
Set proper permissions for the backup scripts and create a daily backup cron job for automated configuration backups.
sudo chmod 755 /usr/local/bin/nftables-backup.sh /usr/local/bin/nftables-restore.sh
Create daily backup cron job
echo "0 2 * root /usr/local/bin/nftables-backup.sh" | sudo tee /etc/cron.d/nftables-backup
Migrate existing iptables rules
If you have existing iptables rules, use the iptables-translate tool to convert them to nftables format. This tool helps migrate complex rulesets.
# Install translation tools
sudo apt install -y iptables-nftables-compat
Translate existing iptables rules
sudo iptables-save > /tmp/iptables-rules.txt
sudo iptables-restore-translate -f /tmp/iptables-rules.txt
For ip6tables
sudo ip6tables-save > /tmp/ip6tables-rules.txt
sudo ip6tables-restore-translate -f /tmp/ip6tables-rules.txt
Verify your setup
Test your nftables configuration and verify all components are working correctly.
# Check nftables status
sudo systemctl status nftables
List current ruleset
sudo nft list ruleset
Check fail2ban status
sudo fail2ban-client status
sudo fail2ban-client status sshd
Test SSH rate limiting (from another machine)
ssh user@example.com (repeat quickly to trigger rate limit)
Check logs
sudo tail -f /var/log/nftables.log
sudo tail -f /var/log/fail2ban.log
Verify backup script
sudo /usr/local/bin/nftables-backup.sh
Check blocked IPs set
sudo nft list set inet security blocked_ips
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service won't start | Syntax error in config | sudo nft -c -f /etc/nftables.conf to check syntax |
| Can't SSH after setup | Blocked by rate limiting | Wait 10 minutes or add IP to whitelist in config |
| Fail2ban not blocking | Wrong backend or action | Check sudo fail2ban-client status and logs |
| Logs not appearing | Rsyslog not restarted | sudo systemctl restart rsyslog |
| Rules not persistent | Service not enabled | sudo systemctl enable nftables |
| Port still accessible | Rule order issue | Check rule priority with nft -a list table inet filter |
Next steps
- Install and configure Fail2ban with advanced rules and email alerts
- Configure Linux system logging with rsyslog and journald for centralized log management
- Configure automatic security updates with unattended-upgrades and email notifications
- Set up intrusion detection with OSSEC and nftables integration
- Configure nftables NAT and port forwarding for home lab environments
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Global variables
PKG_MGR=""
PKG_INSTALL=""
PKG_UPDATE=""
FIREWALL_SERVICE=""
IPTABLES_SERVICE=""
SSH_PORT="${1:-22}"
# Usage message
usage() {
echo "Usage: $0 [SSH_PORT]"
echo " SSH_PORT: SSH port number (default: 22)"
exit 1
}
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function for rollback
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
log_error "Script failed. Attempting cleanup..."
if [ -f /etc/nftables.conf.backup ]; then
mv /etc/nftables.conf.backup /etc/nftables.conf 2>/dev/null || true
fi
systemctl stop nftables 2>/dev/null || true
fi
}
trap cleanup ERR
# Check if running as root or with sudo
check_privileges() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
}
# Auto-detect distribution
detect_distro() {
if [ ! -f /etc/os-release ]; then
log_error "/etc/os-release not found. Cannot detect distribution."
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
FIREWALL_SERVICE="ufw"
IPTABLES_SERVICE="iptables"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
FIREWALL_SERVICE="firewalld"
IPTABLES_SERVICE="iptables"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
FIREWALL_SERVICE="iptables"
IPTABLES_SERVICE="iptables"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_success "Detected distribution: $PRETTY_NAME"
}
# Validate SSH port
validate_ssh_port() {
if ! [[ "$SSH_PORT" =~ ^[0-9]+$ ]] || [ "$SSH_PORT" -lt 1 ] || [ "$SSH_PORT" -gt 65535 ]; then
log_error "Invalid SSH port: $SSH_PORT"
usage
fi
}
# Update system and install nftables
install_nftables() {
echo "[1/6] Updating system and installing nftables..."
log_info "Updating system packages..."
eval $PKG_UPDATE
log_info "Installing nftables..."
eval "$PKG_INSTALL nftables"
log_success "nftables installed successfully"
}
# Stop and disable conflicting firewall services
disable_conflicting_services() {
echo "[2/6] Disabling conflicting firewall services..."
case "$PKG_MGR" in
apt)
systemctl stop ufw 2>/dev/null || log_warning "ufw service not running"
systemctl disable ufw 2>/dev/null || log_warning "ufw service not found"
;;
dnf|yum)
systemctl stop firewalld 2>/dev/null || log_warning "firewalld service not running"
systemctl disable firewalld 2>/dev/null || log_warning "firewalld service not found"
;;
esac
systemctl mask iptables 2>/dev/null || log_warning "iptables service not found"
systemctl mask ip6tables 2>/dev/null || log_warning "ip6tables service not found"
log_success "Conflicting services disabled"
}
# Create nftables configuration
create_nftables_config() {
echo "[3/6] Creating nftables configuration..."
# Backup existing configuration if it exists
if [ -f /etc/nftables.conf ]; then
cp /etc/nftables.conf /etc/nftables.conf.backup
log_info "Existing configuration backed up"
fi
cat > /etc/nftables.conf << EOF
#!/usr/sbin/nft -f
# Clear existing rules
flush ruleset
# Define variables for common ports and networks
define SSH_PORT = $SSH_PORT
define HTTP_PORT = 80
define HTTPS_PORT = 443
define TRUSTED_NETS = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 }
# Main filter table
table inet filter {
# Chain for input packets
chain input {
type filter hook input priority filter; policy drop;
# Accept loopback traffic
iif "lo" accept
# Accept established and related connections
ct state established,related accept
# Drop invalid connections
ct state invalid drop
# Accept ICMP (ping) with rate limiting
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 5/second accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, time-exceeded, nd-neighbor-solicit, nd-neighbor-advert } limit rate 5/second accept
# SSH with rate limiting
tcp dport \$SSH_PORT ct state new limit rate 3/minute burst 3 packets accept
# HTTP and HTTPS
tcp dport { \$HTTP_PORT, \$HTTPS_PORT } ct state new accept
# Log dropped packets (rate limited)
limit rate 10/minute burst 5 packets log prefix "nftables-dropped: "
# Default drop (implicit due to policy)
}
# Chain for forward packets (for routers/gateways)
chain forward {
type filter hook forward priority filter; policy drop;
# Accept established and related connections
ct state established,related accept
# Log dropped forwards
limit rate 5/minute burst 3 packets log prefix "nftables-forward-drop: "
}
# Chain for output packets
chain output {
type filter hook output priority filter; policy accept;
# Generally allow all outbound traffic
# Add restrictions here if needed
}
}
# NAT table for network address translation
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
# Example: masquerade outbound traffic (uncomment if needed)
# oifname "eth0" masquerade
}
}
EOF
chmod 644 /etc/nftables.conf
chown root:root /etc/nftables.conf
log_success "nftables configuration created"
}
# Enable and start nftables service
enable_nftables() {
echo "[4/6] Enabling and starting nftables service..."
systemctl enable nftables
systemctl start nftables
log_success "nftables service enabled and started"
}
# Install and configure fail2ban
configure_fail2ban() {
echo "[5/6] Installing and configuring fail2ban..."
eval "$PKG_INSTALL fail2ban"
# Create fail2ban jail configuration for nftables
cat > /etc/fail2ban/jail.d/sshd-nftables.conf << EOF
[sshd]
enabled = true
port = $SSH_PORT
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 600
bantime = 3600
backend = systemd
banaction = nftables-multiport
banaction_allports = nftables-allports
EOF
# Create local configuration
if [ ! -f /etc/fail2ban/jail.local ]; then
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
backend = systemd
[sshd]
enabled = true
port = $SSH_PORT
EOF
fi
chmod 644 /etc/fail2ban/jail.d/sshd-nftables.conf
chmod 644 /etc/fail2ban/jail.local
chown root:root /etc/fail2ban/jail.d/sshd-nftables.conf
chown root:root /etc/fail2ban/jail.local
systemctl enable fail2ban
systemctl restart fail2ban
log_success "fail2ban configured and started"
}
# Verify configuration
verify_setup() {
echo "[6/6] Verifying configuration..."
# Check if nftables is running
if systemctl is-active --quiet nftables; then
log_success "nftables service is active"
else
log_error "nftables service is not active"
return 1
fi
# Check if fail2ban is running
if systemctl is-active --quiet fail2ban; then
log_success "fail2ban service is active"
else
log_warning "fail2ban service is not active"
fi
# Test nftables rules
if nft list ruleset > /dev/null 2>&1; then
log_success "nftables rules loaded successfully"
else
log_error "Failed to load nftables rules"
return 1
fi
# Show current rules summary
echo
log_info "Current nftables rules summary:"
nft list ruleset | grep -E "(chain|policy|accept|drop)" | head -10
echo
log_success "Firewall configuration completed successfully!"
log_info "SSH port: $SSH_PORT"
log_info "Allowed services: SSH, HTTP, HTTPS"
log_warning "Make sure you can connect via SSH on port $SSH_PORT before closing this session"
}
# Main execution
main() {
log_info "Starting nftables firewall configuration..."
check_privileges
detect_distro
validate_ssh_port
install_nftables
disable_conflicting_services
create_nftables_config
enable_nftables
configure_fail2ban
verify_setup
echo
log_success "nftables firewall setup completed successfully!"
echo
echo "Next steps:"
echo "- Test SSH connectivity on port $SSH_PORT"
echo "- Review logs: journalctl -u nftables -f"
echo "- Monitor fail2ban: fail2ban-client status"
echo "- Customize rules in /etc/nftables.conf as needed"
}
# Run main function
main "$@"
Review the script before running. Execute with: bash install.sh