Basic Ubuntu Hardening: Default iptables rule preset for your machines

avatar
avatar
Konrad Broda
2025-02-114 min read

Introduction

Let the one who has always meticulously secured all their public servers cast the first stone. For me, it was standard practice to configure a server specifically for the intended use — delivering the service was the priority. Properly securing the machine was always postponed, meaning: never.

I won't pretend life harshly taught me to secure servers. It simply started bothering me enough that I prepared a default configuration template for my servers.

Use Case

  • Time saver: add this ready-to-use step-by-step iptables setup guide to your notes for configuring new machines.

Prerequisites

  • Estimated time: 5 minutes per server.
  • Ubuntu 24.04 (should work on other Ubuntu versions too) with root access.

Step-by-Step Guide

At this stage, I assume you have the default Ubuntu configuration and have not yet modified iptables settings. The following steps will overwrite all existing iptables rules on your server. Keep this in mind if you apply this guide on a server already running other public services.

Installing iptables-persistent

This package is a helper that makes it easier to keep your rules after reboot or shutdown.

Refresh apt available packages
sudo apt update
Install iptables-persistent package
sudo apt install iptables-persistent
Create rules.v4 file
sudo nano /etc/iptables/rules.v4

Paste the following config into /etc/iptables/rules.v4 file:

/etc/iptables/rules.v4
*filter
 
# Default policies for each chain
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
 
:UDP - [0:0]
:TCP - [0:0]
:ICMP - [0:0]
 
# Accept SSH
-A TCP -p tcp --dport 22 -j ACCEPT
 
# Accept established, related (e.g. replies to outgoing connections) input and loopback
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i lo -j ACCEPT
 
# Drop invalid packets
-A INPUT -m conntrack --ctstate INVALID -j DROP
 
# Direct packets to protocol-specific chains
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
-A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP
 
# Reject everything else from this point
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable
 
COMMIT
 
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
 
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
 
*security
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
 
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

Apply the iptables rules from the file we just edited:

Apply iptables configuration from rules.v4 file
sudo iptables-restore -t /etc/iptables/rules.v4

I do a similar setup for IPv6. I don't use this protocol at all on my servers, so I completely restrict IPv6 traffic.

Create rules.v6 file
sudo nano /etc/iptables/rules.v6

Paste the following into /etc/iptables/rules.v6:

/etc/iptables/rules.v6
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
COMMIT
 
*raw
:PREROUTING DROP [0:0]
:OUTPUT DROP [0:0]
COMMIT
 
*nat
:PREROUTING DROP [0:0]
:INPUT DROP [0:0]
:OUTPUT DROP [0:0]
:POSTROUTING DROP [0:0]
COMMIT
 
*security
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
COMMIT
 
*mangle
:PREROUTING DROP [0:0]
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
:POSTROUTING DROP [0:0]
COMMIT

Apply IPv6 iptables rules:

Apply iptables configuration from rules.v6 file
ip6tables-restore -t /etc/iptables/rules.v6

Reload netfilter-persistent service:

Reload netfilter-persistent service
service netfilter-persistent reload

For Docker users

Docker manages communication in its virtual networks using iptables rules. If you have running containers on the server, after applying this guide and removing existing rules, you may encounter connectivity issues with your containers. I solve this by always rebooting the machine at the end:

Reboot machine to test iptables persistence
sudo reboot

Docker will automatically recreate all necessary rules on service start. Alternatively, you can restart just the docker service, but rebooting is better as it also tests rule restoration and final configuration.

Disclaimer

Do not rely solely on this guide for securing production infrastructure. If your server is critical or holds important data, consider consulting an expert individually.