Electrical-Forenics Home ray@RayFranco.com                       601.529.7473
   © Dr. Ray Franco, PhD, PE  -  208 Fairways Dr., Vicksburg, MS 39183

 This Page is a Work in Progress !

   Last updated on 8/8/2025

Network Filter Tables (nftable)

Nftables are a framework for packet filtering, firewalls and Network Address Translators (NATs). Support for nftables has been in the Linux Kernel since version 3.13. Nfables is the sucessor to iptables. In Debian 10 (buster - July 6, 2019), nftables replaced iptables. nftables has a compatibility mode for iptables.

To run nft commands requires root privillages. You can either prefix all nft commands with sudo or you can change to the root user:

sudo bash

To determine the verson of Nftables:

nft -v

nftables Hierarchy

At the top is a text file, /etc/nftables.conf. It contains one or more nftables. Nftables contains one or more chains, and chains contain one or more rules.

All of the nftables do not have to be inside of /etc/nftables.conf. You can include other nftables at the end of the file:

include "/Path/nftable_name.conf"

# anything after the hash sign (#) is a comment
; more commands or parameters to follow
\ break a rule into mutliple lines

Ntable Types

There are six ntable types or address families:

ipip4 addresses - default if none specified
ip6ip6 addresses
inetboth ip4 & ip6
bridge 
netdevingress filtering
arpAddress Resolution Protocol

Syntax: table [<address_family>] nft_table_name { }

Chain types

There are two types of chains: base and non-base.

Base Chains

Base chains are the entry point for packets from the networking stack. Base chains specify a type, hook, priority and policy.

Syntax: chain chain_name {type <type> hook <hook> priority <priority>; [policy <policy>;]}

type: filter, route, and nat
priority: -300 to 300
policy: accept or drop

The lower the priority number the higher the priority is. Priority numbers can be negaive.

If no policy is explicitively give the default policy accept will be used.

Non-Base Chains - Regular Chains

Non-base chains are jump target to better organize rules. Non-base type chains do not have a type, hook and priority.

Rules

Rules are some expression to be matched followed by a verdict statement.

The following is an incomplet list of expressions that can be matched:

 

Common IPv4 Matches
saddr <ip source address> Source addresses ip saddr 192.168.1.0
ip saddr 192.168.1.0/24
daddr <ip destination address> Destination addresses ip daddr 192.168.1.0
ip daddr 192.168.1.0/24
protocol <protocol> Upper layer protocol ip protocol tcp
ip protocol udp
ip protocol icmp

 

Common TCP Matches
sport <tcp source port> Source port tcp sport 22
tcp sport ssh
dport <tcp destination port> Destination port tcp dport 22
tcp dport ssh

 

Common UDP Matches
sport <udp source port> Source port udp sport 22
udp sport ssh
dport <udp destination port> Destination port udp dport 22
udp dport ssh

 

Common Meta Matches
iifname <input interface name> Input interface name meta iifname "lo"
meta iifname "eth0"
oifname <Output interface name> Outout interface name meta oifname "lo"
meta oifname "eth0"
iff <input interface index> input interface index meta iif lo
meta iff eth0
oif <Ouput interface index> output interface index meta oif lo
meta oif eth0
ifftype <input interface type> input interface type meta iiftype loopback
meta iiftype ether
meta iiftype ipip
meta iiftype ipip6
oif <Ouput interface type> output interface type meta oiftype loopback
meta oiftype ether
meta oiftype ipip
meta oiftype ipip6

* The prefix meta before the meta key is optional. See meta expressions - types qualified and unqualified in manpage.[]

Common Ether Matches
saddr <mac address> Source MAC address either saddr 00:0f:54:0c:11:04

Verdict statements include:

References

  1. Quick reference-nftables in 10 minutes
  2. Nftables.org - NFT Man Pages

Example

The follow is an ntable that accepts all IPv4 traffic with input source address 192.168.37.52, and its drops all other inputs, including all IPv6 traffic.


table inet FILTER {
    chain INPUT {
        type filter hook input priority 0; policy accept;
        ip saddr 192.168.37.52 accept
        drop
    }
}
        

References

  1. Wiki ArchLinux nftables

Documentation

There are very few good tutorials on nftables. Nftables just does not have the same amount of documentation that iptables has. The nftables organization's (nftables.org) documentation is good; however, it is not a begginers guide. They want to show off their latest advances over iptables and how concise and succinct they can be. They have a simple ruleset for a home router [13], but it uses the concatenation operator (.) [14], vmap [15], and th (transport header) [16]. You have to dig through their documention to learn what these are. In the end, I found out that the th parameter requires both nftables 0.92 and Linux Kernnel 5.3. As of 11-29-22, the Raspberry Pi OS-64 still uses kernnel 5.15.

References

  1. Advanced nftables Rules for Complex Network Scenarios in 2025
  2. Get Started with nftable | Linode
  3. NFTables Beginners Guide
  4. Using nftables in Red Hat Enterprise Linux 8 | Red Hat
  5. Chapter 6. Getting Started with nftables - Linux 7 | Red Hat
  6. Chapter 52. Getting started with nftables Red Hat Enterprise Linux 8 | Red Hat Customer Portal
  7. Best Practices for persisting nftables rules | ask ubuntu
  8. Nftables rules and tables dissapiered after reboot | Linuxquestions.org
  9. systemctl Commands: Restart, Reload, Stop Service and More | Linode
  10. https://Wiki.nftables.org
  11. https://Wiki.nftables.org - Quick reference-nftables in 10 minutes
  12. Simple Ruleset for a Workstation - nftables.org
  13. Simple Ruleset for a Server - nftables.org
  14. Simple Ruleset for a Home Router - nftables.org
  15. Concatenatins - nftables.org
  16. Verdict Maps (vmaps) - nftables.org
  17. Matching Packet Headers - nftabes.org
  18. https://Wiki.nftables.org - Jumping to chain - nftables wiki
  19. Setting up nftables Firewall | Cryptsus Blog
  20. Firewall Configuration with nftables
  21. Protecting Incoming Traffic with Nftables - Linux Cloud Hacks
  22. Unleashing the Power of Nftables Chains and Verdict Maps - Linux Cloud Hacks
  23. Mastering Nftables Sets: A Comprehensive Guide - Linux Cloud Hacks
  24. Keep! - Setting up nftables Firewall
  25. SOCAT - you won't believe what this Linux tool can do! - Linux Cloud Hacks

