A firewall controls network traffic entering, leaving, or passing through a system.
A simple way to think about a firewall is:
A firewall is a rule-based traffic guard.
It checks packets against rules and decides whether to allow, block, reject, forward, or log them.
INTERNET TRAFFIC ---> |--------------------------| ---> INTERNAL NETWORK
[IP:123.45.67.89] | | [Accepted IP: 123.45.67.89]
Port 80 HTTP | +----------------+ | Port 80 -> Allowed
| | FIREWALL | |
| | Rules Applied: | |
[IP: 98.76.54.32] | | - Allow HTTP | | [Rejected IP: 98.76.54.32]
Port 22 SSH | | - Block SSH | | Port 22 -> Blocked
| +----------------+ |
| |
|--------------------------|
Firewalls are used to:
A firewall does not make a vulnerable service safe by itself, but it reduces what can reach that service.
A firewall rule usually answers these questions:
| Firewall Rule Component | Description |
| Direction | Is the traffic incoming, outgoing, or forwarded? |
| Protocol | Does the rule apply to TCP, UDP, ICMP, or all protocols? |
| Port | Which port(s) are affected (e.g., 22, 80, 443, 8080)? |
| Source | Which IP address or subnet is the traffic coming from? |
| Destination | Which IP address or subnet is the traffic going to? |
| Action | What should happen to matching traffic: allow, drop, reject, log, or forward? |
Example:
Allow incoming TCP traffic from anywhere to port 22.
This means SSH is reachable.
Another example:
Deny incoming TCP traffic from anywhere to port 80.
This means HTTP is blocked.
Firewalls commonly use these actions:
| Firewall Target | Description |
| ACCEPT | Allow the packet to pass. |
| DROP | Silently discard the packet without notifying the sender. |
| REJECT | Block the packet and send an error response to the sender. |
| LOG | Record information about the packet, then continue processing subsequent rules. |
| DNAT (Destination NAT) | Rewrite the packet's destination IP address and/or port. |
| SNAT (Source NAT) | Rewrite the packet's source IP address. |
| MASQUERADE | Dynamically rewrite the source IP address for NAT, commonly used for internet sharing when the external IP may change. |
DROP is quiet. The client may wait until timeout.
REJECT is explicit. The client receives a refusal.
Example difference:
| Action | Client Behavior |
| DROP | The packet is silently discarded. The client waits and eventually times out. |
| REJECT | The packet is blocked and an error response is sent. The client quickly receives a "connection refused" or "unreachable" message. |
Traffic direction matters.
| Traffic Direction | Description |
| Incoming traffic | Remote client → this machine |
| Outgoing traffic | This machine → remote server |
| Forwarded traffic | One network → this machine/router → another network |
Diagram:
Incoming:
Internet ---> Server
Outgoing:
Server ---> Internet
Forwarded:
LAN Client ---> Linux Router ---> Internet
Most host firewalls focus on incoming traffic.
Routers, gateways, and NAT systems also care heavily about forwarded traffic.
Many services listen on well-known ports.
| Port / Protocol | Service |
| 22/tcp | SSH |
| 53/tcp, 53/udp | DNS |
| 80/tcp | HTTP |
| 443/tcp | HTTPS |
| 25/tcp | SMTP |
| 3306/tcp | MySQL / MariaDB |
| 5432/tcp | PostgreSQL |
| 21/tcp | FTP (control connection) |
Check listening services with:
sudo ss -tulnp
Example output:
Netid State Local Address:Port Peer Address:Port Process
tcp LISTEN 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1100,fd=3))
tcp LISTEN 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=2200,fd=6))
Interpretation:
Linux has several firewall management tools.
Common ones include:
This note focuses on:
A simple comparison:
| Firewall Tool | Description |
| iptables | Low-level and powerful firewall framework; commonly found on older Linux systems. |
| ufw (Uncomplicated Firewall) | Simple and beginner-friendly firewall interface; commonly used on Ubuntu. |
| firewalld | Zone-based, dynamic firewall management tool; commonly used on Fedora, RHEL, CentOS, AlmaLinux, and Rocky Linux. |
Modern systems often use nftables underneath, even when administrators use higher-level tools such as firewalld or ufw.
Firewall mistakes can lock you out of a remote server.
Before changing firewall rules over SSH:
A safe pattern is:
On remote servers, a bad firewall command can cause this:
iptablesiptables is a low-level firewall tool for managing packet filtering rules.
It uses tables, chains, and rules.
The most common table for basic filtering is:
filter
The default chains in the filter table are:
Diagram:
Packet entering local machine
|
v
INPUT chain
|
v
local service, if allowed
Packet leaving local machine
|
v
OUTPUT chain
|
v
network
Packet passing through machine
|
v
FORWARD chain
|
v
another network
iptables RulesUse:
sudo iptables -L
A more useful version includes numbers and numeric ports:
sudo iptables -L -n -v --line-numbers
Example output:
Chain INPUT (policy ACCEPT)
num pkts bytes target prot opt in out source destination
1 1200 96K ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
2 5 420 DROP icmp -- * * 0.0.0.0/0 0.0.0.0/0 icmp echo-request
3 200 18K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
4 20 1600 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
Chain FORWARD (policy DROP)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT all -- * * 192.168.1.0/24 192.168.1.0/24
2 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
num pkts bytes target prot opt in out source destination
Interpretation:
iptables Rule OrderRule order matters.
iptables checks rules from top to bottom.
Packet arrives
|
v
Rule 1 match? yes -> action
|
no
v
Rule 2 match? yes -> action
|
no
v
Rule 3 match? yes -> action
|
no
v
Default policy
If a broad DROP rule appears before an ACCEPT rule, the later ACCEPT may never be reached.
Example bad order:
The SSH allow rule is useless because traffic was already dropped.
iptables RuleAllow incoming HTTP:
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
Meaning:
| iptables Option | Description |
| -A INPUT | Append a rule to the INPUT chain. |
| -p tcp | Match TCP packets. |
| --dport 80 | Match packets with destination port 80. |
| -j ACCEPT | Jump to the ACCEPT target and allow matching packets. |
Allow SSH:
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
Block ping:
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
iptables RuleFirst list with line numbers:
sudo iptables -L INPUT -n -v --line-numbers
Example:
Chain INPUT (policy ACCEPT)
num target prot source destination
1 ACCEPT tcp 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2 ACCEPT tcp 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
Delete rule 2:
sudo iptables -D INPUT 2
Interpretation:
iptables Rulesiptables changes are usually runtime-only unless saved.
On Debian-based systems:
sudo iptables-save | sudo tee /etc/iptables/rules.v4
You may need:
sudo apt install iptables-persistent
On older Red Hat-based systems:
sudo service iptables save
Important:
If rules are not saved, they may disappear after reboot.
ufwUFW stands for Uncomplicated Firewall.
It provides a simpler interface for common firewall tasks.
It is common on Ubuntu and beginner-friendly systems.
Check status:
sudo ufw status numbered
Example output:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 1000:2000/tcp ALLOW IN 192.168.1.0/24
[ 5] 22/tcp ALLOW IN Anywhere (v6)
[ 6] 80/tcp ALLOW IN Anywhere (v6)
[ 7] 443/tcp ALLOW IN Anywhere (v6)
Interpretation:
ufw CommandsAllow SSH:
sudo ufw allow ssh
Allow port 22 explicitly:
sudo ufw allow 22/tcp
Allow HTTP:
sudo ufw allow http
Allow HTTPS:
sudo ufw allow https
Deny HTTP:
sudo ufw deny http
Delete a numbered rule:
sudo ufw status numbered
sudo ufw delete 2
Enable UFW:
sudo ufw enable
Disable UFW:
sudo ufw disable
A common safe server policy is:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw enable
Meaning:
On a remote server, allow SSH before enabling UFW.
firewalldfirewalld is a dynamic firewall manager commonly used on Fedora, RHEL, CentOS, AlmaLinux, and Rocky Linux.
It organizes rules into zones.
A zone represents a trust level.
Examples:
| Firewalld Zone | Description |
| public | For untrusted networks; only explicitly allowed services are accessible. |
| home | For home networks with a moderate level of trust. |
| work | For work networks with trusted devices and services. |
| internal | For internal trusted networks. |
| trusted | All network traffic is allowed. |
| drop | Incoming traffic is silently dropped. |
| block | Incoming traffic is rejected with an error response. |
Diagram:
eth0 connected to internet
|
v
public zone
|
v
allow ssh, http, https
eth1 connected to office LAN
|
v
internal zone
|
v
allow more trusted services
firewalld has two configurations:
Commands without --permanent usually affect runtime only.
Commands with --permanent save the rule, but the rule usually needs reload before it becomes active.
Common pattern:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
List active zone configuration:
sudo firewall-cmd --list-all
Example output:
public (active)
target: default
interfaces: eth0
services: ssh dhcpv6-client http https
ports: 8080/tcp 9090/tcp
masquerade: no
forward-ports:
rich rules:
rule family="ipv4" source address="192.168.0.0/24" accept
rule family="ipv4" source address="10.0.0.0/8" port port="443" protocol="tcp" accept
Interpretation:
Check zones:
sudo firewall-cmd --get-zones
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones
Allow SSH permanently:
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
Allow a custom port:
sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload
Remove HTTP:
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --reload
List again:
sudo firewall-cmd --list-all
Move an interface to the internal zone:
sudo firewall-cmd --permanent --zone=internal --change-interface=eth1
sudo firewall-cmd --reload
sudo firewall-cmd --zone=internal --list-all
Create a custom zone:
sudo firewall-cmd --permanent --new-zone=web-admin
sudo firewall-cmd --permanent --zone=web-admin --add-service=https
sudo firewall-cmd --permanent --zone=web-admin --add-source=192.168.50.0/24
sudo firewall-cmd --reload
Interpretation:
Rich rules allow more specific matching.
Allow HTTPS only from 10.0.0.0/8:
sudo firewall-cmd --permanent \
--add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="https" accept'
sudo firewall-cmd --reload
List rich rules:
sudo firewall-cmd --list-rich-rules
Example output:
rule family="ipv4" source address="10.0.0.0/8" service name="https" accept
Interpretation:
Only clients from 10.0.0.0/8 match this HTTPS allow rule.
iptables, ufw, and firewalldUse ufw:
Use firewalld:
Use iptables:
Rule of thumb:
Allow remote administration through SSH while blocking web traffic on HTTP port 80.
Check current status:
sudo ufw status numbered
Allow SSH:
sudo ufw allow 22/tcp
Deny HTTP:
sudo ufw deny 80/tcp
Enable UFW if not already enabled:
sudo ufw enable
sudo ufw status numbered
Example output:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 80/tcp DENY IN Anywhere
From another machine:
ssh user@SERVER_IP
curl -I http://SERVER_IP
Example SSH result:
user@SERVER_IP's password:
Example HTTP result:
curl: (7) Failed to connect to SERVER_IP port 80: Connection refused
or:
curl: (28) Connection timed out
Interpretation:
Create a secure default host firewall posture.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw enable
sudo ufw status verbose
Example output:
Status: active
Logging: on
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
Interpretation:
Prevent the system from replying to ping requests.
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
sudo iptables -L INPUT -n -v --line-numbers
Example output:
num pkts bytes target prot opt source destination
1 3 252 DROP icmp -- 0.0.0.0/0 0.0.0.0/0 icmp echo-request
From another host:
ping SERVER_IP
Example output:
PING SERVER_IP 56(84) bytes of data.
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
Interpretation:
List line numbers:
sudo iptables -L INPUT --line-numbers
Delete the matching rule number:
sudo iptables -D INPUT 1
Restrict access to a web service so only one trusted client can connect.
Allow HTTP from one IP:
sudo ufw allow from 192.168.1.50 to any port 80 proto tcp
Deny other HTTP traffic:
sudo ufw deny 80/tcp
sudo ufw status numbered
Example output:
Status: active
To Action From
-- ------ ----
[ 1] 80/tcp ALLOW IN 192.168.1.50
[ 2] 80/tcp DENY IN Anywhere
From allowed host:
curl -I http://SERVER_IP
Example output:
HTTP/1.1 200 OK
From blocked host:
curl -I http://SERVER_IP
Example output:
curl: (28) Failed to connect: Connection timed out
Interpretation:
Avoid locking yourself out while changing firewall rules over SSH.
First, find your client IP from the server side:
echo "$SSH_CLIENT"
Example output:
203.0.113.25 53244 22
Allow SSH from your IP:
sudo ufw allow from 203.0.113.25 to any port 22 proto tcp
Allow fallback SSH generally if needed:
sudo ufw allow 22/tcp
Then set defaults:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Enable:
sudo ufw enable
Open a new terminal and test a second SSH connection before closing the first.
Before risky changes, schedule a rollback:
echo "sudo ufw disable" | at now + 5 minutes
If the firewall works, cancel the job:
atq
atrm JOB_ID
Interpretation:
Reduce SSH attack surface by allowing only known source addresses.
Allow trusted IP:
sudo ufw allow from 203.0.113.25 to any port 22 proto tcp
Deny other SSH:
sudo ufw deny 22/tcp
sudo ufw status numbered
Example output:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN 203.0.113.25
[ 2] 22/tcp DENY IN Anywhere
Interpretation:
Limit repeated connection attempts to reduce brute-force or abuse.
UFW has a simple built-in rate limit:
sudo ufw limit ssh
Check:
sudo ufw status numbered
Example output:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp LIMIT IN Anywhere
Interpretation:
Limit new TCP connections to port 8080:
sudo iptables -A INPUT -p tcp --dport 8080 -m conntrack --ctstate NEW \
-m limit --limit 100/minute --limit-burst 20 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8080 -j DROP
Check:
sudo iptables -L INPUT -n -v --line-numbers
Example output:
num pkts bytes target prot source destination
1 1000 60K ACCEPT tcp 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 ctstate NEW limit: avg 100/min burst 20
2 200 12K DROP tcp 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080
Interpretation:
Record blocked packets for investigation.
Add a logging rule before a drop rule:
sudo iptables -A INPUT -m limit --limit 10/min --limit-burst 5 \
-j LOG --log-prefix "iptables denied: " --log-level 4
sudo iptables -A INPUT -j DROP
On many systems:
sudo journalctl -k | grep "iptables denied"
or:
sudo dmesg | grep "iptables denied"
Example log:
kernel: iptables denied: IN=eth0 OUT= MAC=... SRC=198.51.100.20 DST=203.0.113.10 LEN=60 PROTO=TCP SPT=51512 DPT=23
Interpretation:
Redirect traffic arriving on port 8080 to a service listening on port 80.
This redirects local incoming TCP port 8080 to port 80:
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 80
If testing from the same host, also use OUTPUT redirect:
sudo iptables -t nat -A OUTPUT -p tcp -o lo --dport 8080 -j REDIRECT --to-port 80
sudo iptables -t nat -L -n -v --line-numbers
Example output:
Chain PREROUTING (policy ACCEPT)
num pkts bytes target prot source destination
1 10 600 REDIRECT tcp 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 redir ports 80
Run a web server on port 80, then from another host:
curl -I http://SERVER_IP:8080
Example output:
HTTP/1.1 200 OK
Interpretation:
Prevent the server from connecting to a known unwanted destination.
Block outgoing traffic to a specific IP:
sudo ufw deny out to 203.0.113.200
sudo ufw status numbered
Example output:
Status: active
To Action From
-- ------ ----
[ 1] 203.0.113.200 DENY OUT Anywhere
curl http://203.0.113.200
Example output:
curl: (7) Failed to connect to 203.0.113.200 port 80
Interpretation:
Firewalls usually filter IP addresses, ports, protocols, and interfaces.
Blocking domains is more complicated because domain names can resolve to changing IP addresses.
For domain-based blocking, consider:
Allow web traffic using firewalld.
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
Example output:
public (active)
services: ssh http https
ports:
Interpretation:
Check listening services:
sudo ss -tulnp | grep -E ':80|:443'
Allow a custom TCP port, such as 8443.
sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports
Example output:
8443/tcp
Interpretation:
Understand why a rule may disappear after reload.
sudo firewall-cmd --add-port=9090/tcp
Check:
sudo firewall-cmd --list-ports
Example:
9090/tcp
Reload:
sudo firewall-cmd --reload
Check again:
sudo firewall-cmd --list-ports
Example:
No output.
Interpretation:
sudo firewall-cmd --permanent --add-port=9090/tcp
sudo firewall-cmd --reload
Use firewall tools to separate service problems from firewall problems.
A web service should be reachable, but clients cannot connect.
sudo systemctl status nginx
Example:
Active: active (running)
sudo ss -tulnp | grep ':80'
Example:
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=2200,fd=6))
Interpretation:
UFW:
sudo ufw status numbered
firewalld:
sudo firewall-cmd --list-all
iptables:
sudo iptables -L INPUT -n -v --line-numbers
Status: active
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp DENY IN Anywhere
Interpretation:
sudo ufw allow 80/tcp
Understand that firewall rules are only one part of connectivity.
Commands:
sudo systemctl status SERVICE
sudo ss -tulnp
ip addr
ip route
sudo ufw status numbered
sudo firewall-cmd --list-all
sudo iptables -L -n -v
Example issue:
Check:
sudo ss -tulnp | grep ':80'
Output:
tcp LISTEN 0 511 127.0.0.1:80 0.0.0.0:* users:(("nginx",pid=2200,fd=6))
Interpretation:
When a connection fails:
Example:
| Field | Value |
| Client IP | 192.168.1.50 |
| Server IP | 192.168.1.10 |
| Protocol | TCP |
| Port | 443 |
| Direction | Incoming |
This prevents vague troubleshooting.
sudo ss -tulnp | grep ':443'
If there is no listener, the firewall is not the only issue.
UFW:
sudo ufw status numbered
firewalld:
sudo firewall-cmd --list-all
iptables:
sudo iptables -L INPUT -n -v --line-numbers
iptables counters can show whether traffic is matching rules.
sudo iptables -L INPUT -n -v --line-numbers
Example:
num pkts bytes target prot source destination
1 0 0 ACCEPT tcp 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
2 150 9K DROP all 0.0.0.0/0 0.0.0.0/0
Interpretation:
Use:
curl -I http://SERVER_IP
nc -vz SERVER_IP 22
nc -vz SERVER_IP 443
Example:
Connection to SERVER_IP 22 port [tcp/ssh] succeeded!
Interpretation:
Check listening services:
sudo ss -tulnp
iptables:
sudo iptables -L -n -v --line-numbers
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -D INPUT RULE_NUMBER
sudo iptables-save
UFW:
sudo ufw status numbered
sudo ufw status verbose
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw deny 80/tcp
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
sudo ufw delete RULE_NUMBER
firewalld:
sudo firewall-cmd --list-all
sudo firewall-cmd --get-zones
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rules
Testing:
curl -I http://SERVER_IP
nc -vz SERVER_IP PORT
ping SERVER_IP
traceroute SERVER_IP
Logs:
sudo journalctl -k
sudo dmesg
sudo journalctl -u firewalld
sudo tail -f /var/log/ufw.log
If you added temporary iptables rules, list them first:
sudo iptables -L INPUT -n -v --line-numbers
Delete only the test rules by number:
sudo iptables -D INPUT RULE_NUMBER
For UFW, list and delete numbered rules:
sudo ufw status numbered
sudo ufw delete RULE_NUMBER
For firewalld test ports:
sudo firewall-cmd --permanent --remove-port=8443/tcp
sudo firewall-cmd --permanent --remove-port=9090/tcp
sudo firewall-cmd --reload
If you created a custom firewalld zone:
sudo firewall-cmd --permanent --delete-zone=web-admin
sudo firewall-cmd --reload
curl or nc.iptables, ufw, and firewalld.