Linux Firewall Basics
The Linux firewall controls which network packets are accepted or dropped. Getting it right means your server only accepts traffic on the ports you intend, and everything else is silently discarded. This guide covers the concepts, practical ufw usage for most servers, and a brief look at the underlying nftables for when you need more control.
How the Linux firewall works
The kernel component is Netfilter: a set of hooks embedded in the network stack that process packets at specific points (input, output, forward). Firewall rules plug into these hooks.
[Internet] -> NIC -> INPUT chain -> [Your application]
|-> (drop/reject unwanted packets)
[Your application] -> OUTPUT chain -> NIC -> [Internet]
[Forward chain for routing/NAT]
The tools you use to write rules (nftables, iptables, ufw, firewalld) all ultimately write rules into the kernel’s Netfilter hooks. They are different interfaces to the same underlying mechanism.
ufw: the simple approach
ufw (Uncomplicated Firewall) is available on Debian, Ubuntu, and most other distributions. It is the right tool for the majority of servers.
Initial setup
# Check ufw status
sudo ufw status
sudo ufw status verbose # shows default policies too
# IMPORTANT: allow SSH before enabling the firewall
sudo ufw allow 22/tcp
# Enable the firewall
sudo ufw enable
# Disable the firewall
sudo ufw disable
# Reset to a clean state (removes all rules, disables)
sudo ufw reset
Setting default policies
# Deny all incoming, allow all outgoing (recommended starting point)
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Deny all outgoing too (strict environments)
sudo ufw default deny outgoing
Allowing services
# Allow by port number and protocol
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 25/tcp # SMTP
sudo ufw allow 53 # DNS (both TCP and UDP)
# Allow by service name (from /etc/services)
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
# Allow a port range
sudo ufw allow 8000:9000/tcp
# Allow from a specific IP
sudo ufw allow from 192.168.1.10
sudo ufw allow from 192.168.1.10 to any port 22
# Allow from a subnet
sudo ufw allow from 192.168.1.0/24
sudo ufw allow from 192.168.1.0/24 to any port 5432 # allow postgres from LAN only
# Allow to a specific interface
sudo ufw allow in on eth0 to any port 80
Denying and limiting
# Deny a port
sudo ufw deny 23/tcp # deny Telnet
# Deny from an IP
sudo ufw deny from 203.0.113.5
# Limit: allows connections but blocks IPs that attempt more than 6 in 30 seconds
# Useful for SSH brute-force mitigation
sudo ufw limit ssh
sudo ufw limit 22/tcp
Managing existing rules
# List rules with numbers
sudo ufw status numbered
# Delete a rule by number
sudo ufw delete 3
# Delete a rule by specification
sudo ufw delete allow 80/tcp
# Insert a rule at a specific position
sudo ufw insert 1 allow from 10.0.0.0/8
Application profiles
ufw ships with application profiles that know which ports common services need:
# List available profiles
sudo ufw app list
# Show details of a profile
sudo ufw app info 'Nginx Full'
# Allow a profile
sudo ufw allow 'Nginx Full' # allows both 80 and 443
sudo ufw allow 'Nginx HTTP' # allows only 80
sudo ufw allow 'OpenSSH' # SSH profile
# Custom profiles live in /etc/ufw/applications.d/
ufw configuration files
# Main config: enable/disable logging, default policies
cat /etc/default/ufw
# Before rules (applied before user rules, e.g. allow ICMP ping)
cat /etc/ufw/before.rules
# After rules (applied after user rules)
cat /etc/ufw/after.rules
# User rules (generated from ufw commands, do not edit directly)
cat /etc/ufw/user.rules
iptables: the legacy approach
iptables is the predecessor to nftables. It is still widely used and you will encounter it in older documentation and scripts.
# List all rules
sudo iptables -L -n -v
# List with line numbers
sudo iptables -L -n -v --line-numbers
# Common rule structure:
# iptables -A CHAIN -p PROTO --dport PORT -j ACTION
# Allow inbound SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow established/related (responses to outbound connections)
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT
# Drop everything else
sudo iptables -P INPUT DROP
# Save rules (Debian/Ubuntu)
sudo iptables-save > /etc/iptables/rules.v4
# Restore rules
sudo iptables-restore < /etc/iptables/rules.v4
nftables: the modern approach
nftables replaced iptables as the kernel interface in Linux 3.13 and is the default on Fedora, Debian 10+, and Ubuntu 20.04+. See the nftables vs iptables guide for a full comparison.
# List all rules
sudo nft list ruleset
# Check if nftables is active
sudo systemctl status nftables
# Basic inspection
sudo nft list tables
sudo nft list chain ip filter input
Common server firewall setup
Here is a complete ufw setup for a typical web server:
# Start fresh
sudo ufw reset
# Set defaults
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (do this FIRST before enabling)
sudo ufw allow 22/tcp
# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow SSH only from your management IP (more restrictive)
# sudo ufw allow from 203.0.113.5 to any port 22
# sudo ufw delete allow 22/tcp # remove the open SSH rule after
# Rate limit SSH to reduce brute force risk
sudo ufw limit 22/tcp
# Enable
sudo ufw enable
# Verify
sudo ufw status verbose
Testing your firewall
# From your machine: what is actually listening?
ss -tlnp
# From another machine: what is visible from the network?
nmap -p 22,80,443 your-server-ip
# Test that a blocked port is actually blocked
nc -zv your-server-ip 3306 # should timeout or be refused
nc -zv your-server-ip 443 # should succeed
# View firewall hit counters
sudo ufw status verbose # shows rule stats with ufw
sudo iptables -L -n -v --line-numbers # counter columns
# Watch firewall log (if logging is enabled)
sudo ufw logging on
sudo tail -f /var/log/ufw.log
Firewall logging
# Enable ufw logging
sudo ufw logging on
sudo ufw logging medium # low, medium, high, full
# View the log
sudo tail -f /var/log/ufw.log
sudo grep "UFW BLOCK" /var/log/ufw.log | head -20
# Log format:
# [UFW BLOCK] IN=eth0 OUT= SRC=1.2.3.4 DST=5.6.7.8 PROTO=TCP DPT=3306
# This means: blocked packet on eth0, from 1.2.3.4, to port 3306 (MySQL)
Frequently Asked Questions
How does a Linux firewall work?
The Linux kernel includes Netfilter, a framework of hooks in the network stack that can inspect, modify, accept, or drop packets. Firewall tools (nftables, iptables, ufw) write rules into these hooks. When a packet arrives, the kernel runs it through the applicable chains of rules in order, and the first matching rule determines what happens to the packet (ACCEPT, DROP, REJECT, etc.). ufw and firewalld are frontends that simplify this by generating the underlying nftables or iptables rules for you.
What is the difference between DROP and REJECT in firewall rules?
DROP silently discards the packet. The sender receives no response and must wait for a timeout. REJECT discards the packet but sends back an ICMP error (port unreachable or TCP reset), so the sender knows immediately that the connection was refused. For security (hiding the presence of a service), DROP is often recommended for inbound rules. For outbound filtering where you want applications to fail fast rather than hang, REJECT is more user-friendly. Both prevent the connection; they differ only in whether the sender gets feedback.
What is ufw and why should I use it?
ufw (Uncomplicated Firewall) is a simplified command-line interface for managing the Linux firewall. It was developed for Ubuntu but is available on most distributions. Instead of writing nftables or iptables rules directly (which require understanding of chains, tables, and rule ordering), ufw lets you express policies in natural language: ufw allow 22/tcp, ufw deny 80/tcp. ufw translates these into proper firewall rules automatically. It is the right tool for most servers where you just need to allow specific ports and block everything else.
What firewall rules does a typical Linux server need?
A minimal secure server typically needs: SSH (port 22) allowed from your management IP or any IP if you use key-based auth; the service ports your server provides (80 and 443 for a web server, 25 for a mail server, etc.) allowed from any IP; all other inbound traffic denied by default. A default-deny inbound policy with explicit allows is the correct approach. Outbound traffic is usually allowed by default, though strict environments may restrict it too. Do not forget to allow established/related traffic so response packets for outbound connections can return.
What is a default policy and why does it matter?
The default policy is what happens to a packet when no explicit rule matches it. The two common options are ACCEPT (allow by default) and DROP (deny by default). A default-ACCEPT policy is insecure: any service you accidentally start that listens on a port will be immediately reachable from the internet. A default-DROP policy means only traffic that you have explicitly allowed can enter, which is far safer. Always set the default inbound policy to deny and then add explicit allow rules for the ports you need.
Will setting up a firewall break my existing SSH connection?
It can, if you forget to allow SSH before enabling the firewall. The correct order is: add a rule to allow port 22 (or whatever port SSH uses), then enable the firewall. With ufw, the workflow is: sudo ufw allow 22/tcp first, then sudo ufw enable. If you lock yourself out of a VPS by enabling a firewall without allowing SSH, most cloud providers offer an out-of-band console (web-based terminal) that is independent of the network, allowing you to fix the rules without needing SSH access.