tcp dport 22 accept

Most all of these commands require root privilages. To become the root in the Raspberry Pi OS:

sudo bash

Notice that prompt changes to the hash sign (#), and that

whoami

says "root".

To determine if the the modulel is in the Linux Kernel:

modinfo nf_tables

To determine if nftables is installed, check the version:

nft -v

To determine if nftables is active

systemctl status nftables

My general purpose everyday RPi has firewald and fail2band installed. The "system status nftable" command say that it is unactive. However, the "nft list ruleset" command shows a table with the table_name firewalld. I do not understand this.

Systemctl Commands to Enabling and Disabling Nftabes

Raspberry Pi has everthing installed, but by default it is unactive.

For the current session, you can start or stop nftables (not persistent), with the following commands.

systemctl start nftables
systemctl stop nftables

To enabled or disabled nftables after a reboot (persistent), use one of the following commands:

systemctl enable nftables
systemctl mask nftables

To see errors:

systemctl status nftables

Nftables - Configuration and Script Files

By defination, Nftables does not have defaults chains (like iptables). However, some Linux distributions have a default configuration file, which contains predefined chains. Also, some distrubtion have example nft script files. See table below:

Distribution Config. Files Script Files
Debian
Raspberry Pi
/etc/nftables.conf /usr/share/doc/nftables/examples/
Red Hat
Fedora
/etc/sysconfig/nftables.conf /etc/nftables/

Debian's default configuration file, /etc/nftables.conf, which is listed below, contains three chains, an input chain, a forward chain, and an output chain. It accepts everything. It is just a skelton that can be added to.

Debian's Default nftables Configuration File

#!/usr/sbin/nft -f

flush ruleset

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

Editing Configuration Files and Creating Script Files

There are three ways to edit or create nftables.

  1. Edit the nftables configuration file directly with your favority text editor. Anything after a # is a comment. A Nft configuration file can include nft script files, but they must be located in the /etc directory i.e. include "etc/Exampe2.nft".
  2. Edit the nftables configuration file with the nft add command to add tables, chains, and rules. Similarly, there is an nft delete command. In addition, there are commands to change the order of the rules. This method will detect syntax errors; however, it is very cumberson. In addition the changes are not persistant after a reboot unless you write the debugged ruleset back to the configuration file. Unfortunately, many introductory nftables articles spend most of their time explaining how to edit the configuration file with this method, and they spend very little time on nftables.
  3. Create nftables script files.
    1. You can run the script directly.
    2. The script can be called (included) from another script.
    3. The script can be loaded and ran from the nft enviroment with the command nft -f or --file. The last method will check for syntax errors. However, again this method is not persistent. You can write the nft list ruleset output back to the configuration file, which will make it persistance, but there will be no comments in the configuration file.

The easest method, is to create and debug a script file wih method 3C, and to include the script file in the configuration file. Note, the configuration file can be just a single include statement.

Nft script files can have any name, but it is customary for them to have the .nft extension.

If you are going to pass a nft script file to the nft utility, it must have the folowing header:

#!/usr/sbin/nft -f

If you want to flush the existing ruleset then the next line after the header should be:

flush ruleset

To pass a script to the nft utility, enter:

nft -f your_script_filename.nft

To run a script directly, you have to make the script executable:

chmod +x your_script_filename.nft

In debugging nftables, the following command my be useful:

systemctl status nftables

Other than the extension, location and that the fact that the nftables.conf is ran at boot, there are no differences between the nftables.conf file and nftables script files.

Idiosyncrasies

References

  1. https://wiki.debian.org/nftables
  2. Using nftables in Red Hat Enterprise Linux 8
  3. Wiki Archlinux Nftables
  4. Red Hat Training for RHEL 8 - Chapter 41. Getting started with nftables
  5. Wiki-Nftables.org - Scipting

nft - viewing chains & rules

To view all chains and rules in all tables:

nft list ruleset

To view chains and rules in a particuar table:

nft list table address_family table_name

To view the rules in a particuar chain:

nft list chain address_family table_name chain_name

Although you should never use firewalld and nftables together, you can use the nft utility to the view the nftables and rules generatated by firewalld:

nft list table inet firewalld
nft list table ip firewalld
nft list table ip6 firewalld

nftable

# anything after the hash sign (#) is a comment
; more commands or parameters to follow
\ break a rule into mutliple lines

 

Address Families

ipip4 addresses
ip6ip6 addresses
inetboth ip4 & ip6
bridge 
netdevingress filtering
arp 

 

Both iftables and iptables have chains with policy (accept/drop) and rules.

Both nf and ip table can filter:

prerouting  
input  
forward  
output  
postrouting  
arp  

nft -v # version

/////////

sudo nft add table [address_famaily] example_table

sudo nft delete table [address_famaily] example_table

sudo nft flush table [address_famaily] example_table

The flush command deletes every rule in every chain in the table

sudo nft add chain inet example_table example_chain '{type filter hook input priority 0; }'

base chain
type is filter
hook is input
priority is 0

Chains with lower priority numbers get procesed first.

Chain Type

filter
NAT

Non-base chains aslo refered to as regular chains, do not have type, hook and priority

 

saddr = source address e.g., 192.168.0.0/24
daddr = destination address e.g., 192.168.0.2-192.168.0.59
iifname = input interface name e.g., "eth0", "wlan0"
oifname = output interface name e.g., "eth0", "wlan0"
ct= connection tracking  

 

Tables have chains, and chains have rules. A table can have more than one chain, and a chain can have more than one rule.

Aliases

Common port aliases are:

Port No. Alias
22ssh
80http
443https
53domain (dns)

To list all aliases: cat /etc/services.

 

Priority No.|Alias
0filter

I am not a fan of using keywords as aliases e.g., a type of rule is a filter (keyword), but the word "filter" can also be used as an alias for the rule priority number 0, and when they are in the same line, this makes it confusing for newcomers.

Example 1A: Only allow incoming from 192.168.37.52


#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;

        # Allow all traffic from 192.168.37.52 - Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Drop everything else
        drop
    }
}
     

Examble 1 does not allow loopback traffic that some services require. Modify it to allow loopback traffic.

Example 2:


