Skip to main content

Iptables More Strict: Building a Least-Privilege Linux Firewall

A production-safe baseline policy for INPUT, OUTPUT, and FORWARD

Created: April 24, 2026 Larry Qu 4 min read

Introduction

Many firewall guides are either too permissive (ACCEPT everything) or too strict to operate safely in production. A good iptables policy should be strict by default, explicit about allowed traffic, and maintainable over time.

This guide shows how to build a least-privilege iptables policy without breaking SSH access or application traffic.

What “Strict” Should Mean

A strict firewall is not random denial. It means:

  1. Default deny (DROP) for inbound and forwarded packets.
  2. Explicit allow rules for required services.
  3. Established/related traffic allowed for return paths.
  4. Logging for denied packets at controlled rate.
  5. Minimal, documented outbound allowances.

Baseline Policy Design

Chain defaults

Recommended baseline:

  1. INPUT: DROP
  2. FORWARD: DROP
  3. OUTPUT: ACCEPT initially, then tighten if your environment needs egress control.

Why OUTPUT is often left open first

In many production systems, over-restricting OUTPUT breaks package managers, DNS, telemetry, object storage, backup agents, and time sync. Tightening egress should be deliberate and inventory-driven.

Safe Baseline Rules

# Flush current rules carefully in controlled maintenance window
iptables -F
iptables -X

# Set policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT

# Allow established and related return traffic
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# SSH from admin network
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.0/24 -m conntrack --ctstate NEW -j ACCEPT

# Web traffic
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT

# ICMP (recommended for diagnostics and MTU behavior)
iptables -A INPUT -p icmp -j ACCEPT

Service-Specific Examples

DNS server role

iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT -p tcp --dport 53 -j ACCEPT

Database exposed only to app subnet

iptables -A INPUT -p tcp --dport 5432 -s 10.10.0.0/16 -m conntrack --ctstate NEW -j ACCEPT

Internal metrics endpoint

iptables -A INPUT -p tcp --dport 9100 -s 10.20.30.0/24 -m conntrack --ctstate NEW -j ACCEPT

Controlled Logging for Denied Traffic

Logging every dropped packet can overload disks. Use rate limiting:

iptables -A INPUT -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "iptables-drop: " --log-level 4
iptables -A INPUT -j DROP

Put LOG before final DROP in chain order.

About state vs conntrack

Older examples use -m state --state .... Modern kernels prefer -m conntrack --ctstate ....

Both can work, but use conntrack in new rules for clarity and consistency.

Should You Lock Down OUTPUT?

For high-security environments, yes. For general web servers, maybe later.

If you do lock down OUTPUT, allow at least:

  1. DNS (53 UDP/TCP) to trusted resolvers.
  2. NTP (123 UDP) to time sources.
  3. Package repository traffic.
  4. Required API endpoints.

Example:

iptables -P OUTPUT DROP
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -d 10.0.0.2 -j ACCEPT
iptables -A OUTPUT -p udp --dport 123 -d 169.254.169.123 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT

Rule Persistence

Always save known-good state.

iptables-save > /root/iptables.known-good.rules

On Debian/Ubuntu with iptables-persistent:

apt-get install -y iptables-persistent
iptables-save > /etc/iptables/rules.v4

Validation and Troubleshooting

Useful commands:

iptables -S
iptables -L -n -v
ss -tulpen
journalctl -k | grep iptables-drop

If traffic fails unexpectedly:

  1. Check chain order.
  2. Check source IP assumptions.
  3. Check whether packet belongs to ESTABLISHED flow.
  4. Check NAT/filter table interactions.

Common Mistakes

  1. Typo in chain name (OUTUT instead of OUTPUT).
  2. Forgetting loopback allow rule.
  3. Using default drop before adding SSH allowlist.
  4. Relying on broad ACCEPT all rules in production.
  5. Not documenting why a port is open.

Practical Policy for Most Web Servers

A pragmatic strict policy for internet-facing web hosts:

  1. INPUT DROP.
  2. FORWARD DROP.
  3. OUTPUT ACCEPT initially.
  4. Allow lo, ESTABLISHED/RELATED, SSH from admin CIDR, and 80/443.
  5. Add controlled ICMP.
  6. Add rate-limited logging before final drop.

This keeps attack surface small while preserving operability.

Conclusion

“More strict” should mean intentional, testable, and maintainable firewall design. Start with a clear baseline, open only required services, and keep rule changes documented and reversible.

A strict firewall that your team cannot operate safely is not secure. A strict firewall with safe rollout and clear ownership is.

Resources

Comments

Share this article

Scan to read on mobile