Guides

8 min readUpdated

Firewall configuration: a Linux server baseline

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.

/etc/iptables/rules.v4 — IPv4 baseline (tested with iptables-restore --test, iptables 1.8.9, Debian 12)
*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: "
COMMIT

03The 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.

/etc/iptables/rules.v6 — IPv6 baseline (tested with ip6tables-restore --test, iptables 1.8.9, Debian 12)
*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: "
COMMIT

04The 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.

/etc/nftables.conf — dual-stack baseline (validated with nft -c -f in a network namespace, nftables 1.0.6, Debian 12)
#!/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 baseline (behaviour verified with ufw 0.36.2 against a veth pair in a network namespace, Debian 12)
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 verbose

06Default 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 / nftablesufwAWS security groupWindows Defender Firewall
Default inboundDrop, set by the chain policydeny incomingNo inbound rules, nothing allowedBlocked unless a rule allows it
Deny rulesYesYesNone: allow rules onlyYes, and they win over allows
Does rule order matter?Yes, first match winsYes, first match winsNo, the rules are combinedNo, block beats allow, then more specific beats less specific
Outbound defaultAccept in this baselineallow outgoingOne rule allowing all outboundAllow, 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.

MistakeWhat happensFix
Adding a deny rule after a broad allowFirst match wins, so the earlier allow already let the packet through; the deny never runsInsert the deny before the allow it must override
Forgetting the established/related ruleReplies to the host's own outbound connections are dropped, breaking updates and DNSAccept established,related near the top, before the default drop
Dropping ICMPv6 neighbour discoveryIPv6 stops working entirely once the policy is dropAccept nd-neighbor-solicit/advert and router-advert (RFC 4890) before the final drop
Locking down IPv4 but leaving IPv6 at acceptEvery service is still reachable on the host's IPv6 address, so the firewall covers half the boxLoad rules.v6 alongside rules.v4, or use one nftables inet table for both
No loopback acceptLocal services that talk over 127.0.0.1 fail in confusing waysMake iif lo accept the first rule
Editing live rules with no rollbackA wrong rule closes your own SSH and you cannot reconnect to fix itUse iptables-apply, which reverts unless you confirm you still have access
Apply rules with an automatic rollback (behaviour verified with iptables-apply, Debian 12)
# 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.v4

08Where 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

Deny by default. The policy on the inbound chain should be DROP, so that anything you have not explicitly allowed is refused, and a service you forgot about stays closed rather than exposed. NIST's firewall guidance states the same principle: block all traffic not expressly permitted. Every allow rule is then a deliberate, documented exception to that default, scoped as narrowly as the service permits.

For a new configuration prefer nftables, which is the modern kernel interface and covers IPv4 and IPv6 in one file. ufw is the fastest way to a safe default on a single server and is fine for most hosts. iptables still works and is widely documented, but it is the legacy front end. All three express the same baseline; what matters is that it is default-deny, stateful and least-privilege, not which tool writes it.

Almost always rule order or a missing rule. Firewalls act on the first matching rule, so a deny added after a broad allow never fires, and an allow added after a default drop is never reached. The classic lockout is a default-deny policy with no rule permitting your SSH source. Apply changes with iptables-apply, which reverts automatically unless you confirm you still have access.

With iptables, yes: IPv4 and IPv6 are configured independently, so you need both rules.v4 and rules.v6, and the IPv6 set must permit ICMPv6 neighbour discovery or IPv6 breaks. nftables can cover both families in one inet table. ufw manages both together. Whichever you use, never leave IPv6 at a default-accept while IPv4 is locked down, or you have left half the host open.

Print the live ruleset, not the file on disk. Use iptables -S and ip6tables -S, or iptables -L -v -n to see per-rule packet counters, nft list ruleset for nftables, and ufw status verbose for ufw. Counters that stay at zero point to a rule that never matches, often because an earlier rule catches the traffic first. Check again after a reboot to confirm the rules persist.

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.

  1. 01NIST SP 800-41 Rev. 1: Guidelines on Firewalls and Firewall Policy · NIST
  2. 02nftables wiki: Simple ruleset for a server · Netfilter
  3. 03iptables-extensions(8): conntrack, limit, multiport · Debian manpages
  4. 04ufw(8) manual page · Debian manpages
  5. 05iptables-apply(8) manual page · Debian manpages
  6. 06RFC 4890: Recommendations for Filtering ICMPv6 Messages in Firewalls · RFC Editor
  7. 07Packet filtering and firewalls (Docker and ufw) · Docker docs
  8. 08Security group rules · AWS documentation
  9. 09Control traffic to your AWS resources using security groups (stateful behaviour) · AWS documentation
  10. 10Windows Firewall rules (rule precedence) · Microsoft Learn

Last updated