A firewall is one of the oldest and still one of the most important security tools in networking. At its core, a firewall is simply a gatekeeper: it inspects traffic flowing in and out of a network or a single machine, and decides — based on a set of rules — whether that traffic should be allowed through or blocked.
Despite how simple that idea sounds, misconfigured firewalls are one of the leading causes of both security breaches (rules too open) and mysterious outages (rules too strict). This article walks through firewall fundamentals, the most important rules every system and network administrator should know, and how to configure them on Linux (using both iptables and the more modern nftables/firewalld) as well as on Cisco devices.
What Is a Firewall, Really?
A firewall examines network packets and compares them against an ordered list of rules. Each rule typically specifies:
- Source (where the traffic is coming from)
- Destination (where it’s going)
- Port/Protocol (what service it’s for — e.g., port 22 for SSH, port 443 for HTTPS)
- Action (allow, deny, or log)
flowchart LR
A[Incoming Packet] --> B{Match Rule 1?}
B -->|Yes| C[Apply Action: Allow/Deny]
B -->|No| D{Match Rule 2?}
D -->|Yes| C
D -->|No| E{...more rules...}
E --> F[Default Policy: Allow or Deny]Rules are evaluated in order, top to bottom, and the first matching rule usually wins (this is critical to understand — rule order matters enormously).
Default Deny vs. Default Allow
The single most important firewall design decision is your default policy:
| Policy | Behavior | Security Level |
|---|---|---|
| Default Deny | Block everything, then explicitly allow only what’s needed | High security, more setup effort |
| Default Allow | Allow everything, then explicitly block known-bad traffic | Low security, easier initial setup |
Professional security practice is almost always default deny — you close everything, and open only the specific ports and sources you actually need.
Essential Firewall Rules Every Admin Should Know
1. Allow Established and Related Connections
This is arguably the single most important rule in any firewall. It allows return traffic for connections your own system initiated (e.g., your server making an outbound web request), without needing a separate rule for every possible response.
iptables:
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT2. Allow Loopback Traffic
Many applications communicate with themselves via 127.0.0.1 (localhost). Blocking this breaks all sorts of things.
sudo iptables -A INPUT -i lo -j ACCEPT3. Allow SSH (But Restrict the Source!)
SSH (port 22) is essential for remote administration — but leaving it open to the entire internet is a common and dangerous mistake.
# Only allow SSH from a specific trusted network
sudo iptables -A INPUT -p tcp -s 203.0.113.0/24 --dport 22 -j ACCEPT4. Allow Web Traffic (HTTP/HTTPS)
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT5. Allow DNS Traffic (Both UDP and TCP)
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPTMany people forget the TCP rule — DNS mostly uses UDP, but falls back to TCP for large responses (like zone transfers or DNSSEC-signed answers).
6. Drop Everything Else (Default Deny)
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP⚠️ Warning: Always set your default-deny policy last, after your allow rules are in place — otherwise you may lock yourself out of a remote server!
7. Rate-Limit Connections to Prevent Brute-Force Attacks
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m limit --limit 5/min --limit-burst 10 -j ACCEPTThis slows down attackers attempting to guess SSH passwords via rapid repeated connection attempts.
8. Block Known Malicious IP Ranges
sudo iptables -A INPUT -s 198.51.100.0/24 -j DROP9. Log Dropped Packets (For Investigation)
sudo iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROPPED: "Saving iptables Rules
Rules configured with iptables directly are not persistent by default — they vanish on reboot unless saved:
Debian/Ubuntu:
sudo apt install iptables-persistent -y
sudo netfilter-persistent saveRHEL/CentOS:
sudo service iptables saveModern Alternative: firewalld (RHEL/CentOS/Fedora)
firewalld is a higher-level, zone-based firewall management tool built on top of nftables/iptables, common on RHEL-family systems.
# Check status
sudo firewall-cmd --state
# Allow SSH permanently
sudo firewall-cmd --permanent --add-service=ssh
# Allow HTTP and HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Allow a custom port
sudo firewall-cmd --permanent --add-port=8080/tcp
# Apply changes
sudo firewall-cmd --reload
# List all active rules
sudo firewall-cmd --list-all
Firewalld Zones
firewalld organizes rules into zones, each representing a different trust level:
| Zone | Trust Level | Typical Use |
|---|---|---|
drop | Lowest — drops everything | Hostile/untrusted networks |
public | Low | Default for internet-facing interfaces |
internal | Medium | Trusted internal LAN |
trusted | Highest — allows everything | Rare, only for fully trusted networks |
Modern Alternative: ufw (Uncomplicated Firewall — Ubuntu)
ufw provides a simpler, more beginner-friendly interface over iptables.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22
sudo ufw enable
sudo ufw status verbose
Comparison Table: Linux Firewall Tools
| Tool | Complexity | Best For | Config Persistence |
|---|---|---|---|
iptables | High (low-level) | Fine-grained control, legacy systems | Manual save required |
nftables | Medium-High | Modern replacement for iptables | Native persistent config |
firewalld | Medium | RHEL/CentOS/Fedora, zone-based policies | Built-in |
ufw | Low | Ubuntu, beginners, simple servers | Built-in |
Real-World Example: Securing a Public Web Server
Scenario: You’re deploying a public-facing web server that should only expose HTTP/HTTPS to the world, and SSH only to your office IP range.
flowchart TD
Internet((Internet)) -->|Port 80/443 Allowed| WebServer[Web Server]
Internet -->|Port 22 Blocked| WebServer
Office[Office Network 203.0.113.0/24] -->|Port 22 Allowed| WebServer
Internet -.->|All Other Ports| Dropped[Dropped/Denied]sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22
sudo ufw enableThis configuration means: the web ports are open to everyone (as they should be), but SSH — the door administrators use to manage the server — is only reachable from the trusted office network, dramatically reducing the attack surface.
Cisco Example: Access Control Lists (ACLs)
On Cisco routers and switches, firewall-like filtering is implemented through Access Control Lists.
ip access-list extended OFFICE_TO_SERVER
permit tcp 203.0.113.0 0.0.0.255 host 198.51.100.10 eq 22
permit tcp any host 198.51.100.10 eq 80
permit tcp any host 198.51.100.10 eq 443
deny ip any any log
interface GigabitEthernet0/1
ip access-group OFFICE_TO_SERVER inNotice the 0.0.0.255 — Cisco ACLs use wildcard masks (the inverse of a subnet mask) instead of CIDR notation. This is a common point of confusion for engineers coming from a Linux-only background.
Cisco ASA (Dedicated Firewall) Example
object network WEB_SERVER
host 198.51.100.10
access-list OUTSIDE_IN extended permit tcp any object WEB_SERVER eq 443
access-list OUTSIDE_IN extended permit tcp any object WEB_SERVER eq 80
access-list OUTSIDE_IN extended deny ip any any log
access-group OUTSIDE_IN in interface outside
Python Example: Auditing Open Ports
Before writing firewall rules, it helps to know what’s actually listening on your server. Here’s a simple Python port scanner for auditing your own systems (never use this against systems you don’t own or have permission to test):
import socket
def check_port(host, port, timeout=1):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
host = '127.0.0.1'
common_ports = {22: 'SSH', 80: 'HTTP', 443: 'HTTPS', 53: 'DNS', 3306: 'MySQL', 21: 'FTP'}
for port, name in common_ports.items():
status = "OPEN" if check_port(host, port) else "closed"
print(f"Port {port} ({name}): {status}")
Running this on a server before writing firewall rules helps confirm exactly what services are actually running and need to be accounted for — no more, no less.
Best Practices
- Default deny, explicitly allow — always start from “block everything” and open only what’s needed.
- Restrict management ports (SSH, RDP) to known trusted IP ranges — never leave them open to the whole internet.
- Always allow established/related traffic so return traffic from your own outbound connections works.
- Log dropped packets, at least temporarily, to catch misconfigurations and attacks.
- Use rate limiting on authentication-related ports to slow brute-force attempts.
- Document every rule with a comment explaining why it exists — six months later, nobody remembers.
- Test rules in a maintenance window — a single wrong rule can lock you out of a remote server entirely.
- Regularly audit and remove stale rules — firewalls accumulate cruft over years of ad-hoc changes.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Locked out of SSH after applying rules | Default deny policy applied before allow rule for your IP | Use console/out-of-band access to fix; always allow your own IP first |
| Service unreachable despite rule existing | Rule ordering — an earlier DROP rule matches first | Reorder rules so specific ALLOW rules come before general DENY rules |
| Rules disappear after reboot | Not saved/persisted | Use netfilter-persistent save, firewall-cmd --permanent, or ufw (auto-persists) |
| Outbound connections failing | Missing ESTABLISHED/RELATED rule, or OUTPUT chain too strict | Add state-tracking rule; review OUTPUT chain policy |
| DNS queries failing through firewall | Only UDP/53 allowed, TCP/53 blocked | Add both UDP and TCP rules for port 53 |
Useful Diagnostic Commands
sudo iptables -L -v -n --line-numbers # List all rules with packet counts
sudo firewall-cmd --list-all # List firewalld rules
sudo ufw status numbered # List ufw rules with numbers
sudo tcpdump -i eth0 port 22 # Watch traffic live to debug rule matchesSummary
Firewalls remain a foundational layer of network and system security. The most important concepts to internalize are: default-deny as your baseline posture, careful handling of established/related traffic, restricting sensitive management ports to trusted sources, and always testing changes carefully to avoid locking yourself out. Whether you’re using iptables, firewalld, ufw on Linux, or ACLs on Cisco hardware, these same core principles apply everywhere.