#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;

        # Allow loopback traffic from this computer
        meta iif lo accept

        # Drop loopback traffic not from this computer
        iif != lo ip daddr 127.0.0.1/8 drop  # IPv4
        iff != lo ip6 daddr ::1/128 drop     # IPv6

        # Allow all traffic from 192.168.37.52 - Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Drop everything else
        drop
    }
}
     

References:

  1. YouTube - A Brief Intro to Firewalls with nftables

Example 1A: Allow all incoming tcp traffic to ports 22 (ssh) and 5900 (VNC).



#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Allow SSH traffic
        # matched by tcp and destination port 22 (alias ssh) 
        tcp dport ssh accept

        # Allow VNC traffic
        tcp dport 5900 accept # no alias for this port

        # Drop everything else
        drop
    }
}
     

I am not a fan of keywords and/or aliases having multiple meaning. However, this seems to be the norm with nftable. For example, a type of chain is a "filter" (keyword), and "filter" is also used as an alias for the priority number 0. Furthermore, in this example the table name is "filter", and the chain name is "input", which is a type of hook (keyword). This is very confusing for newcomers.

Inline Counters

Example 1B: To determine if this is working, you can add counters to count the number of packets and bytes. Counters should be paced immediately before accept or drop.

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Allow SSH traffic
        # matched by tcp and destination port 22 (alias ssh) 
        tcp dport ssh counter accept

        # Allow VNC traffic
        tcp dport 5900 counter accept # no alias for this port

        # Drop everything else
        drop
    }

}
       

Named Counters

You can also defined named counters. Modify example 1a by naming the counter.

#!/usr/sbin/nft -f

flush ruleset

    counter ssh_or_Vnc {}

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Allow SSH traffic
        # matched by tcp and destination port 22 (alias ssh) 
        tcp dport ssh counter name ssh_or_vnc accept

        # Allow VNC traffic
        tcp dport 5900 counter name ssh_or_vnc accept 

        # Drop everything else
        drop
    }

}
       

Example 1C: Modify Example 1B to allow SSH and VNC traffic only from source addresses 192.168.37.45 and 192.168.37.45.

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Allow SSH traffic
        # matched by tcp and destination port 22 (alias ssh) 
        ip saddr 192.168.37.45 tcp dport ssh counter accept
        ip saddr 192.168.37.46 tcp dport ssh counter accept

        # Allow VNC traffic
        ip saddr 192.168.37.45 tcp dport 5900 counter accept 
        ip saddr 192.168.37.46 tcp dport 5900 counter accept 

        # Drop everything else
        drop
    }

}
       

To use MAC addresses in lieu of IP addresses, prefix saddr with ether. For example:

        ether saddr 04:0D:0A:3D:FE:06 tcp dport ssh counter accept
       

References

  1. Protecting Incoming Traffic with Nftables

In-Line Sets or Anonymous Sets

Sets are enclosed in brackets, and the elements are separated by commas. You can use sets to combine rules.

Example 1D: Modify Example 1C using sets.

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Allow SSH and VNC traffic
        # matched by source address and tcp protocol with destination port 22 or 5900. 
        ip saddr {192.168.37.45, 192.168.37.46} tcp dport {ssh, 5900} counter accept

        # Drop everything else
        drop
    }

}
       

References

  1. nftables Wiki - Sets
  2. Mastering Nftables Sets: A Comprehensive Guide - Linux Cloud Hacks

Introduction to Named Sets

Example 1E: Modify Example 1D to use named sets.


#!/usr/sbin/nft -f

flush ruleset

# set_example.nft


table inet filter {

    set allowable_ip_addresses{
        typeof ip saddr  
        elements = {192.168.37.45, 192.168.37.46}
    }

    set allowable_dports{
        typeof tcp dport 
        elements = {ssh, 5900}
    }

    chain input {
        type filter hook input priority filter;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Only allow SSH & VNC traffic
        # matched by source IP and tcp destination ports   
        ip saddr @allowable_ip_addresses tcp dport @allowable_dports accept

        # Drop everything else
        drop
     }

}
     

Having to declare named sets in advance is more complicated than in-line sets. However, we have just touched on named sets. Named sets are more powerful than in-line sets. For example, as will be in "Advanced Named Sets", you can use nft rules to add elements to named sets.

References

  1. nftables Wiki - Sets
  2. Mastering Nftables Sets: A Comprehensive Guide - Linux Cloud Hacks

Allow and Limit Pings

For troubleshooting, you should accept pings but limit them

Example 1F: Modify Example 1E to accept pings.


#!/usr/sbin/nft -f

flush ruleset

# set_example.nft


table inet filter {

    set allowable_ip_addresses{
        typeof ip saddr  
        elements = {192.168.37.45, 192.168.37.46}
    }

    set allowable_dports{
        typeof tcp dport 
        elements = {ssh, 5900}
    }

    chain input {
        type filter hook input priority filter;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Only allow SSH & VNC traffic
        # matched by source IP and tcp destination ports   
        ip saddr @allowable_ip_addresses tcp dport @allowable_dports accept

        # allow pings without limits.
        ip protocol icmp accept;

        # Drop everything else
        drop
     }

}
     

Example 1G: Modify Example 1F to limit pings.


#!/usr/sbin/nft -f

flush ruleset

# set_example.nft


table inet filter {

    set allowable_ip_addresses{
        typeof ip saddr  
        elements = {192.168.37.45, 192.168.37.46}
    }

    set allowable_dports{
        typeof tcp dport 
        elements = {ssh, 5900}
    }

    chain input {
        type filter hook input priority filter;

        # Don't lock myself out 
        ip saddr 192.168.37.52 accept

        # Only allow SSH & VNC traffic
        # matched by source IP and tcp destination ports   
        ip saddr @allowable_ip_addresses tcp dport @allowable_dports accept

        # allow only 1 ping per second 
        ip protocol icmp limit rate 1/second accept;

        # Drop everything else
        drop
     }

}
     

References

  1. Firewall Configuration with nftables - Travis Horn

Non-base Chains and Verdict Maps

Thus far, all our chains have been "base" chains. Non-base chains do NOT have a type, hook, priority or default policy. They only contain rules. They are used to group your rules according to some criteria. Non-based chains are the targets of jump or goto actions.

Connection Tracking - Conntrack / Stateful Firewald

TCP has connection tracking (ct) states. UDP does NOT have connection tracking states [1].

There are five (5) conntrack (ct) states [4]:

