Preparing a Debian Server Firewall Using Remote Access with SSH

by Lance Gold

Adjusting the .conf file and logging rejected traffic

Return to index
x@c7:~$ iptables -V -bash: iptables: command not found x@c7:~$ nftables -V -bash: nftables: command not found x@c7:~$ sudo systemctl status nftables [sudo] password for x: ○ nftables.service - nftables Loaded: loaded (/usr/lib/systemd/system/nftables.servi> Active: inactive (dead) Docs: man:nft(8) http://wiki.nftables.org lines 1-5/5 (END) z [1]+ Stopped sudo systemctl status nftables x@c7:~$

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

Chains attach rules to kernel hooks: input, output, forward hooks

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

Families:
inet, ip, ip6, arp, bridge

inet is both IPv4 and IPv6

ip is only IPv4

Priority:
NAT prerouting -100

Filtering 0

NAT postrouting 100

Data Types:

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>;
}

type: filter, nat, route, etc.
hook: input, output, forward, prerouting, postrouting
priority: Execution order.
policy: default behavior if no match is found.
example:


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

a) Clear previous rules

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

Name tables and chains logically

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

directory firewall-nfts
directory rules
base.nft

ssh_web.nft

blacklist.nft

directory docs

rule_explaination

directory practices

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:

A Linux router connecting an internal network to the internet must perform NAT and allow external connections to access an internal web server.

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.

Block traffic between group1 and group2

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.

Principles
Default Deny: only allow explicitly necessary traffic

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

Public Server

Allow only necessary services and use logging

Workstation

Accept output, filter input

Router/NAT

Forward filtering, and explicit NAT

IoT/Embedded

Strict whitelist, no input, only outgoing connections

Example
A student network divided into VLANs.

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

Create a security table

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

Tips
Always use ct state for state handling to reduce 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

Put rules into a file or script

Use systemd to apply at boot

Manage securely with a controlled source

File structure

/etc/nftables/
firewall.nft #Main rules

interfaces.nft #Rules per interface

services.nft #Rules per ports/services

logging.nft #Logging rules

scripts/

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

Tip:
Before applying a script or restarting rules, make sure you have physical or console access.

Avoid locking yourself out, especially in network or remote access environments.

Method 1:

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

Method 2:

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

Design rules by zones and interfaces.

Control public and private services.

Inplement NAT and logging.

Automate the loading with systemd.

Validate, document and version the configuration.

Three interfaces:
eth0: Internet (external zone)

eth1: Internal LAN (teachers/students).

eth2: DMZ (public services zone)

Exposed services:

File Structure

/etc/nftables/
firewall.nft #Main file

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

scripts/

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

Verify rules with nft list ruleset

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

Starting with a setup:

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

GNS3 + Linux VMs to simiulate complex topologies

VirtualBox with snapshots to facilitate recovery of previous states.

Git for versions and documentation of rules

Markdown to write guides with clear syntax