How to Bootstrap a Secure Linux Setup Using iptables and ufw
A fresh Linux server is wide open. Every port a service binds is reachable from the internet the moment the machine comes online. For an attacker running automated scanners, an unprotected machine is visible in seconds and exploitable in minutes. The firewall is the first control you should put in place — before you install anything else, before you configure your application, before you generate TLS certificates.
This guide walks you through getting a fresh Ubuntu or Debian machine to a defensible firewall baseline. It covers two paths: ufw for the fast, human-friendly route, and iptables for the precise, rule-by-rule route. It also covers common attack mitigations, the modern nftables successor, WireGuard port rules, and how to verify that your ruleset is actually doing what you think it is.
1. Why Firewall-First Matters
Default-Open vs Default-Deny
Most Linux distributions install with a default-open posture: no firewall rules, all inbound connections accepted. This is pragmatic for a desktop but dangerous for any internet-facing server.
A default-deny posture inverts the logic: everything is blocked unless you explicitly allow it. The firewall starts by rejecting all inbound traffic, and you punch holes only for services you intend to run. The security properties are fundamentally different:
| Posture | New service installed | Forgotten port | Attack surface | |---------|----------------------|----------------|----------------| | Default-open | Immediately exposed | Permanently exposed | Grows with every install | | Default-deny | Blocked until rule added | Stays blocked | Controlled and auditable |
Default-deny means that a misconfigured service, a dependency that opens a debug port, or a package that installs a daemon automatically cannot be reached from outside until you make a deliberate decision to allow it.
The firewall is not a substitute for patching, access control, or application-level security — but it is the most leverage you get per minute of effort. A single ufw default deny incoming command converts your attack surface from "everything" to "only what you explicitly enumerate."
2. Understanding iptables Architecture
Before writing rules, it helps to understand what iptables is actually doing.
Tables
iptables organises rules into tables based on their purpose:
- filter — the default table. Controls whether packets are accepted, dropped, or rejected. This is where firewall rules live.
- nat — handles Network Address Translation. Used for masquerading, port forwarding, and routing traffic through a WireGuard or VPN interface.
- mangle — modifies packet headers (TTL, TOS, mark bits). Rarely needed for basic firewall hardening.
- raw — processes packets before connection tracking. Used to exempt specific traffic from conntrack.
Unless you specify -t <table>, iptables commands operate on the filter table.
Chains
Within each table, rules are organised into chains. The three built-in chains in the filter table are:
- INPUT — packets destined for the local machine (inbound connections, replies to your own requests).
- OUTPUT — packets originating from the local machine (outbound connections).
- FORWARD — packets routed through the machine (relevant if the server acts as a router, VPN gateway, or NAT device).
Each chain has a default policy: ACCEPT or DROP. When a packet reaches the end of a chain without matching any rule, the default policy applies. The goal of a default-deny setup is to set INPUT and FORWARD to DROP, then add explicit rules for the traffic you want.
3. UFW — The Fast Path for Most Users
ufw (Uncomplicated Firewall) is a front-end for iptables that ships by default on Ubuntu. It translates human-readable commands into iptables rules. For the vast majority of servers — web servers, API backends, SSH jump hosts — ufw provides everything you need with far less syntax overhead.
Install ufw
On Ubuntu it is pre-installed. On Debian:
sudo apt update && sudo apt install ufw -y
Apply a Default-Deny Baseline
# Block all inbound by default
sudo ufw default deny incoming
# Allow all outbound (your server can initiate connections freely)
sudo ufw default allow outgoing
Allow the Services You Run
# SSH — always allow this first, before enabling ufw
sudo ufw allow ssh # resolves to port 22/tcp via /etc/services
# Or explicitly:
sudo ufw allow 22/tcp
# Web traffic
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Critical: allow SSH before enabling ufw. If you enable the firewall without an SSH rule, your current session will be the last one.
Enable and Verify
sudo ufw enable
sudo ufw status verbose
Example output:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
4. Rate Limiting with UFW
A default-deny firewall still leaves SSH exposed to brute-force attacks. ufw has a built-in rate limiter: it uses iptables recent module to track connection frequency and drops packets from source IPs that exceed 6 connection attempts within 30 seconds.
sudo ufw limit ssh
After running this, ufw status verbose will show LIMIT IN instead of ALLOW IN for port 22. The rule blocks the most common automated SSH credential-stuffing tools with a single command.
For custom ports you can specify the port directly:
sudo ufw limit 2222/tcp
To allow SSH only from a specific IP (the strongest option if you have a static address):
sudo ufw delete allow ssh
sudo ufw allow from 203.0.113.42 to any port 22
5. iptables — The Precise Path for Advanced Users
When you need fine-grained control — complex multi-port rules, per-interface policies, logging specific traffic classes, or integration with custom chains — you work with iptables directly.
5.1 Flush Existing Rules Safely
Before building a new ruleset, clear the current state. Do not do this on a live production machine without console access, as it briefly leaves the machine open before the new rules are applied.
# Flush all rules in the filter table
sudo iptables -F
# Flush all chains in the nat table
sudo iptables -t nat -F
# Delete user-defined chains
sudo iptables -X
# Reset packet counters
sudo iptables -Z
5.2 Set Default DROP Policies
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
At this point the machine drops all inbound traffic. Proceed immediately to allow established connections and SSH.
5.3 Allow Established and Related Connections
This rule is essential. Without it, your server can initiate outbound connections but can never receive the replies:
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ESTABLISHED matches reply packets for connections your server initiated. RELATED matches auxiliary connections opened by protocols like FTP's data channel.
5.4 Allow Loopback
sudo iptables -A INPUT -i lo -j ACCEPT
Many services (databases, caches, IPC) communicate over the loopback interface. Dropping loopback traffic breaks them.
5.5 Allow SSH, HTTP, HTTPS
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
5.6 Log Dropped Packets
Before the final DROP, add a logging rule. This writes dropped-packet metadata to the kernel log (visible via dmesg or journalctl -k):
sudo iptables -A INPUT -m limit --limit 5/min -j LOG \
--log-prefix "iptables-drop: " --log-level 4
The --limit 5/min prevents log flooding during a scan or flood attack.
5.7 Save the Ruleset
iptables rules are ephemeral — they disappear on reboot without persistence. Install iptables-persistent:
sudo apt install iptables-persistent -y
# Accept "yes" when prompted to save current IPv4 and IPv6 rules
# Save manually at any time:
sudo iptables-save > /etc/iptables/rules.v4
sudo ip6tables-save > /etc/iptables/rules.v6
Rules are loaded automatically at boot via a systemd unit installed by iptables-persistent.
6. Protecting Against Common Attacks
A default-deny policy blocks the majority of opportunistic attacks. For more targeted mitigations, add the following rules (these work with either ufw's underlying iptables or directly):
Port Scanning: Log and Drop Invalid Packets
Packets with an invalid connection-tracking state are commonly used in stealth scans (Nmap's -sX, -sF, -sN modes):
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j LOG \
--log-prefix "iptables-invalid: " --log-level 4
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
SYN Flood Protection
SYN floods exhaust the server's half-open connection table. Use the limit module to cap the rate at which new TCP connections can arrive:
sudo iptables -A INPUT -p tcp --syn -m limit \
--limit 25/second --limit-burst 50 -j ACCEPT
sudo iptables -A INPUT -p tcp --syn -j DROP
The --limit-burst 50 allows short bursts of legitimate connections (page loads, API calls) while dropping sustained floods.
ICMP Flood Limiting
Allow ICMP (ping) but rate-limit it to prevent ICMP flood amplification:
sudo iptables -A INPUT -p icmp --icmp-type echo-request \
-m limit --limit 1/second --limit-burst 5 -j ACCEPT
sudo iptables -A INPUT -p icmp -j DROP
Block Private IP Spoofing on Public Interfaces
If your public interface is eth0, block packets claiming to come from RFC 1918 private addresses — a common spoofing technique:
IFACE="eth0"
sudo iptables -A INPUT -i $IFACE -s 10.0.0.0/8 -j DROP
sudo iptables -A INPUT -i $IFACE -s 172.16.0.0/12 -j DROP
sudo iptables -A INPUT -i $IFACE -s 192.168.0.0/16 -j DROP
sudo iptables -A INPUT -i $IFACE -s 127.0.0.0/8 -j DROP
sudo iptables -A INPUT -i $IFACE -s 169.254.0.0/16 -j DROP
Replace eth0 with your actual public interface name (check with ip link show).
7. nftables — The Modern Replacement
nftables is the kernel-level replacement for iptables, available in the Linux kernel since 4.x and the default on Debian 10+ and Ubuntu 20.10+. It uses a single tool (nft) and a cleaner rule syntax that avoids the separate iptables/ip6tables/arptables split.
A minimal nftables equivalent of the iptables baseline above:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Loopback
iifname "lo" accept
# Established and related
ct state established,related accept
# Invalid — drop
ct state invalid drop
# SSH (rate-limited)
tcp dport 22 ct state new limit rate 6/minute accept
# HTTP, HTTPS
tcp dport { 80, 443 } accept
# ICMP (rate-limited)
icmp type echo-request limit rate 1/second accept
# Log and drop everything else
limit rate 5/minute log prefix "nft-drop: " level warn
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Save this to /etc/nftables.conf and enable it:
sudo nft -f /etc/nftables.conf
sudo systemctl enable nftables
sudo systemctl start nftables
nftables and iptables cannot run simultaneously on the same machine in conflicting modes. If you migrate to nftables, disable iptables-persistent.
8. WireGuard VPN Firewall Rules
If you are running a WireGuard server or using FastSox's WireGuard-based gateways, you need to open the WireGuard UDP port and allow IP forwarding for tunnel traffic.
Open the WireGuard Port
With ufw:
sudo ufw allow 51820/udp
With iptables:
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT
Allow Forwarding for VPN Clients
If the machine acts as a WireGuard server and routes client traffic to the internet:
# Enable IP forwarding
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
# Allow forwarding from the WireGuard interface
sudo iptables -A FORWARD -i wg0 -j ACCEPT
sudo iptables -A FORWARD -o wg0 -j ACCEPT
# NAT outbound VPN traffic to the public interface
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
With ufw, forwarding requires one additional step: edit /etc/ufw/before.rules to add the POSTROUTING rule in the *nat section, and set DEFAULT_FORWARD_POLICY="ACCEPT" in /etc/default/ufw.
9. Verifying Your Firewall
Writing rules and enabling the firewall is not enough — you must verify that the ruleset behaves as intended. A misconfigured rule that looks correct in ufw status may still leave ports open due to rule ordering or conflicting rules.
Check Listening Services
Before scanning, know what is actually listening:
sudo ss -tlnp
-t TCP, -l listening only, -n numeric ports, -p show process. Any port in this output is a candidate for an open firewall rule. If a port appears here that you did not intend to expose, stop the service or bind it to 127.0.0.1 only.
Scan from Another Machine
The definitive test is an external port scan from a machine outside your network:
# From a separate machine (replace with your server's IP)
nmap -sS -p 1-65535 203.0.113.10
-sS performs a TCP SYN scan. Ports your firewall is blocking will appear as filtered; ports with running services and open rules will appear as open. You should see only your intended ports (22, 80, 443) as open and everything else as filtered.
If you do not have a second machine, Nmap can scan localhost from the server itself, but this bypasses the firewall (loopback traffic is not filtered by INPUT rules for the external interface). An external scan is the only reliable verification.
Check ufw Status in Detail
sudo ufw status verbose
sudo ufw show raw # shows the underlying iptables rules ufw generated
Check iptables Rule Counters
sudo iptables -L INPUT -v -n
The -v flag shows packet and byte counters per rule. After a few minutes of normal traffic, your ESTABLISHED,RELATED and service rules should show non-zero counters. If a rule has zero hits, either it is never matched or there is a rule above it absorbing all relevant traffic.
10. How FastSox Complements Firewall Hardening
A firewall controls which ports are reachable — but it cannot protect the content of the traffic that passes through those open ports. An attacker on the same network segment, a malicious ISP, or a compromised upstream router can still intercept or tamper with unencrypted traffic on ports 80 or 443.
FastSox addresses the layer above the firewall: it routes your traffic through encrypted tunnels (WireGuard and HyperSox) between your machines and FastSox gateways, ensuring that even traffic on open, approved ports is encrypted end-to-end. Combined with a properly hardened firewall baseline, FastSox provides defence in depth: the firewall limits exposure at the transport layer, and FastSox encrypts the traffic that is permitted through.
FastSox gateways also allow you to bind services to the VPN interface exclusively — meaning port 22 (SSH), database ports, or internal APIs are not exposed to the public internet at all, accessible only through the encrypted tunnel. This effectively removes entire service classes from your public firewall ruleset.
This guide covers the infrastructure layer. OneDotNet Ltd, the company behind FastSox, builds tools that integrate with your hardened baseline to provide end-to-end encrypted connectivity across your infrastructure.
11. Going Further: Full Server Hardening
A firewall is the first line of defence, not the only one. Once your firewall baseline is in place, continue with the broader server hardening checklist covered in Best Practices to Secure a Linux Server:
- Disable root SSH login (
PermitRootLogin no) - Enforce SSH key authentication only (
PasswordAuthentication no) - Install and configure
fail2banfor application-level brute-force detection - Keep packages current with
unattended-upgrades - Audit running services (
systemctl list-units --type=service --state=running) - Configure
auditdfor syscall-level event logging - Apply kernel hardening via
sysctl(ASLR, restrictdmesg, disable SysRq)
Conclusion
Getting from a default-open fresh install to a default-deny firewall baseline takes under five minutes with ufw, or fifteen minutes if you build the iptables ruleset manually. Either path gives you the same fundamental property: only the ports you explicitly name are reachable, and everything else is dropped.
The critical steps are:
- Set
default deny incomingbefore enabling the firewall. - Allow SSH first — always, before enabling.
- Use
ufw limit sshor iptables--syn -m limitto blunt brute-force attacks. - Add ICMP and SYN flood mitigations if the server is directly internet-facing.
- Verify with an external
nmapscan, not justufw status. - Save your rules so they survive a reboot.
A firewall baseline is not the end of server security — but it is the correct place to start. Everything else builds on top of a controlled, auditable set of open ports.
Related Articles
Best Practices to Secure a Linux Server in 2026
A comprehensive, checklist-style guide to hardening a Linux server in 2026. Covers SSH hardening, firewalls, fail2ban, automatic updates, user management, kernel sysctl tuning, file system security, audit logging, and VPN-only management access.
How to Use WireGuard on Linux: From Installation to Multi-Peer Setup
A practical, step-by-step guide to installing WireGuard on Linux, generating keys, configuring a server and multiple clients, and verifying your tunnel — plus tips on troubleshooting common issues.
How to Optimize TCP Traffic on Windows and Linux
A practical guide to tuning TCP congestion control, buffer sizes, and MTU on both Linux and Windows — so your VPN or proxy connection reaches its full potential.