new Netfilter has so far seen packets between this pair of hosts only in one direction. At least on of theses packets is part of a valid initialization sequence,e.g. SYN packet for a TCP Connection.
established Netfilter has seen valid packets travel in both directions between this pair of hosts. For TCP connections, the three-way-handshake has been successfully completed.
related This connection was initiated after the main connection, as expected from normal operation of the main connection. A common example is an FTP data channel established at the behest of an FTB control channel.
invalid Assigned to packets that do not follow the expected behavior of a connection. These include malformed, corrupted packets and maliceware. Another example, would be a late TCP packet that is received after retransmission has been performed [3]. These should be dropped.
untracked Dummy state assigned to packets.

The following example uses connection tracking to only allow outbound DNS, HTTP, and HTTPS traffic and only allow inbound return traffic and loop back traffic.


#!/usr/bin/nft -f

flush ruleset

table ip transport {

   chain INPUT  {
       type filter hook input priority filter; policy drop;
       ct state established,related counter accept
       iif lo ip daddr 127.0.0.1/8 counter accept
   }

   chain OUTPUT {
      type filter hook output  priority filter; policy drop;
      # allow established and existing traffic
      ct state established,related counter accept
      # allow DNS out
      udp dport 53 ct state new counter accept
      tcp dport 53 ct state new counter accept
      # allow HTTP/HTTS  out
      tcp dport { 80, 443 } ct state new counter accept
      udp dport { 80, 443 } ct state new counter accept
      # drop everyting else
   }

}
         

The reasons for the two DNS statements and the two HTTP/HTTPS statements are:

References:

  1. TCP vs UDP: What Is the Difference Between TCP and UDP?
  2. Why does DNS use UDP and not TCP?
  3. Is DNS TCP or UDP port 53?
  4. Matching connection tracking stateful metainformation
  5. Sever Fault - Good Examples of CT States
  6. MokroTik: Firewall - Default Config & Basic for Dummies
  7. MokroTik Firewall Basic Concept

Matching Both TCP & UDP with Transport Header (th)

Altought, tcp and udp are seperate protocols with their own headers, the raw location of dport is the same in both headers. This allows merging of dport statements that only differ in whether the protocol is tcp or udp.

For example the two statements:

 tcp  dport 53 accept
 upd dport 53 accept

Can be replaced by the single statement:

meta l4proto {tcp,udp} th dport 53 accept

The first part of the statement, meta l4proto {tcp, udp}, ensures that it is a level 4 protocol - either tcp or udp and excludes other level 4 protocols. The second part of the statement, th dport 53, does a raw read of the transport header (th) and checks to see if dport matches 53.

With the transport header raw read feature, four lines in the previous example can be condensed to one line:


#!/usr/bin/nft -f

flush ruleset

table ip transport {

   chain INPUT  {
       type filter hook input priority filter; policy drop;
       ct state established,related counter accept
       iif lo ip daddr 127.0.0.1/8 counter accept
   }

   chain OUTPUT {
      type filter hook output  priority filter; policy drop;
      # allow established and existing traffic
      ct state established,related counter accept
      # allow DNS, HTTP and HTTP out
      meta l4proto {udp,tcp} th dport { 53, 80, 443 } ct state new counter accept
      # drop everyting else

   }

}
         

This should be more efficent and faster than executng four sepearate tests.

References:

  1. Wikipedia - Google's QUIC Protocol.
  2. StackExchange - How to match both UDP and TCP for given ports in one line with nft.
  3. Mankier.coom - Raw Payload Expression
  4. LWN.net - nftables 0.83 Release
  5. How-to-Greek - How to Block Facebook (or Any Distracting Website)

You can use variables:

define accepted_input_ports = {22,5900}

tcp dport $accepted_input_ports accept

Advance Named Sets - Limit Connection Attemps

As previously stated, you can used nft rules to add elements to a named set. The example below illustrates this and the use of timers.

Example 5 - If an IP address attemps to SSH into a machine more than twice in one minute, the IP address is locked out for 24 hours. This is the last example in referencet [1]. The code is below:


#!/usr/sbin/nft -f

flush ruleset

table ip filter {

    set stage1 {
        typeof ip saddr
        flags timeout
    }

    set stage2 {
        typeof ip saddr
        flags timeout
    }

    set stage3 {
        typeof ip saddr
        flags timeout
    }
    chain input {
        type filter hook input priority filter; policy accept;
        # allowed established and related traffic
        ct state related,established accept
        # do not lock yourself out
        ct state new ip saddr 172.27.96.76 tcp dport 22 accept

        # To understand the lockout code below, read it in the order of the comments numbers
        ct state new ip saddr @stage2 tcp dport 22 add @stage3 { ip saddr timeout 1d } #3
        ct state new ip saddr @stage1 tcp dport 22 add @stage2 { ip saddr timeout 1m } #2
        ct state new tcp dport 22 add @stage1 { ip saddr timeout 1m } #1
        ct state new ip saddr @stage3 tcp dport 22 drop #4
    }    
}

         

You can test this by trying to SSH into the machine, and when prompted for the password, type in an incorrect passwords three times. Then try to SSH into the machine a second time. Again type in an incorrect password three times. If you attempt to SSH into the machine for a third time, it wil NOT prompt you for a password. You are now locked out for 24 hours.

Thus if you know nftables, you do not need a program such as fail2ban that has dependaces.

You do not need to lock an IP address out for one day to prevent a brute force attach. Five minutes is probably sufficent. I belive the one day is so you can see that someone tried to attach this machine.

Example 2

There is a Red Hat example that claims to limit login attempts. See reference [2]. The code us below:


#!/usr/sbin/nft -f

flush ruleset

table ip filter {

    set denylist {
        type ipv4_addr
            flags dynamic, timeout; timeout 5m;
    }

    chain input {
        type filter hook input priority 0;

        # I changed the rate from over 10/min to over 2/min for testing
        # I am not sure what "untracked" does, if anything,  in the statement below.
        ip protocol tcp ct state new,untracked limit rate over 2/minute add @denylist {ip saddr}

        ip saddr @denylist drop
    }
}
         

When you attemp to remotely SSH into a machine, you usally get three (3) attemps to type in the correct password, before you have to SSH into the machine again. I was not able to type an incorrect password fast enought for my IP address to be placed in the denylist. However, when prompted for the password, you can hit Control-C, and then you will have to SSH again into the machine. This method took six (6) attemps before my IP address was put into the denylist. On at least one occassion it took seven (7).

