Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · Linux

Host Firewalls: ufw and firewalld

Default-deny rules on Ubuntu with ufw and on CentOS with firewalld, safe rule testing over SSH, zones, rate limiting and how cloud security groups fit in.

3 min read

  • Ubuntu 18.04.3 LTS
  • ufw 0.36
  • CentOS 7.7
  • firewalld 0.6
  • iptables

A firewall decides which network traffic may reach a server. Both Ubuntu and CentOS ship a simple front end over the kernel’s packet filter (netfilter): ufw on Ubuntu and firewalld on CentOS. The goal is the same everywhere: deny everything by default and open only the ports the server must serve.

Versions in use on this date. ufw 0.36 on Ubuntu 18.04.3 LTS (iptables backend; nftables is not the default yet) and firewalld 0.6 on CentOS 7.7. Cloud providers add their own network-level rules (security groups) in front of the host.

Where filtering happens

flowchart TB
  NET[Internet] --> SG[Cloud security group<br/>or network firewall]
  SG --> NIC[Server network interface]
  NIC --> NF[Netfilter in the kernel<br/>managed by ufw or firewalld]
  NF -->|allowed port| SVC[Service such as nginx on 443]
  NF -->|not allowed| DROP[Dropped or rejected]
  SVC --> APP[Application]
  ADM[Admin SSH from office IP] --> SG

Use both layers: the network firewall for coarse control and the host firewall as a second line, protecting the server even if a security group is mis-set.

ufw on Ubuntu

sudo ufw status verbose
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp     # SSH from the office only
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw limit 22/tcp                                          # rate-limit new SSH connections
sudo ufw enable
sudo ufw status numbered
sudo ufw delete 3

Allow SSH before enabling, or you will lock yourself out of a remote server. ufw is disabled by default on Ubuntu; check sudo ufw status. Application profiles help: sudo ufw app list and sudo ufw allow 'Nginx Full'. Logs go to /var/log/ufw.log with sudo ufw logging low.

firewalld on CentOS

firewalld groups rules into zones (public, internal, trusted, drop) and assigns interfaces to them. It has a runtime configuration (immediate, lost on reload) and a permanent one.

sudo systemctl enable --now firewalld
sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-all
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp
sudo firewall-cmd --permanent --remove-service=dhcpv6-client
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept'
sudo firewall-cmd --reload

Forgetting --permanent means the rule disappears at the next reload or reboot; forgetting --reload means a permanent rule is not active yet. Test a rule first without --permanent, and it self-clears on reload: firewall-cmd --add-port=9000/tcp --timeout=300.

Side by side

Task ufw firewalld
Default deny incoming ufw default deny incoming zone public already drops non-listed traffic
Open web ports ufw allow 80,443/tcp --add-service=http --add-service=https
Allow from one subnet ufw allow from 10.0.0.0/8 to any port 5432 rich rule or a dedicated zone with --add-source
Show rules ufw status numbered firewall-cmd --list-all
Persistence automatic --permanent + --reload

Verify from outside, and from inside

sudo ss -tulpn                       # what is listening (inside)
nmap -Pn -p 22,80,443,3306 server1   # what is reachable (from another host)
nc -zv server1 3306

A port open in the firewall with no process listening is harmless but confusing; a listening process with no firewall rule is unreachable. Both checks together show the real exposure. Databases (3306, 5432, 6379, 27017) should never be open to the world; bind them to private addresses and allow only the application subnet.

Docker note

Docker writes its own iptables rules, so -p 8080:80 can publish a port even when ufw denies it. Bind published ports to localhost (-p 127.0.0.1:8080:80) or manage the DOCKER-USER chain when you add containers later in this series.

Safe change routine

  1. Open a second SSH session as a safety net.
  2. Add the SSH allow rule first, then the others, then enable or reload.
  3. Test new connections from a separate terminal before closing the old one.
  4. Keep an out-of-band console (cloud serial console or hypervisor access) for recovery.
  5. Write the intended open ports in your documentation and review the list quarterly.