Chapter 30 closes with an explicit promise: our second layer of defense in depth, namely the firewall, becomes the full focus of this chapter. Throughout this series, we have actually repeatedly run sudo ufw allow without ever discussing where that command comes from or where it actually goes inside the kernel. Custom SSH ports in Chapter 3, DNS and DHCP in Chapter 9 and Chapter 10, web servers and databases in Parts IV and V, up to Samba and NFS in Part VI, all have stacked waiting ufw allow rules, but UFW itself has never really been officially activated. This chapter completes that activation, while answering two pending promises: the reason why Docker containers in Chapter 27 could bypass UFW even though their ports were not allowed yet, and a deep discussion regarding application profiles that we postponed in Chapter 24.
We will start from the evolution of Linux firewall architecture, from classic iptables to nftables as its successor, to understand UFW's position between the two. After that, we get into basic iptables concepts that are still relevant to read in many old production scripts, officially activate UFW complete with rate limiting and custom application profiles, try native nftables syntax along with its migration method, summarize all firewall rules for services we installed throughout this series, and close with firewall logging and monitoring as preparation for detecting intrusion attempts before Chapter 32 discusses Fail2ban.
31.1 Evolution of Linux Firewall Architecture: from iptables to nftables
Before touching any command, it is good to understand the big picture first: the reason why Ubuntu Server 26.04 still recognizes iptables as well as nftables, and the way both relate to UFW which we have secretly used for a long time.
31.1.1 Netfilter, iptables, and Their Limitations
Netfilter is a framework inside the Linux kernel that provides hook points along the network packet path, starting from when a new packet enters a network interface until it leaves again, where filtering modules can inspect and decide the fate of each packet. iptables (along with its siblings ip6tables for IPv6, arptables, and ebtables) is a classic userspace utility that talks to Netfilter via an old framework called x_tables. For over two decades, iptables became the de facto standard of Linux firewalls, but its architecture carries real limitations: a separate utility for each protocol family, rules are evaluated one by one linearly from top to bottom without a built-in fast search data structure, and there is no truly atomic ruleset reload mechanism, making swapping many rules at once risky due to leaving brief windows during execution.
31.1.2 nftables as the Successor and iptables-nft as a Bridge
nftables entered the mainline Linux kernel in version 3.13 (2014) as the official successor to Netfilter, designed to address all the above limitations: one single utility nft for all protocol families via table family concepts (ip, ip6, inet for dual-stack simultaneously, arp, bridge, netdev), native support for sets and maps with O(1) lookup instead of linear, atomic ruleset reloads, and a declarative syntax that is much easier to read compared to series of short flags ala iptables.
Its transition in Ubuntu ran gradually. Since Ubuntu 16.04 LTS, the iptables package included a compatibility wrapper called iptables-nft that translates old iptables syntax into nftables rules behind the scenes. Since Ubuntu 20.10, this wrapper became the default backend: running iptables commands on Ubuntu Server 26.04 actually invokes iptables-nft, managed via the update-alternatives system, while the old backend is still available as iptables-legacy for specific kernel module compatibility cases. Both backends keep completely separate rulesets, so mixing both on the same server will only cause self-confusion during troubleshooting.
This fact also explains UFW's position more precisely. UFW calls iptables/ip6tables binaries to apply every rule we write, and because those binaries are now iptables-nft, UFW rules are ultimately saved as nftables data structures in the kernel. However, UFW itself, up to the writing of this series, does not have a native backend that talks directly to nft syntax. So if anyone says "UFW generates nftables rules", it does not mean UFW writes nft syntax directly, but rather UFW continues to write iptables-style rules which are then automatically translated by iptables-nft into nftables entries in the kernel.
| Aspect | iptables (x_tables / legacy) | nftables |
|---|---|---|
| Utility per protocol family | Separate: iptables, ip6tables, arptables, ebtables | One utility nft for all families |
| Rule evaluation | Linear, read one by one from top to bottom | Supports set/map with O(1) lookup |
| Ruleset reload | Not natively atomic | Atomic via nft -f |
| Syntax style | Procedural, many short flags | Declarative, closer to block structures |
Practical Steps
- Check the currently active
iptablesbackend on our server.iptables -V - Inspect the alternatives symlink determining that backend choice.
sudo update-alternatives --display iptables
Verification and Troubleshooting
- The output of
iptables -Von Ubuntu Server 26.04 contains the(nf_tables)indication, signaling that its active backend is indeediptables-nft, notiptables-legacy. - If the output instead displays
(legacy), the server was likely directed manually to the old backend viaupdate-alternatives --config iptables, either by a third-party software installation requiring it or a previous configuration error. Revert it to the option pointing toiptables-nftunless there is a strong reason to maintain the legacy backend.
31.2 iptables: Basic Concepts and Real Case Behind Docker
Even though Ubuntu 26.04 uses nftables behind the scenes, mastering iptables syntax remains worthwhile for three concrete reasons: it is still widely used in legacy automation scripts like Ansible playbooks or old vendor images, it is the language used by UFW as we just discussed, and it is used directly by Docker to manipulate the firewall without UFW's knowledge, a real case that forms the focus of this section.
31.2.1 Anatomy of Tables, Chains, and Rules
iptables organizes rules into tables, each table contains a set of chains, and each chain contains a list of rules. The default table named filter contains three built-in chains: INPUT for packets destined for this host itself, OUTPUT for packets originating from this host, and FORWARD for packets merely passing through or routed through this host, a chain directly relevant to the Docker case in Section 31.2.3. Each chain has a default policy (ACCEPT, DROP, or REJECT) that applies if no rule matches, and rule lists inside are evaluated sequentially from top to bottom until the first matching rule is found (first-match-wins). A single rule consists of matching criteria, such as -p for protocol, --dport for destination port, -s for source IP, combined with targets via the -j flag such as ACCEPT, DROP, REJECT, or LOG. The filter table is not the only table known to iptables. The nat table, with PREROUTING and POSTROUTING chains, specifically handles address translation such as port forwarding, and this exact table is what Docker uses to publish container ports, not the filter table where UFW operates, as we will see directly in Section 31.2.3.
31.2.2 Reading and Testing Rules Directly
The safest practice to start getting acquainted with iptables is to read the running ruleset first, then try adding and deleting practice rules that break nothing.
Practical Steps
- View all active rules in the
filtertable chains along with packet counters matched by each rule.sudo iptables -L -n -v --line-numbers - Add a practice rule, for example allowing port 8443/tcp.
sudo iptables -A INPUT -p tcp --dport 8443 -j ACCEPT - Remove that practice rule. Re-run the command from step one to see which line number this new rule resides on, because
-Aalways appends rules at the end of the chain, then delete based on that number.sudo iptables -D INPUT 5
Verification and Troubleshooting
- Rules added via
-Alive only in kernel memory and disappear after the next reboot, unlike UFW rules which are stored permanently in/etc/ufw/user.rulesas discussed in Section 31.3.1. For rawiptablesrules needing to survive reboots, theiptables-persistentpackage provides thenetfilter-persistentservice which saves rulesets to/etc/iptables/rules.v4andrules.v6. - Avoid manually adding rules for the exact same port managed by UFW on the same server. Having two mechanisms managing identical ports from different sources will only confuse diagnostics when access issues occur later.
31.2.3 Case Study: DOCKER-USER Chain and Why Containers Can Bypass UFW
Section 27.2.3 gave a brief warning: if UFW is active, Docker Compose stack ports need to be allowed first. The reality is slightly more complex, and that is precisely where understanding iptables comes in handy. When a container publishes a port via the -p option in docker run or via the ports: block in docker-compose.yml, Docker Engine creates NAT rules in the PREROUTING chain of the nat table directing incoming packets to the internal container IP, then those packets pass through the FORWARD chain, not the INPUT chain where all UFW rules reside. Docker also creates two custom chains named DOCKER and DOCKER-USER, inserting jumps to both at the top of the FORWARD chain, so packets bound for containers are evaluated by Docker's chains long before UFW has a chance to intervene. Consequently, containers publishing ports remain accessible from outside even if UFW has never allowed those ports, because technically that traffic never passes through the path guarded by UFW.
Docker officially provides the DOCKER-USER chain specifically for this scenario. Docker intentionally never populates this chain automatically, making it safe for Sysadmins to insert custom access control rules that persist whenever the Docker daemon restarts.
Practical Steps
- Ensure this chain exists, meaning Docker Engine has previously run on this server as in Chapter 26 and Chapter 27.
sudo iptables -L DOCKER-USER -n -v - Restrict access to all containers publishing ports exclusively to trusted subnets, such as the office subnet
192.168.1.0/24consistently used since Chapter 11. The sequence of the following three commands is important because-Iinserts rules at the top, so the command executed last ends up at the very top. TheESTABLISHED,RELATEDrule is intentionally executed last to end up on the first line, because theFORWARDchain containingDOCKER-USERis also traversed by return traffic from connections initiated by the container itself to the internet, such as when runningapt-get updateinside it. Without this rule, lower blocks would cut off container outbound access as well, not just the inbound access we want to limit.sudo iptables -I DOCKER-USER -j DROP sudo iptables -I DOCKER-USER -s 192.168.1.0/24 -j ACCEPT sudo iptables -I DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT - Verify the order matches expectations.
sudo iptables -L DOCKER-USER -n -v --line-numbers
Verification and Troubleshooting
- The
ACCEPTrule forESTABLISHED,RELATEDtraffic must be on line 1, theACCEPTrule from trusted subnets on line 2, and theDROPrule on line 3. If the order is broken, both inbound access from trusted subnets and container outbound internet access can be dropped, as the first matching rule wins. - Rules in
DOCKER-USERpersist across Docker daemon restarts by design, but do not automatically persist across host reboots unless made persistent viaiptables-persistentas discussed in Section 31.2.2, or rewritten via boot automation. - Honest note on limitations: this solution operates at the raw
iptables/nftableslevel, entirely outside UFW's reach. Up to the writing of this series, UFW does not provide a native way to manage theDOCKER-USERchain, meaning combining Docker and UFW always requires extra manual steps like above, rather than relying solely onufw allowon published container ports.
31.3 UFW: Behind-the-Scenes Mechanics and Advanced Controls
After understanding raw iptables, we return to UFW, a tool secretly used since Chapter 3, but this time covering its inner workings and advanced features never touched before.
31.3.1 UFW as a Rule Generator and Official Activation
UFW (Uncomplicated Firewall) is a Python-based frontend that simplifies iptables/nftables syntax into concise commands, developed specifically for Ubuntu so daily firewall configuration does not require memorizing individual iptables flags. Every time we run ufw allow or ufw deny, UFW writes a new line to rule files in /etc/ufw/user.rules (and user6.rules for IPv6), regardless of whether UFW status itself is active or not. That is why all rules added since Chapter 3 remain saved and waiting, even though we never actually executed ufw enable. Once activated, UFW loads all those rule files via iptables-restore, forming its own chain structures such as ufw-before-input, ufw-user-input, and ufw-after-input, inserted into standard INPUT/OUTPUT/FORWARD chains.
Practical Steps
- Before activating anything, view all rules accumulated since Chapter 3. The standard
ufw statuscommand displays nothing except the wordinactivewhen UFW is off, so useshow addedwhich reads rules directly from configuration files.sudo ufw show added - Ensure the rule for the custom SSH port from Section 3.2.4 exists in that list before proceeding further. Losing SSH access due to a misconfigured firewall is one of the most common incidents experienced by Sysadmins, yet easiest to prevent.
- Officially activate UFW.
This command outputs the confirmation promptsudo ufw enableCommand may disrupt existing ssh connections. Proceed with operation (y|n)?. Typeyonly after confirming SSH rules are present in the previous step. - Verify full status along with default policies.
sudo ufw status verbose
Verification and Troubleshooting
- A healthy
ufw status verboseoutput showsStatus: active, followed byDefault: deny (incoming), allow (outgoing), disabled (routed), then the list of accumulated rules from previous chapters. - Prove that UFW rules actually materialize as real
iptableskernel rules, not just configuration on paper.sudo iptables -L ufw-user-input -n -v - If the SSH session drops right after enabling UFW, do not panic. Active connected sessions are usually not forcefully disconnected by rule changes immediately, but new sessions can fail to connect if SSH rules point to wrong ports or syntax. Console access from Section 3.4 remains the emergency path for this scenario.
31.3.2 Rate Limiting and Numbered Rule Management
Standard allow rules do not limit how frequently a single source IP attempts connections. Attackers running automated SSH brute-force attacks can try hundreds of username and password combinations in minutes without blockage, until Chapter 32 introduces Fail2ban as a log-detection solution. As a lightweight first line of defense, UFW provides built-in rate limiting mechanisms.
Practical Steps
- Apply rate limiting to our custom SSH port. Note that the ready-to-use alias
ufw limit sshorOpenSSHprofile points to standard port 22, not our customized port 2222 from Section 3.2.4, so we must specify the port explicitly.sudo ufw limit 2222/tcp - View all rules alongside line numbers.
sudo ufw status numbered - Delete a rule based on its line number, adjusting the number to match output from the step above.
sudo ufw delete 3 - Insert a new rule at a specific position, for instance blocking documentation subnet
203.0.113.0/24at position 1 before other allow rules are evaluated.sudo ufw insert 1 deny from 203.0.113.0/24 to any port 2222 proto tcp comment 'example suspicious subnet block'
Verification and Troubleshooting
- UFW
limitrules work by denying new connections from a source IP once that source attempts 6 or more connections within 30 seconds, a threshold not customizable within UFW itself. This limit is lenient enough for legitimate users occasionally mistyping passwords, but strict enough to slow down automated brute-force attacks. - Rule numbers shift whenever rules are deleted or inserted. Always re-run
ufw status numberedbefore runningdeleteorinsertcommands, rather than relying on stale output from previous checks.
31.3.3 Application Profile: Reading Built-in Profiles and Writing Custom Ones
Section 24.3 postponed in-depth UFW application profile discussions when opening Samba ports manually. Now is the time to finalize it, as well as build a custom profile for the Docker Compose stack from Chapter 27.
Practical Steps
- List all profiles registered on this server from various service installations throughout the series.
sudo ufw app list - Inspect details of the
Sambaprofile avoided in Section 24.3.
Output showssudo ufw app info SambaPorts: 137,138/udp|139,445/tcp. This is the technical reason Section 24.3 chose manualufw allow 445/tcpoverufw allow Samba: the default profile opens ports 137, 138, and 139 for NetBIOS, even though NetBIOS on our server was intentionally disabled in that section. Using default profiles there would reopen ports unused by active processes. - Create a new profile for the Docker Compose stack from Section 27.2, using port 8090 for static sites and 8091 for Adminer.
Populate the file with the following format.sudo nano /etc/ufw/applications.d/notes-compose[NotesCompose] title=Notes Compose Stack description=Static site and Adminer from Chapter 27 Docker Compose stack ports=8090,8091/tcp - Register the new profile with UFW and apply it.
sudo ufw app update NotesCompose sudo ufw allow NotesCompose
Verification and Troubleshooting
- Confirm the new profile reads correctly.
sudo ufw app info NotesCompose - Important note: the
allowrule above controls UFW'sINPUTchain, whereas per Section 31.2.3, containers publishing ports viaports:in Compose bypassINPUTand useFORWARDandDOCKER-USERchains instead. For the Chapter 27 Compose stack specifically, those containers were accessible from outside from the start, regardless ofallow NotesComposerules. Profiles remain useful as port documentation and take effect if the same port is later used by non-container processes listening directly on the host. For access control applying directly to port-published Compose containers, refer back to theDOCKER-USERapproach in Section 31.2.3.
31.4 Native nftables: Modern Syntax and Migration from iptables
UFW suffices for most daily needs in this series, but Sysadmins sometimes require granular control missing in UFW, such as named sets containing dynamic IPs or condition-based rules unsupported by UFW syntax. Native nftables, the official successor discussed in Section 31.1.2, fills that role.
Because UFW and native nftables rules share identical Netfilter hooks in the kernel, running both simultaneously as main firewalls can cause confusion or conflicts. Treat the following practice section as a standalone lab: temporarily disable UFW first, then reactivate it after experiments conclude, maintaining a single source of firewall truth.
31.4.1 Installation and Ruleset Anatomy: Tables, Chains, Rules
Practical Steps
- Temporarily disable UFW to avoid conflicts with upcoming experiments.
sudo ufw disable - Install the
nftablespackage if not present. Kernel and compatibility layers are likely active viaiptables-nft, but thenftutility and systemd unit require explicit installation.sudo apt install nftables - Create an
inputtable and chain with defaultdroppolicy. Theinetfamily is chosen to cover IPv4 and IPv6 simultaneously in one rule, unlikeiptablesrequiring separateip6tables. Thepriority 0value sets execution order relative to other hooks attached to theinputpoint, such as Docker or othernftables.serviceinstances; smaller values execute earlier, and 0 is standard for main filtering chains.sudo nft add table inet filter sudo nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; } - Add base rules: loopback, established or related connections, and SSH on our custom port.
sudo nft add rule inet filter input iif lo accept sudo nft add rule inet filter input ct state established,related accept sudo nft add rule inet filter input tcp dport 2222 accept
Verification and Troubleshooting
- Display the complete ruleset just created.
sudo nft list ruleset - Rule ordering inside base chains evaluates top-down just like
iptables. Loopback and established/related rules are placed at the top so high-frequency traffic matches early without traversing remaining rules, a small optimization noticeable on busy servers.
31.4.2 Persistent Ruleset in /etc/nftables.conf
Rules added via nft add exist only in kernel memory and vanish upon reboot, matching raw iptables in Section 31.2.2. nftables provides a cleaner method: defining declarative rulesets inside /etc/nftables.conf, loaded automatically by nftables.service systemd unit during boot.
Practical Steps
- Rewrite the rules above into a declarative file.
Populate the file as follows.sudo nano /etc/nftables.conf
The#!/usr/sbin/nft -f flush ruleset table inet filter { chain input { type filter hook input priority 0; policy drop; iif lo accept ct state established,related accept tcp dport 2222 accept ct state invalid drop } }flush rulesetdirective at the top ensures existing rulesets clear before loading, preventing duplicate rules in the kernel. A new line is included here,ct state invalid drop, absent in initial testing: this drops packets marked invalid by kernel connection tracking, such as out-of-order TCP fragments. Such packets almost always stem from port scans or corrupted packets, making early dropping safe before evaluation against lower rules. - Load the file and enable it as a service starting automatically at boot.
sudo nft -f /etc/nftables.conf sudo systemctl enable --now nftables.service
Verification and Troubleshooting
- Ensure the service runs and kernel rulesets match file contents.
sudo systemctl status nftables.service sudo nft list ruleset - On syntax errors,
nft -frefuses file loading and displays problematic line numbers, proving far more informative than legacyiptableserrors lacking context. - After completing experiments, revert the server state: disable
nftables.service, flush rulesets, and re-enable UFW as the sole active firewall.sudo systemctl disable --now nftables.service sudo nft flush ruleset sudo ufw enable
31.4.3 Migrating Legacy Rules with iptables-translate
A common operational scenario: Sysadmins inherit servers containing raw legacy iptables rules and consider full migration to nftables. Manually rewriting rules is error-prone and time-consuming. The nftables package includes iptables-translate, converting single iptables rules into equivalent nft syntax without manual flag mapping lookup.
Practical Steps
- Translate a sample rule to inspect conversion patterns.
This command outputs the equivalentiptables-translate -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPTnftline, roughly formatted asnft add rule ip filter INPUT tcp dport 22 ct state new counter accept, ready for execution or configuration file inclusion. - For full ruleset migrations saved via
iptables-save, use its restore translation variant to process entire files at once.sudo iptables-save > /root/rules-lama.v4 sudo iptables-restore-translate -f /root/rules-lama.v4
Verification and Troubleshooting
- Translated output must be reviewed manually before production deployment, particularly complex rules utilizing
iptablesmodules lacking directnftequivalents. This tool accelerates migration rather than replacing manual review entirely.
31.5 Firewall Rules for Common Services: Series Consolidation
Having understood underlying mechanisms, this section consolidates all firewall rules applied since Chapter 3 into a single concise reference usable without reviewing individual chapters.
31.5.1 Summary of Ports Opened Since Chapter 3
| Chapter | Service | Port / Protocol | UFW Rule |
|---|---|---|---|
| 3 | SSH (custom port) | 2222/tcp | ufw allow 2222/tcp |
| 9 | DNS (BIND9) | 53 (tcp & udp) | ufw allow 53 |
| 10 | DHCP | 67/udp | ufw allow 67/udp |
| 11 | NTP (chrony) | 123/udp, subnet restricted | ufw allow from 192.168.1.0/24 to any port 123 proto udp |
| 12 | HTTP (Nginx) | 80/tcp | ufw allow 80/tcp |
| 13 | HTTP & HTTPS (Apache) | 80, 443/tcp via profile | ufw allow 'Apache Full' |
| 17 | HTTPS (Let's Encrypt) | 443/tcp | ufw allow 443/tcp |
| 18 | PostgreSQL | 5432/tcp, subnet restricted | ufw allow from 192.168.1.0/24 to any port 5432 proto tcp |
| 19 | MySQL | 3306/tcp, subnet restricted | ufw allow from 192.168.1.0/24 to any port 3306 proto tcp |
| 24 | Samba | 445/tcp, manual non-profile | ufw allow 445/tcp |
| 25 | NFS | 2049/tcp, subnet restricted | ufw allow from 192.168.1.0/24 to any port 2049 proto tcp |
A notable pattern emerges from the table above. Services requiring wide public access, such as HTTP, HTTPS, and DNS, open without source restrictions. Internal services like databases and file sharing remain restricted to the trusted 192.168.1.0/24 subnet rather than Anywhere. This reflects consistent application of the least privilege principle introduced in Chapter 30 and expanded in Chapter 34.
31.5.2 Default Deny Incoming Principle and Safe Rule Ordering
UFW's default policy verified in Section 31.3.1, deny incoming, means the server automatically rejects all incoming traffic not explicitly permitted. This is a whitelist philosophy: closing all access first, opening ports individually per requirement, proving far safer than blacklist models opening all traffic and blocking offending sources post-incident. UFW handles this ordering automatically without requiring manual rule ordering, internally placing specific allow rules before broad default deny rules. In raw iptables or nftables, rule order management rests entirely on the admin, as demonstrated in Section 31.2.3 where ACCEPT rules were explicitly inserted above DROP rules to function correctly.
Verification and Troubleshooting
- Confirm active default policies.
sudo ufw status verbose - Field note: never change default outgoing policies to
denywithout careful preparation. Processes likeapt update, NTP synchronization, or outbound notifications will get blocked, often presenting subtle symptoms like failed package updates or gradual clock drift noticed long after changes.
31.6 Firewall Logging and Monitoring
Correct rules without visibility leave security risks. Firewall logs provide raw data showing active server connection attempts, including failed attacker probes blocked by rules established throughout this chapter.
31.6.1 UFW Logging Levels and Reading Log Entries
UFW supports five logging levels: off, low, medium, high, and full, with low active as default upon UFW enablement. The low level logs blocked packets matching no rules along with packets matching rules marked for logging, providing sufficient daily monitoring data without heavy disk usage.
Practical Steps
- Ensure logging is set to
low. While default since Section 31.3.1, explicit configuration aids documentation.sudo ufw logging low - Monitor logs in real-time.
sudo tail -f /var/log/ufw.log
Verification and Troubleshooting
- Locate rejected traffic entries using the
[UFW BLOCK]keyword.sudo grep 'UFW BLOCK' /var/log/ufw.log | tail -20 - If
/var/log/ufw.logis missing despite normalrsyslogoperation, inspect logs via journald.sudo journalctl -k | grep UFW - Levels
mediumand higher generate significant log volume by recording allowed traffic alongside blocks. Use higher levels for temporary troubleshooting sessions rather than permanent configurations to avoid quick disk saturation on busy systems.
31.6.2 Native Logging in nftables with Log Statements
When selecting native nftables per Section 31.4, logging does not run globally by default like UFW. Rules requiring logging must explicitly include log statements.
Practical Steps
- Add logging to the SSH rule in the
nftablestest ruleset from Section 31.4.1.sudo nft add rule inet filter input tcp dport 2222 log prefix "SSH-ATTEMPT " accept
Verification and Troubleshooting
- Monitor newly appended log entries via journald.
Look for lines prefixed withsudo journalctl -k -fSSH-ATTEMPTupon connection attempts to that port. - The
logstatement sits before final actions likeacceptordrop, passing packets to that action rather than replacing it. Statements attach to eitheracceptordroprules depending on monitored traffic. - For advanced analysis delegating logs to userspace consumers like
ulogd, append thegroupparameter tologstatements. Detailedulogdsetup falls outside this series' scope.
31.6.3 Case Study: Identifying Port Scanning Patterns from Logs
In production, repeated UFW BLOCK entries sharing identical source IPs across sequential ports within seconds strongly indicate automated scanners like nmap rather than misconfigured user addresses.
Practical Steps
- Identify top blocked source IPs during the current hour.
sudo grep 'UFW BLOCK' /var/log/ufw.log | grep "$(date '+%b %e %H')" | grep -oP 'SRC=\K[0-9.]+' | sort | uniq -c | sort -rn | head -10
Verification and Troubleshooting
- A single source IP attempting hundreds of connections across varied ports in brief windows warrants permanent manual block rules as an emergency measure.
sudo ufw insert 1 deny from alamat_ip_scanner comment 'manually detected scanner' - Manual grep approaches remain reactive and unscalable for servers receiving thousands of daily connection attempts. Chapter 32 automates detection and blocking workflows using Fail2ban, eliminating manual log grepping.
At this point we have completed our second layer of defense in depth from Section 30.1.2: understanding the evolution from iptables to nftables, reading raw iptables rules directly to comprehend why Docker bypasses UFW, officially enabling UFW complete with rate limiting and custom application profiles, experimenting with native nftables syntax and migration methods, summarizing service ports opened since Chapter 3, and applying firewall logging and monitoring for early detection. A clean firewall still keeps one blind spot: it rejects only explicitly prohibited connections, but cannot automatically identify repeated attack patterns or block suspicious IPs without manual intervention like performed in Section 31.6.3. Chapter 32 addresses that blind spot using Fail2ban, the third layer in our defense in depth map, automating log pattern detection and real-time IP blocking without requiring daily manual log reviews.