The Youtube video in reference [3], uses the same code except he changed the rate to 3/minute. It took him seven (7) attemps before his IP address was placed into the denylist.

References:

  1. Mastering Nftables Sets: A Comprehensive Guide - Linux Cloud Hacks
  2. Red_Hat - Using nftables to limit the amount of connections
  3. YouTube - Block SSH brute force attacks for 5 minutes using nftables on Linux firewall
  4. Wiki Sets
  5. Unleashing the Power of Nftables Chains and Verdict Maps - Linux Cloud Hacks
  6. Matching connection tracking stateful metainformation
  7. Mikrotik - Firewall Basic Concepts

Port Knocking

References:

  1. How to Secure SSH with Port Knocking and Nftables on CentOS 8 - The Urban Penguin


#!/usr/sbin/nft -f

flush ruleset

table inet filter {

    set ssh_clients {
        type ipv4_addr 
        flags timeout
    }

    chain input {
        type filter hook input priority filter; policy accept;

        # do not lock myself out
        ct state new ip saddr 192.168.37.45 tcp dport 22 accept
        # accept loopback trafic
        iif "lo" accept
        # allowed established and related traffic
        ct state related,established accept

        tcp dport 10 add @ssh_clients {ip saddr timeout 30s}
        ip saddr @ssh_clients tcp dport ssh accept
        drop
    }    
}


Port Knocking with Limited Attemps

Sets - type or typeof

Concatenation

False add @ { } statements before drop or accept

Reference

  1. Wiki nftables.org - Concatenation
  2. ServerFault - nftables set of couples { IP/MAC address }
  3. Using nftables in Red Hat Enterprise Linux 8 - Set Concatenatiion

LAN Monitoring

nft script:


#!/usr/sbin/nft -f

flush ruleset

table ip filter {

# 11monitor.nft - version 11
#
# The following code helps you to idenify tcp, udp and icmp traffic 
# on your local area network. 
#
# For now (7-22-2022), I have decided to implement this only for IPv4 traffic
# and to drop, all IPv6 traffic.

    set log_tcp_traffic { typeof ip saddr . tcp sport . ip daddr . tcp dport; }
    set log_udp_traffic { typeof ip daddr . udp dport . ip saddr . udp sport; }
    set log_ct_established_tcp { typeof ip daddr . tcp dport . ip saddr . tcp sport; }
    set log_ct_established_udp { typeof ip daddr . udp dport . ip saddr . udp sport; }
    set log_ct_invalid { typeof ip saddr; }
    set log_loopback_traffic { typeof ip saddr; }
    set log_loopback_bad { typeof ip saddr; }
    set log_pings { typeof ip saddr; }
    set log_dropped { typeof ip saddr; }

chain input {
        type filter hook input priority filter; policy accept;

        #log source ip of all input
        add @log_tcp_traffic { ip daddr . tcp dport . ip saddr . tcp sport}
        add @log_udp_traffic { ip daddr . udp dport . ip saddr . udp sport }

        # do not lock myself out - allow local network
        ct state new ip saddr 192.168.37.0/24 accept

        # allowed established and related traffic
        ct state related,established add @log_ct_established_tcp \
            {ip daddr . tcp dport . ip saddr . tcp sport} accept
        ct state related,established add @log_ct_established_udp \
            {ip daddr . udp dport . ip saddr . udp sport} accept
        ct state invalid add @log_ct_invalid {ip saddr} drop

        # accept loopback trafic
        iif "lo" add @log_loopback_traffic {ip saddr} accept

        # loopback bad - I am currently working on this
#        iff != lo  ip daddr 127.0.0.1/8 add @loopback_bad {ip saddr} drop
#        iff != lo ip6 daddr ::1/128 drop
       
        # allow pings but limit the rate  
        ip protocol icmp limit rate 1/second add @log_pings {ip saddr} accept

        # log dropped traffic
        add @log_dropped {ip saddr} drop
    } 

	chain forward {type filter hook forward priority filter; policy drop; }

	chain output { type filter hook output priority filter; policy accept; }
    
}

# Drop all IPv6 Traffic:
table ip6 filter {
	chain input   { type filter hook forward priority filter; policy drop; }
	chain forward { type filter hook forward priority filter; policy drop; }
	chain output  { type filter hook output priority filter; policy drop;  }
}

Why Semicoln in single line set definations?

