Quick answer
Firewall configuration is the rule set that decides which traffic a firewall allows, drops or logs. A sound one denies by default, keeps state so replies are allowed automatically, grants each service the least access it needs, and logs what it drops. On a Linux server that means permitting loopback, established traffic and a few named services, nothing else. This guide gives that baseline in iptables, nftables and ufw, each verified, plus the rule-order mistakes that cause lockouts.
01What is firewall configuration, and what makes a good one?
A firewall configuration is the ordered set of rules, plus the default policy, that tells a firewall what to do with each packet: accept it, drop it, or log it. Every defensible configuration rests on four ideas. Deny by default: the final rule drops everything not explicitly allowed, so a service you forgot about is closed, not open. NIST's firewall guidance puts it plainly, that firewalls should block all traffic 'not expressly permitted by the firewall policy', a practice it calls deny by default. Keep state: allow packets that belong to a connection the host already established, so you open ports in one direction and replies come back automatically. Least privilege: each allow rule names a specific port, and where possible a specific source, rather than opening a range 'to be safe'. And log drops, at a rate limit, so you can see what is being refused without filling the disk.
Those principles are independent of the tool. The three tools below, iptables, nftables and ufw, all express the same baseline; they differ in syntax and ergonomics, not in what a correct ruleset does. The order of rules within each matters enormously, which is why a later section is devoted to the mistakes that order causes.
- Default deny: the policy on the INPUT chain is DROP, and the last rule is a rate-limited log
- Stateful: accept established and related connections, and drop packets conntrack marks invalid
- Least privilege: open only the ports services actually need, scoped to a source address where you can
- Keep loopback open: the host talks to itself constantly, so the first rule accepts the lo interface
- Do not drop the control traffic a protocol needs, in particular ICMPv6 neighbour discovery on IPv6
02A default-deny baseline in iptables
iptables is still the tool most people search for, and the ruleset below is a complete inbound baseline: loopback, established and related traffic, rate-limited ICMP echo, SSH from a management network only, public web ports, and a logged default drop. Load it with iptables-restore, which applies the whole file atomically rather than one rule at a time. The matching IPv6 ruleset is the same shape, with one addition that is not optional: ICMPv6 neighbour discovery must be allowed, or IPv6 stops working entirely.
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p icmp --icmp-type echo-request -m limit --limit 10/second --limit-burst 20 -j ACCEPT
-A INPUT -p tcp -s 203.0.113.0/24 --dport 22 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -m limit --limit 5/minute --limit-burst 10 -j LOG --log-prefix "fw-drop: "
COMMIT03The IPv6 ruleset and how to persist it
The IPv6 baseline accepts the ICMPv6 error, neighbour-discovery and multicast-listener-query messages from RFC 4890's must-not-drop list (type 130 is the listener query, which ip6tables has no name for), then mirrors the IPv4 rules. Testing in a network namespace confirmed that removing the neighbour-discovery accepts breaks IPv6 reachability completely, while removing the established/related rule breaks the host's own outbound connections because the replies are refused.
Rules do not survive a reboot on their own. On Debian and Ubuntu the iptables-persistent (netfilter-persistent) package reloads rules.v4 and rules.v6 at boot; verify after any change by printing the live ruleset, not the file.
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type destination-unreachable -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type packet-too-big -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type time-exceeded -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type parameter-problem -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type router-advertisement -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type neighbour-solicitation -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type neighbour-advertisement -j ACCEPT
-A INPUT -p ipv6-icmp --icmpv6-type 130 -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p ipv6-icmp --icmpv6-type echo-request -m limit --limit 10/second --limit-burst 20 -j ACCEPT
-A INPUT -p tcp -s 2001:db8:100::/48 --dport 22 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -m limit --limit 5/minute --limit-burst 10 -j LOG --log-prefix "fw6-drop: "
COMMIT04The same baseline in nftables
nftables is the modern replacement, and a single file covers both address families through the inet table. The ruleset below is the exact equivalent of the two iptables files, and flush ruleset at the top makes re-applying it idempotent, replacing whatever was loaded before. Check it without applying using nft -c -f, then load it with nft -f and confirm with nft list ruleset.
#!/usr/sbin/nft -f
flush ruleset
define ADMIN4 = { 203.0.113.0/24 }
define ADMIN6 = { 2001:db8:100::/48 }
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
iif "lo" accept
ct state established,related accept
icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, mld-listener-query } accept
ct state invalid drop
icmp type echo-request limit rate 10/second burst 20 packets accept
icmpv6 type echo-request limit rate 10/second burst 20 packets accept
tcp dport 22 ip saddr $ADMIN4 ct state new accept
tcp dport 22 ip6 saddr $ADMIN6 ct state new accept
tcp dport { 80, 443 } ct state new accept
limit rate 5/minute burst 10 packets log prefix "nft-drop: "
}
chain forward { type filter hook forward priority filter; policy drop; }
chain output { type filter hook output priority filter; policy accept; }
}05The same baseline in ufw
ufw (the uncomplicated firewall) is a front end over the same kernel machinery, and it is the quickest way to reach a safe default on a single server. The commands below set deny-by-default inbound, allow outbound, permit SSH from a management network and the public web ports, turn on logging, and enable the firewall. ufw inserts an established/related rule and loopback handling for you, so you do not write them by hand. Verify with ufw status verbose, which was confirmed to produce a default-deny inbound policy with exactly the allowed services showing.
ufw default deny incoming
ufw default allow outgoing
ufw allow from 203.0.113.0/24 to any port 22 proto tcp
ufw allow from 2001:db8:100::/48 to any port 22 proto tcp
ufw allow 80,443/tcp
ufw logging low
ufw enable
ufw status verbose06Default deny in cloud security groups and Windows Firewall
The four principles carry over to the other firewalls a server usually sits behind, but the rule logic differs, and that difference is where mistakes come from. Linux packet filters act on the first rule that matches, so order decides the outcome. An AWS security group has no order and no deny rules: a new group has no inbound rules, so nothing gets in, every rule you add is an allow, and it is stateful, so replies return without a rule. Windows Defender Firewall has no administrator-set order either; an explicit block rule beats any conflicting allow rule, and an allow rule beats the default block.
Treat the cloud layer and the host layer as two independent fences. A security group that admits port 22 only from your office does not excuse a host firewall that admits it from anywhere, because a later change to either one should not be enough to open the box.
| iptables / nftables | ufw | AWS security group | Windows Defender Firewall | |
|---|---|---|---|---|
| Default inbound | Drop, set by the chain policy | deny incoming | No inbound rules, nothing allowed | Blocked unless a rule allows it |
| Deny rules | Yes | Yes | None: allow rules only | Yes, and they win over allows |
| Does rule order matter? | Yes, first match wins | Yes, first match wins | No, the rules are combined | No, block beats allow, then more specific beats less specific |
| Outbound default | Accept in this baseline | allow outgoing | One rule allowing all outbound | Allow, Microsoft's recommendation for most deployments |
07Rule-order pitfalls that lock you out
Firewalls evaluate rules top to bottom and act on the first match, so order is not cosmetic. The table lists the mistakes that most often either lock an administrator out or silently leave a hole; the established-state, neighbour-discovery and rollback rows were reproduced in an isolated test namespace. The single most dangerous habit is editing a live ruleset over SSH with no safety net; use a tool that rolls back if you do not confirm.
| Mistake | What happens | Fix |
|---|---|---|
| Adding a deny rule after a broad allow | First match wins, so the earlier allow already let the packet through; the deny never runs | Insert the deny before the allow it must override |
| Forgetting the established/related rule | Replies to the host's own outbound connections are dropped, breaking updates and DNS | Accept established,related near the top, before the default drop |
| Dropping ICMPv6 neighbour discovery | IPv6 stops working entirely once the policy is drop | Accept nd-neighbor-solicit/advert and router-advert (RFC 4890) before the final drop |
| Locking down IPv4 but leaving IPv6 at accept | Every service is still reachable on the host's IPv6 address, so the firewall covers half the box | Load rules.v6 alongside rules.v4, or use one nftables inet table for both |
| No loopback accept | Local services that talk over 127.0.0.1 fail in confusing ways | Make iif lo accept the first rule |
| Editing live rules with no rollback | A wrong rule closes your own SSH and you cannot reconnect to fix it | Use iptables-apply, which reverts unless you confirm you still have access |
# Applies the ruleset, then asks you to confirm connectivity.
# If you do not answer within the timeout, it reverts to the previous rules.
iptables-apply -t 15 /etc/iptables/rules.v408Where this baseline stops protecting you
A default-deny baseline shrinks the attack surface; it is not a defence against denial of service. It can drop malformed packets and, with extra rules, limit new connections per source, which blunts scanning and small floods. It cannot help against a volumetric attack that saturates your uplink, because by the time a packet reaches the firewall it has already consumed the bandwidth. That class of attack has to be absorbed upstream, before it reaches the host, and the detail of what a packet filter can and cannot do under a flood is covered in can a firewall stop a DDoS attack.
A packet filter also operates at the network and transport layers. It does not understand HTTP, so it cannot tell a legitimate request from an abusive one on the same port; that is the job of a web application firewall, which inspects the request itself. And it is not the place to keep attackers off your origin address: locking an origin so that only your proxy's addresses may reach it is a specific pattern covered under hiding your origin IP. Use the host firewall for least-privilege access control and rate limiting of what does arrive, and layer dedicated DDoS protection and rate limiting above it for the attacks it cannot stop alone.
Questions
Frequently asked questions
Sources
Sources and verification
The iptables and ip6tables rulesets passed iptables-restore --test / ip6tables-restore --test (iptables 1.8.9) and were confirmed to allow and block the intended ports, IPv4 ping and IPv6 neighbour discovery across a veth pair; the nftables.conf passed nft -c -f and behaved identically (nftables 1.0.6); the ufw commands were run with ufw 0.36.2 using a sandboxed configuration directory; iptables-apply rollback on timeout was observed. All in isolated network namespaces on Linux 6.1, Debian 12, October 2026. The cloud and Windows rows are taken from AWS and Microsoft documentation, not tested.
- 01NIST SP 800-41 Rev. 1: Guidelines on Firewalls and Firewall Policy · NIST
- 02nftables wiki: Simple ruleset for a server · Netfilter
- 03iptables-extensions(8): conntrack, limit, multiport · Debian manpages
- 04ufw(8) manual page · Debian manpages
- 05iptables-apply(8) manual page · Debian manpages
- 06RFC 4890: Recommendations for Filtering ICMPv6 Messages in Firewalls · RFC Editor
- 07Packet filtering and firewalls (Docker and ufw) · Docker docs
- 08Security group rules · AWS documentation
- 09Control traffic to your AWS resources using security groups (stateful behaviour) · AWS documentation
- 10Windows Firewall rules (rule precedence) · Microsoft Learn
Last updated