GICT knowledge base
Linux iptables: The Firewall That Actually Runs Under Your VPS
Published
UFW is just a script that runs iptables commands for you. When something goes wrong with your firewall, the error message points to iptables. When a tutorial says “this rule will drop all traffic except SSH,” it is an iptables rule underneath. Understanding iptables means you are not guessing at what your firewall is actually doing. This article covers how iptables works at the kernel level, how to read what is currently running, and how to write rules directly without UFW hiding them from you.
What iptables Actually Is
Linux has a firewall built into the kernel called netfilter. It intercepts every network packet that moves through the system and lets you decide what to do with it: accept it, drop it, modify it, or log it. iptables is the CLI tool that programs netfilter.
You write rules. Each rule says: if a packet looks like this, do this. Rules are organized into chains. Chains are organized into tables.
The four tables:
- filter is the one you use most. It accepts, drops, or rejects packets.
- nat handles network address translation. It rewrites source or destination IPs.
- mangle modifies packet headers, like changing TOS flags.
- raw is used to bypass connection tracking for specific packets.
Most VPS use cases only touch the filter table.
The five chains:
- INPUT handles packets destined for local programs on your server.
- OUTPUT handles packets originating from local programs.
- FORWARD handles packets being routed through your server to another destination.
- PREROUTING handles packets before the routing decision is made.
- POSTROUTING handles packets after the routing decision is made.
A typical VPS web server only cares about the INPUT chain in the filter table. Note that if Docker is running on your server, it manages its own iptables rules in the DOCKER-USER and DOCKER-FORWARD chains. Dropping the FORWARD policy with Docker present can break container networking unless you explicitly allow Docker traffic in DOCKER-USER.
How to Read What Is Currently Running
Before you change anything, look at what is there:
iptables -L -v -n --line-numbers
-L lists rules. -v shows byte and packet counters so you can see if a rule has ever matched. -n shows IP addresses as numbers instead of resolving them to hostnames, which is faster and more readable. –line-numbers gives you rule numbers so you can refer to specific lines when you need to delete or insert.
The output has a Policy column. It shows what happens to packets that do not match any rule in the chain. On a default Ubuntu VPS, the INPUT policy is usually ACCEPT, which means nothing is being filtered on ingress.
Keep an emergency unlock ready. If you are configuring iptables over SSH, keep a second terminal open with a flush command you can paste if you lock yourself out:
iptables -F; iptables -P INPUT ACCEPT; iptables -P FORWARD ACCEPT; iptables -P OUTPUT ACCEPT
The Structure of a Rule
Every iptables rule follows this pattern:
iptables -A CHAIN -i interface -p protocol -s source --dport port -j TARGET
-A appends the rule to the end of a chain. -i sets the incoming interface (e.g. eth0). -p sets the protocol (tcp, udp, icmp). -s sets the source IP or CIDR range. –dport sets the destination port. -j sets the target: ACCEPT, DROP, REJECT, or LOG.
Example: drop all TCP traffic from the IP 1.2.3.4:
iptables -A INPUT -p tcp -s 1.2.3.4 -j DROP
DROP and REJECT both block traffic. DROP silently discards the packet, as if the server is not there. REJECT sends back an ICMP “port unreachable” message so the client knows the port is closed. Use DROP when you want to be invisible to port scanners. Use REJECT when you want the client to get a clear error.
A Baseline Ruleset for a VPS
Here is a practical starting point. It allows loopback traffic, accepts established connections, allows SSH on port 2222, allows HTTP and HTTPS, and drops everything else.
Allow loopback. The server needs this for internal communication:
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
Allow established connections. Any packet that is part of an existing conversation should pass through:
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
Allow SSH on your custom port. Replace 2222 with your actual SSH port:
iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
Allow HTTP and HTTPS:
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
Set the default policy on INPUT to DROP. This applies to any packet that does not match a rule above:
iptables -P INPUT DROP
The -P flag sets the chain policy. This is different from a rule: it handles packets that fall through all the rules in the chain.
Saving Rules Across Reboots
Rules disappear after a reboot by default. On Ubuntu, install iptables-persistent:
apt install iptables-persistent
Save the current rules:
netfilter-persistent save
The rules are stored in /etc/iptables/rules.v4 and rules.v6 for IPv6. Reload without rebooting:
netfilter-persistent reload
If iptables-persistent is not available, use iptables-save and iptables-restore manually:
iptables-save > /etc/iptables/rules
iptables-restore < /etc/iptables/rules
Common Maintenance Tasks
Delete a rule by number. First list the rules with line numbers:
iptables -L INPUT --line-numbers -n
Then delete rule number 5:
iptables -D INPUT 5
Insert a rule at a specific position. This inserts at position 3, pushing existing rules down:
iptables -I INPUT 3 -p tcp --dport 2222 -j ACCEPT
Flush all rules in a chain:
iptables -F INPUT
Flush everything and set all default policies to ACCEPT. Use this to unlock yourself if you get locked out:
iptables -F
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
Check if a specific IP is being blocked:
iptables -L INPUT -n | grep 1.2.3.4
Rate Limiting Connection Attempts
If an IP is making too many connection attempts, you can block it at the firewall level. This limits new SSH connections to 3 per minute per IP:
iptables -A INPUT -p tcp --dport 2222 -m state --state NEW -m recent --set --name sshburst
iptables -A INPUT -p tcp --dport 2222 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 --name sshburst -j DROP
The first rule marks new connections and records the IP. The second rule drops the fourth connection attempt within 60 seconds. Adjust the port and thresholds to fit your use case. This is more powerful than Fail2Ban because it blocks at the kernel level before the SSH daemon even sees the connection.
Summary
iptables is the actual firewall running on your Linux server. UFW is a layer on top of it. Understanding iptables means you know what is actually blocking or allowing traffic. The key concepts are tables, chains, rules, and targets. The baseline ruleset covers loopback, established connections, SSH, HTTP, and HTTPS, with everything else dropped. Save your rules with iptables-persistent so they survive reboots. If you lock yourself out, flush all rules and set ACCEPT policies as an emergency recovery step.
Related guides
How to Find Services That Need a Restart After Updates with needrestart
Find services using outdated libraries with needrestart on Ubuntu. Read restart reports, review automatic restart policy and plan maintenance safely.
Firewall and SecurityHow to Connect Two Servers Privately with WireGuard
Connect two servers with WireGuard and keep PostgreSQL on the tunnel. Verify peer traffic, private database access and denied public connections.
Firewall and SecurityInterServer HIPAA Hosting: BAA and Shared Responsibilities
Review InterServer HIPAA hosting responsibilities and download the BAA and provisioning checklist. Request service-specific coverage through a support ticket.
Firewall and SecurityHow to Sign and Verify Files with Minisign
Sign release files with Minisign, share an authenticated public key, verify downloads, and stop processing when signatures fail.