References:

  1. Beginners guide to traffic filtering with nftables - Break Long Lines
  2. Wikipedia - Multicast Addresses
  3. Wikipedia - Web Services Dynamic Discovery (WS-Discovery
  4. Wikipedia - Internet Group Management Protocol (igmp)
  5. WireShark UDP Port 52217
  6. Raspberry Pi Forum - Default TCP/UDP Ports for Default RP Services
  7. Greeks-for-Greeks - Introduction of Message Queue Telemetry Transport Protocol (MQTT)
  8. Wikipedia - MQTT
  9. MQTT.org - The Standard for IoT Messaging

Source Network Address Translation (SNAT)

References

  1. Mastering Source Network Address Translation (SNAT) in Nftables - Linux Cloud Hacks

Destination Network Address Translation (DNAT)

References

  1. Unlock the Power of Nftables: Mastering Destination Network Address Translation (DNAT) - Linux Cloud Hacks

Debian's - Default nftable and chains

systemctl start nftables

nft list ruleset

table inet filter {
  chain input {
    type filter hook input priority filter; policy accept;
 }
 
  chain forward {
    type filter hook forward priority filter; policy accept;
# only routers should forward packets
counter drop
 }
 
  chain output {
  type filter hook output priority filter; policy accept;
 }

-- -- -- -- -- -- -- -- -- -- --

To stop nftables from doing anything, just drop all the rules:

nft flush ruleset

To uninstall it and purge any traces of nftables in your system:

aptitude purge nftables

///////////////////////////

nft Kernel Modules

modinfo nf_tables

Show Active nftables Kernel Module

lsmod | grep nf_tables

///////////////////////

Disable iptables

iptables -F

ip6tables -F

Raspberry Pi Updater

I wanted to lock down a Raspberry Pi and only allow https traffice (443) out to a specfic IP address or domain and only accept input traffic that was only established or related (connection tracking state).

What I learned was the Raspberry Pi uses plain http (80) for updating the OS.

I also learned that you can prevent checking for updates on boot and checking for updates every 24 hours by removing the updater from the taskbar. To do this righ click on the taskbar, click on the updater and remove it.

Network Time Protocol (NTP)

The NTP uses UDP port 123

Raspberry Pi NTP

If your Raspberry Pi has a real time clock, you can disable NTP traffic with the command [8][9]:

timedatectl set-ntp false

To re-enable NTP:

timedatectl set-ntp true

References

  1. Wikipedia - Network Time Protocol
  2. How to Set Up Time Synchronization on Debian
  3. Raspberry Pi Tips - How to Sync Time with a Server on Raspberry Pi
  4. PiMyLifeUp - Syncing Time from the Network on the Raspberry Pi
  5. Setting Up NTP on Raspberry Pi “Bookworm” for Accurate Timekeeping
  6. The Geek Diary - host: command not found
  7. StackExchange - Why does NTP require bi-directional firewall acess to UDP port 123?
  8. Raspberry Pi Forum - How to Disable System Time Update?
  9. timedatectl(1) — Linux manual page

Debian and Derivatives Repositories

For updating the OS and installing packages (software), Debian uses the Advanced Package Tool (APT).

Debian Repositories

The Debian repository listing are at:


/etc/apt/sources.list
            

For Debian 12 Bookworm, they are:

http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware
http://deb.debian.org/debian-security/ bookworm-security main contrib non-free non-free-firmware
http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware

The IP addresses for these url's must pass through your firewall.

You can use dnschecker.org or other internet tools to find the IP addresses [2][3]. They are:

151.101.2.132, 151.101.66.132, 151.101.130.132, 151.101.194.132

Derivatites Repositories

The repository listing for Debian derivatites and other packages are in the directory:


/etc/apt/sources.list.d
            

I am runing Raspberry Pi OS (Bookworm), and the entry is reaspi.list The url for this is:

http://archive.raspberrypi.com/debian/ bookworm main

and the IP addresses for this single url are:

176.126.240.167, 176.126.243.6, 46.235.231.151, 46.235.231.111, 93.93.135.118, 93.93.135.141, 93.93.135.117, 176.126.240.86, 176.126.240.84, 46.235.231.145, 176.126.243.5, 176.126.243.3

The IP addresses for the Raspberry Pi are hosted by Mystic Beast LTD in the UK.

Packages Repositories

In my /etc/apt/soures.list.d directory, there is also an entry for NordVPN: nordvpn-app.list. The url for this is:

https://repo.nordvpn.com/deb/nordvpn/debian stable main

and the IP addresses for this url are:

104.19.159.190, 104.16.208.203

To recieve OS updates and install new packages (software) all of these IP addresses must pass through all firewalls.

Hit:1 http://deb.debian.org/debian bookworm InRelease
Get:2 http://deb.debian.org/debian-security bookworm-security InRelease [48.0 kB]
Get:3 http://deb.debian.org/debian bookworm-updates InRelease [55.4 kB]
Hit:4 https://repo.nordvpn.com/deb/nordvpn/debian stable InRelease
Get:5 http://archive.raspberrypi.com/debian bookworm InRelease [55.0 kB]
Get:6 http://deb.debian.org/debian-security bookworm-security/main armhf Packages [253 kB]
Get:7 http://deb.debian.org/debian-security bookworm-security/main arm64 Packages [268 kB]
Get:8 http://deb.debian.org/debian-security bookworm-security/main Translation-en [163 kB]
Get:9 http://archive.raspberrypi.com/debian bookworm/main armhf Packages [547 kB]
Get:10 http://archive.raspberrypi.com/debian bookworm/main arm64 Packages [547 kB]
            

References

  1. Raspberry Pi Forum - [solved] apt update problem with repositories
  2. dnschecker.org
  3. https://dnschecker.org/all-dns-records-of-domain.php
  4. Raspberry Pi Security Hardening Complete Guide

Lockdown a Raspberry Pi Zero 2W

The Raspberry Pi Zero 2W only has 512 MB (1/5 GB) of RAM memory. This is not large enough to run a modern web browser. It is primarily used without a GUI.

Below is a skeleton nftable for locking down a raspberry Pi Zero 2W. Note that https traffic is blocked.


#!/usr/bin/nft -f

# Pi Zero 2W - CUPS Print Server - lockdown updated on 2025-08-08

flush ruleset

table ip zero31 {

# ----------------------------------------------------------------
# Customized the sets below for your network

define This_Host = 192.168.0.31

set ssh_allow_in {
	typeof ip saddr
	elements = { 192.168.0.70, 192.168.0.71 }
}

set ping_allow_in {
	typeof ip saddr
	elements = { 192.168.0.70, 192.168.0.34 }
}

set allow_printing_from {
	typeof ip saddr
	elements = { 192.168.0.70, 192.168.0.75 }
}

# Customize the sets below for your OS
set update_debian_bookworm {
	typeof ip daddr
	elements = { 151.101.2.132, 151.101.66.132, 151.101.130.132,
                     151.101.194.132, 146.75.94.132, 199.232.66.132,
                     151.101.46.132, 146.75.126.132, 199.232.90.132,
                     151.101.18.132, 146.75.106.132, 151.101.22.132,
                     151.101.114.132, 199.232.98.132, 199.232.38.132,
                     146.75.42.132, 151.101.162.132, 151.101.202.132,
                     146.75.78.132, 151.101.134.132, 151.101.14.132,
                     151.101.74.132, 151.101.50.132, 151.101.250.132,
                     146.75.122.132
                   }
}

set update_pi_bookworm {
	typeof ip daddr
	elements = { 176.126.240.167, 176.126.243.6, 46.235.231.151,
                     46.235.231.111, 93.93.135.118, 93.93.135.141,
                     93.93.135.117, 176.126.240.86, 176.126.240.84,
                     46.235.231.145, 176.126.243.5, 176.126.243.3
                    }
}
#--------------------- End Customization ---------------------------

set unknown_udp {typeof ip saddr . udp sport . ip daddr . udp dport ; }
set unknown_tcp {typeof ip saddr . tcp sport . ip daddr . tcp dport ; }
set unknown { typeof ip saddr . ip daddr ; }

   chain INPUT  {
       type filter hook input priority filter; policy drop;

       # establlihed traffice
       ct state established,related counter accept

       # local traffice
       iif lo ip daddr 127.0.0.1/8 counter accept

       # allow only certain ip addresses to ping and limit rate
       ip saddr @ping_allow_in icmp type echo-request limit rate 5/second accept;

       # allow only certain ip addresses to SSH into this machine
       ip saddr @ssh_allow_in tcp dport ssh accept

      # allow only certain ip addresses to print
      ip saddr @allow_printing_from ip daddr $This_Host tcp dport 631 accept

      drop # everyting else
   }

   chain OUTPUT {
      type filter hook output  priority filter; policy drop;

      # allow established and existing traffic
      ct state established,related counter accept

      # allow DNS out - need for OS updates
      udp dport 53 ct state new counter accept
      tcp dport 53 ct state new counter accept

      # Debian Updates
      tcp dport 80 ip daddr @update_debian_bookworm counter accept
      # Raspberry Pi Updates
      tcp dport 80 ip daddr @update_pi_bookworm counter accept

      # Network Time Protocol
      udp dport 123 counter accept

     # Multicast DNS
     udp dport 5353 counter drop

     # WS-Discovery (Web Services) used by HP Printers [1]
     ip daddr 239.255.255.250 udp dport 3702 counter drop

     # Internet Group Management Protocol
     ip protocol igmp counter drop

     # drop unkown udp
     udp dport {1-65535} add @unknown_udp {ip saddr . udp sport . ip daddr . udp dport} \
     counter drop

     # drop unkown tcp
     tcp dport {1-65535} add @unknown_tcp {ip saddr . tcp sport . ip daddr . tcp dport} \
     counter drop

     # Drop Everything Else - non-tcp/udp
     add @unknown {ip saddr . ip daddr} counter drop
   }

}
       

References

  1. Wikipedia - Web Services Dynamic Discovery (WC-Discovery)

NordVPN

Nordvpn 4.0 still uses iptables. However, Nordvpn 4 is the first version that I observed that plays nicely with nftables. That is, all of the previous version flushed all of the nft tables and rules.

NordVPN/NordLynx

NordVPN/Lynx is based on WireGuard, which only uses one port [4]: UDP 51280. Well, it may also use tcp port 8884. See Port 8884 sub-section below.

NordVPN/OpenVPN

Nordvpn implementation of OpenVPN uses the following ports:[4]

According to Wikipeda [2], NordVPN no longer uses the following porotocls: L2TP, IPSec and PPTP

NordVPN uses TCP Port 8884

As stated in reference [5] NordVPN does use tcp port 8884. I assume the reference is correct in stating that NordLynx uses tcp port 8884. I trapped the port using nftables, and when I unstalled NordVPN, I had no more uses of tcp port 8884.

References

  1. Wikipedia - NordVPN
  2. Wikipedia - WireGuard
  3. WireGuard - Changing port from 51820 to 443/80 or other for Public Wifi Spots (McDonald's, Tims, etc)?
  4. NordVPN Support - What open ports does NordVPN offer?
  5. DD-WRT - Obtaining Configuration files for NordVPN's NordLynx?
  6. NordVPN Support - Installing NordVPN on Linux distributions

Lock Down Raspberry Pi 5B

 
#!/usr/bin/nft -f

# lockdown4c.nft

# Last Updated on 7/29/2025 
#
# Investigate 192.168.37.112 and 192.168.37.113, 
# these showed up on test machine 54 before I removed avahi
# these ipv4 addresses do not respond to pings.
#
# Add a black list set - everything above 100..254 
# Windows 11 PC 90 or 91, iphone 80 or 81 etc.
#
# Change to "accept NTP protocol" need to update OS.
#
# Add use NordVPN DNS Servers 
#
# The NetBIOS may be need for Samba File Sharring but I want to block here!


table ip fb {

set update_debian { 
	typeof ip daddr 
	elements = { 151.101.2.132, 151.101.66.132, 151.101.130.132, 
                     151.101.194.132, 146.75.94.132, 199.232.66.132, 
                     151.101.46.132, 146.75.126.132, 199.232.90.132, 
                     151.101.18.132, 146.75.106.132, 151.101.22.132,
                     151.101.114.132, 199.232.98.132, 199.232.38.132,
                     146.75.42.132, 151.101.162.132, 151.101.202.132,
                     146.75.78.132, 151.101.134.132, 151.101.14.132,
                     151.101.74.132, 151.101.50.132, 151.101.250.132
                   }
}

set update_pi { 
	typeof ip daddr 
	elements = { 176.126.240.167, 176.126.243.6, 46.235.231.151,
                     46.235.231.111, 93.93.135.118, 93.93.135.141,
                     93.93.135.117, 176.126.240.86, 176.126.240.84,
                     46.235.231.145, 176.126.243.5, 176.126.243.3
                    }
}

set update_nordvpn { 
	typeof ip daddr 
	elements = { 104.19.159.190, 104.16.208.203 }
}



set input_dropped_udp { typeof ip saddr . udp sport . ip daddr . udp dport; }
set input_dropped_tcp { typeof ip saddr . tcp sport . ip daddr . tcp dport; }

set input_dropped_dropbox  { typeof ip saddr . udp sport . ip daddr . udp dport; }
set input_dropped_scansnap { typeof ip saddr . udp sport . ip daddr . udp dport; }
set input_dropped_mDNS { typeof ip saddr . udp sport . ip daddr . udp dport; }

set input_samba_NetBIOS { typeof ip saddr . udp sport . ip daddr . udp dport; }

set  output_dropped_ntp { typeof ip saddr . udp sport . ip daddr . udp dport; }
 
set  output_dropped_mDNS { typeof ip saddr . udp sport . ip daddr . udp dport; }

set dropped_tcp_http  { typeof ip saddr . tcp sport . ip daddr . tcp dport; }
set dropped_udp_http  { typeof ip saddr . udp sport . ip daddr . udp dport; }
set dropped_input_tcp { typeof ip saddr . tcp sport . ip daddr . tcp dport; }


set output_https_tcp  { typeof ip saddr . tcp sport . ip daddr . tcp dport; }

set vlc_tcp_drop { typeof ip saddr . tcp sport . ip daddr . tcp dport; }
set vlc_udp_drop { typeof ip saddr . udp sport . ip daddr . udp dport; }


set unknown_protocol { typeof ip saddr . tcp sport . ip daddr . tcp dport; }
set unknown_tcp { typeof ip saddr . tcp sport . ip daddr . tcp dport; }

    chain INPUT {
        type filter hook input priority filter; policy drop;
        meta iif lo counter accept

        ct state established,related counter accept
        ct state invalid counter drop

        # To accept ssh input, uncomment the next 2 lines 
        tcp dport 22 counter accept
        udp dport 22 counter accept

        # To accept vnc input, uncomment the next 2 lines
        tcp dport  5900 counter accept
        udp dport  5900 counter accept

        # Internet Printing Protocol
        tcp sport 631 accept
        tcp dport 631 accept

        # To accept Dropbox input, in the the line below change drop to accept
        udp dport 17500 counter drop

        # Drop Fujitsu's Scansnap - Page Scanner
        udp dport 52217 counter drop

        # To accept Multicast DNS, change drop to accept
        udp dport 5353  add @input_dropped_mDNS \
           { ip saddr . udp sport . ip daddr . udp dport } counter drop

        # To accept NetBIOS, change drop to accept
        udp dport {137,138} add @input_samba_NetBIOS \
          { ip saddr . udp sport . ip daddr . udp dport } counter accept

        ip protocol tcp add @input_dropped_tcp \
           { ip saddr . tcp sport . ip daddr . tcp dport } counter drop
        ip protocol udp add @input_dropped_udp \
           { ip saddr . udp sport . ip daddr . udp dport } counter drop

        # ping
        ip protocol icmp counter drop
        
        # Drop Unknown Input
        counter drop
     } # close INPUT chain


     chain OUTPUT {
        type filter hook output priority filter; policy drop;

# Accept List
        # Debian Updates 
        tcp dport 80 ip daddr @update_debian counter accept

        # Raspberry Pi Updates
        tcp dport 80 ip daddr @update_pi counter accept

        # NordVPN Updates 
         tcp dport 80 ip daddr @update_nordvpn counter accept

        # Nordvpn-NordLynx 
        udp dport 51820 counter accept
        tcp dport 8884 counter accept # Require but not documented

        # NordVPN OpenVPN
        udp dport 1194 counter accept
        udp sport 1194 counter accept

        # Network Time Protocol
        udp dport 123 counter accept

        # Secure Shell Protocol ssh
        tcp dport 22 counter accept
        udp dport 22 counter accept

       # NetBIOS 
       tcp dport 139 counter accept

       # SMB Protocol
       tcp dport 445 counter accept

       # VNC Protocol
       tcp dport 5900 counter accept
       udp dport 5900 counter accept

       # Domain Name Servers 
       udp dport 53 counter accept
       tcp dport 53 counter accept 
        
       # Pi-Hole
       ip daddr 192.168.37.32 tcp dport 80 counter accept

        # Should ct state new be added to the https below?
        #
        # accept https
        tcp dport 443 counter add @output_https_tcp \
            { ip saddr . tcp sport . ip daddr . tcp dport } accept
        udp dport 443 counter accept

        # Local Traffic
        oif lo counter accept
        iif lo counter accept

        # Establishd Traffic
        ct state established, related counter accept

# Dropped List:
        # ICMP Protocol
        ip protocol icmp counter accept

        # Multicast DNS
        udp dport 5353 add @output_dropped_mDNS \
            {ip saddr . udp sport . ip daddr . udp dport} \
            counter accept

        # Internet Printing Protocol
        tcp dport 631 accept 
        tcp sport 631 accept

        # vlc traffic
        tcp dport {1935, 554, 8554} add @vlc_tcp_drop \
             { ip saddr . tcp sport . ip daddr . tcp dport } \
             counter drop
        udp dport {1234, 5004, 7001} add @vlc_udp_drop \
             { ip saddr . udp sport . ip daddr . udp dport } \
             counter drop

        # http traffic
        tcp dport 80 add @dropped_tcp_http \
             { ip saddr . tcp sport . ip daddr . tcp dport } \
             counter drop
         udp dport 80 add @dropped_udp_http \
             { ip saddr . udp sport . ip daddr . udp dport } \
         counter drop

         # Unknown https traffic
        udp dport {1-1023} counter drop
        udp dport {1024-65535} counter drop

        tcp dport {1-1023} add @unknown_tcp \
           {ip saddr . tcp sport . ip daddr . tcp dport } counter drop
        tcp dport {1024-65535} add @unknown_protocol \
           {ip saddr . tcp sport . ip daddr . tcp dport} counter drop

        # Everything Else
        counter drop
      } # close OUPUT chain
}

        

This last line in this nftable table revielded that some package was running a non-tcp/IP protocol. I still do not know what the protocol is, but I traced it to the avahi package.

To remove the avahi package [5]:

sudo apt remove --purge avahi-daemon

Firefox (Mozilla) Browser Configuration Settings

Next, I observed that as soon as I opened the FireFox browser that I had unsecure http traffic going to IP 34.107.221.82.

There is not a lot on the Internet on this, but this is the IP address that Firefox uses to determine if you are accessing the Interne through a captive portal [2]. When accessing the Internet through a captive portal, additional authorization is required. An example of a captive portal is Internet service provided by a hotel. To access the Internet, you first have to sign in and provide your room number and a pass code. Your browser has to detects this, in order to show the hotel's authorization page [3].

If you do a dns lookup on the url: https://detectportal.firefox.com, it will return the IP address: 34.107.221.82.

To prevent Firefox from checking for a captive portal:

In the search box type about:config

Click "Accept the Risk and Continue".

Then, search for "portal" and set "network.captive-portal-service.enabled" to false.

Then, search for "network.connectivity-service.enabled", and toggle it to false [5].

While you are at it, there are a few more setting you need to disable for privacy [4] :

To prevent Web Real Time Communicatins (WebRTV), search for "media.peerconnection.enabled, and toggle it to false.

To disable experiments or studies, search for app.normandy.enabled and toggle it to false.

References

  1. Disable or remove unneeded services and software to help keep your Raspberry Pi online
  2. Disable Firefox Captive Portal and Connection to detectportal.firefox.com
  3. Firefox trying to connect multiple times to a site on Port 80 about every 30 seconds
  4. Mozilla Suport - How to stop Firefox from making automatic connections
  5. IPs insead URLs in sources-list
  6. Managing Repositories like a Pro: An In-Depth Guide for Debian and Ubuntu
  7. Local IP port redirection using Linux nftables
  8. Nftables.org Wiki - Data types