Firewall Basics With UFW & Firewalld: A Practical Linux Guide
A Linux server can be fully patched, correctly configured and running only trusted software—and still expose services to networks that should never have been able to reach them.
That is where a host firewall helps.
A firewall decides which network traffic is:
- allowed
- rejected
- dropped
- limited
based on rules you define.
For many Linux systems, two common management tools are:
UFW — popular on Ubuntu and Debian-based systems.
firewalld — common on Fedora, Rocky Linux, AlmaLinux, RHEL and related distributions.
Both ultimately manage Linux packet-filtering capabilities underneath. The difference is mainly how you express and manage the rules.
The principle is the same:
allow only what the server genuinely needs.
What Does a Firewall Actually Do?
Suppose a Linux server runs:
- SSH on TCP 22
- a website on TCP 443
- a database on TCP 3306
Without sensible firewall controls, all three services may potentially be reachable wherever the network routing allows.
But perhaps your requirements are actually:
HTTPS
Accessible from the internet.
SSH
Accessible only from your management network.
MySQL
Accessible only from the application server.
A firewall lets you enforce those boundaries.
That means instead of thinking:
“Which ports should I block?”
a better approach is:
“Which connections should this machine accept at all?”
Firewalls Are Not a Substitute for Secure Services
A firewall is an important layer.
It does not mean you can ignore:
- software updates
- strong authentication
- MFA where applicable
- application security
- SSH hardening
- least privilege
- vulnerability management
If you expose SSH to the internet, allowing TCP 22 through the firewall does not make SSH secure by itself.
Likewise, blocking access to an old vulnerable application from most networks reduces exposure, but it does not repair the vulnerability.
Think:
firewall = control who can reach the service
service security = control what happens once they reach it
Before Changing a Remote Firewall: Protect Your SSH Session
This is the single most important operational warning in this article.
If you are connected remotely over SSH and enable a firewall before allowing SSH, you can lock yourself out.
Before enabling UFW or changing firewalld rules, confirm:
- Which port SSH actually uses.
- Which network should be allowed to reach it.
- Whether you have console/out-of-band recovery access.
- That the allow rule exists before you apply restrictive policy.
Ubuntu’s UFW documentation specifically warns that enabling UFW can affect existing SSH connections and recommends adding the SSH rule before enabling the firewall.
If possible, keep a second SSH session open while testing important firewall changes.
Part 1: UFW Basics
UFW stands for:
Uncomplicated Firewall
It provides a simpler interface for managing a host-based Linux firewall.
Ubuntu describes UFW as primarily intended to make simple host firewall configuration easier.
Check whether it is installed:
ufw --version
Check status:
sudo ufw status
You may see:
Status: inactive
That is normal on a system where UFW has not yet been enabled.
Set Sensible Default Policies
For a typical server, a useful starting point is:
sudo ufw default deny incoming
sudo ufw default allow outgoing
This means:
unsolicited inbound traffic → denied
outbound traffic → allowed
Ubuntu documents UFW’s installation defaults as deny for incoming and forwarded traffic while allowing outgoing connections.
That is a good general host-firewall model for many ordinary servers.
But do not enable it until you have allowed the services you actually need.
Allow SSH First
If the server uses standard SSH:
sudo ufw allow OpenSSH
or explicitly:
sudo ufw allow 22/tcp
Application profiles are useful because they avoid hard-coding ports when UFW already knows the service definition.
List available application profiles:
sudo ufw app list
Inspect one:
sudo ufw app info OpenSSH
UFW supports application profiles stored under /etc/ufw/applications.d.
Restrict SSH to a Management Network
This is better than opening SSH to everyone where your network design permits it.
For example:
sudo ufw allow proto tcp from 192.168.10.0/24 to any port 22
Ubuntu documents this source-restricted syntax directly.
Now only systems from:
192.168.10.0/24
can initiate SSH connections.
That is much better than:
sudo ufw allow 22/tcp
from everywhere if global access is unnecessary.
Allow a Web Server
For HTTP:
sudo ufw allow 80/tcp
For HTTPS:
sudo ufw allow 443/tcp
Or use application profiles where installed.
For example, Apache or Nginx packages may register profiles visible through:
sudo ufw app list
The principle is:
allow the service, not every possible port.
Preview UFW Changes
One useful UFW feature is:
--dry-run
For example:
sudo ufw --dry-run allow 443/tcp
Ubuntu’s current documentation says dry-run outputs the rules that would be generated without actually applying them.
That is especially useful when writing more specific source/interface rules.
Enable UFW
Once the required management rules exist:
sudo ufw enable
Then check:
sudo ufw status verbose
You may see something like:
Status: active
To Action From
-- ------ ----
22/tcp ALLOW 192.168.10.0/24
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
Now test from another machine.
Do not assume the rule is correct simply because UFW accepted the command.
View Rules With Numbers
Run:
sudo ufw status numbered
Example:
[1] 22/tcp ALLOW IN 192.168.10.0/24
[2] 80/tcp ALLOW IN Anywhere
[3] 443/tcp ALLOW IN Anywhere
To delete rule 2:
sudo ufw delete 2
UFW officially supports numbered rule deletion.
Be careful because rule numbers can change after deletions.
Always run:
sudo ufw status numbered
again before deleting another one.
UFW deny vs reject
UFW can:
sudo ufw deny 8080/tcp
or:
sudo ufw reject 8080/tcp
Broadly:
deny
Silently drops the traffic.
reject
Refuses it and sends an appropriate response where supported.
Ubuntu’s UFW manual notes that reject can be useful where you want the sender to know the connection was refused instead of simply timing out.
For most simple server firewall policies, the defaults are usually adequate.
Rate-Limit SSH Attempts
UFW provides:
sudo ufw limit ssh/tcp
The UFW manual documents this as a simple method of limiting repeated SSH connection attempts.
This can help reduce noisy brute-force activity.
But it is not a substitute for:
- SSH keys
- disabling unnecessary password authentication
- MFA where appropriate
- source restrictions
- monitoring
Enable UFW Logging
You can enable logging:
sudo ufw logging on
Or specify a level:
sudo ufw logging medium
Check:
sudo ufw status verbose
UFW uses kernel logging, and systems using rsyslog may log to /var/log/ufw.log.
Logging can help answer:
Why can't this client connect?
But avoid turning on excessively verbose firewall logging permanently on busy systems without considering:
- disk usage
- log rotation
- noise
Part 2: firewalld Basics
firewalld takes a somewhat different approach.
Instead of primarily thinking in terms of one long list of rules, it uses zones to represent different trust levels or network contexts.
The firewalld project describes it as a dynamically managed firewall supporting zones associated with interfaces, connections or source networks.
Examples of predefined zones may include:
- public
- home
- work
- internal
- trusted
- dmz
The actual zones available can be listed with:
sudo firewall-cmd --get-zones
Check Whether firewalld Is Running
Run:
sudo firewall-cmd --state
Expected:
running
firewall-cmd is the primary command-line interface for firewalld.
To see active zones:
sudo firewall-cmd --get-active-zones
Example:
public
interfaces: enp1s0
This means the interface:
enp1s0
is currently associated with the public zone.
Understand firewalld Zones Before Opening Ports
This is critical.
If you run:
sudo firewall-cmd --zone=public --add-service=https
you are allowing HTTPS only within the public zone.
If the relevant network interface is actually bound to:
internal
your rule may have no effect where you expected it.
Check:
sudo firewall-cmd --get-active-zones
and:
sudo firewall-cmd --get-zone-of-interface=enp1s0
firewalld officially supports both commands.
Runtime vs Permanent: The Most Important firewalld Concept
firewalld has two configurations:
runtime
and:
permanent
The runtime configuration is what the kernel is using right now.
The permanent configuration is what firewalld will load after restart/reload.
The firewalld project explicitly says runtime changes are lost when the service stops or reloads unless they are also made permanent.
This catches people constantly.
For example:
sudo firewall-cmd --zone=public --add-service=https
allows HTTPS now.
But after a reboot, that rule may disappear.
Allow HTTPS Permanently With firewalld
One method:
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload
The reload makes the permanent configuration become active.
firewalld documents exactly this model.
Alternatively, you can first test a rule in runtime:
sudo firewall-cmd --zone=public --add-service=https
Confirm everything works.
Then save the working runtime configuration:
sudo firewall-cmd --runtime-to-permanent
This is one of firewalld's nicest safety features.
The official documentation specifically recommends runtime testing and then migrating the working configuration to permanent.
Services Are Usually Better Than Raw Ports
To allow HTTP:
sudo firewall-cmd --zone=public --add-service=http
HTTPS:
sudo firewall-cmd --zone=public --add-service=https
SSH:
sudo firewall-cmd --zone=public --add-service=ssh
firewalld recommends using predefined service definitions for common protocols instead of manually specifying ports where an appropriate service already exists.
List available services:
sudo firewall-cmd --get-services
Opening a Raw Port With firewalld
Sometimes no predefined service exists.
For TCP 8443:
sudo firewall-cmd --zone=public --add-port=8443/tcp
To make it permanent:
sudo firewall-cmd --permanent --zone=public --add-port=8443/tcp
sudo firewall-cmd --reload
firewalld documents both runtime and permanent port rules.
Again:
use a named service when one exists
and:
use raw ports when necessary.
Check What a Zone Allows
Run:
sudo firewall-cmd --zone=public --list-all
You may see:
public
interfaces: enp1s0
services: ssh http https
ports:
For only services:
sudo firewall-cmd --zone=public --list-services
For raw ports:
sudo firewall-cmd --zone=public --list-ports
Always inspect the active configuration after changes.
Remove a Service
Runtime:
sudo firewall-cmd --zone=public --remove-service=http
Permanent:
sudo firewall-cmd --permanent --zone=public --remove-service=http
sudo firewall-cmd --reload
Do not leave old firewall openings indefinitely after an application is removed.
Firewall rules should evolve with the server.
Runtime Testing Is Safer for Remote Changes
This is where firewalld has a particularly useful operational pattern.
Suppose you want to change SSH access.
Apply the new rule at runtime only.
Test it from another terminal.
If it works:
sudo firewall-cmd --runtime-to-permanent
If it does not work:
sudo firewall-cmd --reload
The reload discards runtime-only experimentation and re-applies the known permanent configuration.
That can be much safer than immediately making an untested remote rule permanent.
UFW or firewalld: Which Should You Use?
In most cases:
use the firewall management system already standard for your distribution.
UFW
A good fit for:
- Ubuntu
- simple server rules
- users wanting straightforward allow/deny syntax
Typical workflow:
set defaults → allow required services → enable → inspect
firewalld
A good fit for:
- Fedora
- RHEL
- Rocky Linux
- AlmaLinux
- environments benefiting from zones and dynamic configuration
Typical workflow:
identify zone → test runtime rule → verify → make permanent
Neither is automatically “more secure.”
Security depends on the rules you configure.
Do Not Run UFW and firewalld as Competing Managers
Avoid trying to manage the same host simultaneously with:
- UFW
- firewalld
- hand-written nftables rules
- old iptables scripts
unless you understand exactly how those components interact.
Having several systems manipulate packet-filtering rules can make troubleshooting extremely confusing.
Choose one authoritative management method for ordinary firewall administration.
Default Deny Is Usually the Right Mental Model
Instead of:
allow everything and block known bad things
prefer:
deny unsolicited inbound traffic and explicitly allow required services.
For example, a web server might need:
22/tcp management network only
80/tcp internet
443/tcp internet
It probably does not need random exposure of:
- database
- Redis
- management dashboards
- internal APIs
Ask of every open service:
Who needs to reach this?
Then restrict accordingly.
Databases Should Rarely Be Open to the Whole Internet
Suppose MySQL uses:
3306/tcp
Do not casually run:
sudo ufw allow 3306/tcp
or:
sudo firewall-cmd --add-port=3306/tcp
from everywhere.
If only application server:
10.10.20.15
needs access, restrict the source.
With UFW:
sudo ufw allow proto tcp from 10.10.20.15 to any port 3306
For firewalld, source-specific policy can be expressed through zones, source bindings or richer rules depending on the environment.
The general principle matters more than the syntax:
internal service → internal access only.
Check Which Services Are Actually Listening
Before opening a port, establish whether anything is even listening on it.
Run:
sudo ss -lntup
For TCP listeners:
sudo ss -lntp
You might discover:
0.0.0.0:22
0.0.0.0:443
127.0.0.1:5432
The address matters.
0.0.0.0:443
Listening on all IPv4 interfaces.
127.0.0.1:5432
Listening only on localhost.
A firewall rule opening PostgreSQL externally is pointless if PostgreSQL itself only listens on localhost.
Conversely, an application accidentally listening on all interfaces may deserve configuration changes even if the firewall currently protects it.
Test From Another Machine
Do not test only from the server itself.
From another system:
nc -vz server.example.com 443
or:
Test-NetConnection server.example.com -Port 443
for a Windows test client.
This proves the actual network path.
A local:
curl localhost
does not tell you whether the firewall permits remote access.
Firewall Troubleshooting: Check the Layers in Order
If an application is unreachable:
- Is the service running?
- Is it listening on the expected address/port?
- Is the host firewall allowing it?
- Is upstream routing correct?
- Is there another firewall/security group?
- Is NAT/port forwarding required?
- Is DNS pointing at the right address?
Do not immediately disable the firewall.
A firewall is only one layer.
Never “Fix” Connectivity With ufw disable
or:
systemctl stop firewalld
as the permanent solution.
Temporarily proving that the firewall is involved can sometimes be diagnostically useful in a controlled environment.
But the correct fix is:
add the minimum required rule
not:
remove the whole security boundary.
Cloud Servers May Have Two Firewalls
A cloud VM may have:
Linux host firewall
plus:
cloud network security rules
such as:
- Azure Network Security Groups
- AWS Security Groups
- another provider's network firewall
Both must permit the connection.
For example:
UFW allows 443
but:
cloud security group blocks 443
Result:
HTTPS still fails.
Likewise, opening a cloud security group while UFW blocks the traffic still fails.
Know where all enforcement layers are.
Container Platforms Can Complicate Firewall Behaviour
Docker and other container platforms may add their own packet-filtering or NAT rules.
That means firewall behaviour can be less straightforward than:
UFW says deny → therefore Docker port is definitely unreachable.
Do not assume ordinary host-firewall rules automatically describe every container exposure.
Check:
docker ps
and inspect published ports.
For production container hosts, design firewall policy with the container networking model in mind rather than treating Docker as an ordinary host process.
Keep Firewall Rules Documented
A rule such as:
allow 8443/tcp
becomes mysterious six months later.
Where supported, use comments or maintain documentation explaining:
- purpose
- owner
- source network
- service
- expiry/review date
For example, UFW supports rule comments in current syntax.
You want to be able to answer:
Why is this port open?
If nobody knows, it should be investigated.
Review Rules Regularly
Firewalls accumulate history.
An administrator opens a port for a migration.
The migration finishes.
The rule stays forever.
Review:
sudo ufw status numbered
or:
sudo firewall-cmd --list-all
periodically.
Remove openings belonging to:
- old applications
- retired servers
- temporary projects
- obsolete management networks
Firewall hygiene is an ongoing process.
A Practical UFW Example
A simple Ubuntu web server might use:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow proto tcp from 192.168.10.0/24 to any port 22
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Result:
SSH → management network only
HTTP/HTTPS → public
everything else unsolicited inbound → denied
That is much better than opening services whenever an application complains.
A Practical firewalld Example
Suppose a Rocky Linux web server uses the public zone.
Check:
sudo firewall-cmd --get-active-zones
Test rules at runtime:
sudo firewall-cmd --zone=public --add-service=http
sudo firewall-cmd --zone=public --add-service=https
Confirm the site works.
Then save:
sudo firewall-cmd --runtime-to-permanent
Verify:
sudo firewall-cmd --zone=public --list-all
That runtime-first approach gives you a clean testing and rollback workflow.
The Firewall Checklist
Before considering a Linux firewall finished:
- Identify required inbound services.
- Identify who actually needs access to each service.
- Deny unsolicited inbound traffic by default where appropriate.
- Allow SSH before enabling restrictive rules remotely.
- Restrict management services to trusted sources where possible.
- Prefer named services over raw ports where supported.
- Check the active interface/zone.
- Test rules from another host.
- Make firewalld rules permanent only after validation where practical.
- Check IPv4 and IPv6 behaviour.
- Review container/cloud firewall layers.
- Enable useful—not excessive—logging.
- Remove obsolete rules.
- Document why each non-obvious opening exists.
- Maintain console/out-of-band recovery for critical remote systems.
The key principle is:
A firewall should expose exactly what the server needs—nothing more.
UFW vs firewalld at a Glance
| UFW | firewalld |
|---|---|
| Straightforward rule syntax | Zone-based management |
| Common on Ubuntu | Common on Fedora/RHEL derivatives |
ufw allow 443/tcp | firewall-cmd --add-service=https |
| Persistence built into UFW rules | Runtime and permanent configs are separate |
| Easy for simple servers | Excellent for dynamic/multi-zone environments |
| Supports dry runs | Excellent runtime-testing workflow |
Use whichever is appropriate for the system you are administering.
Do not switch firewall managers simply because another distribution uses a different one.
How Hamilton Group Can Help
Linux firewall problems can range from one missing rule to a poorly designed network exposing services that should never have been internet-accessible.
Hamilton Group can help businesses with:
- Linux servers
- UFW
- firewalld
- host firewalls
- SSH security
- cloud servers
- network segmentation
- VPN
- server hardening
- vulnerability management
- managed IT support
For business systems, the goal is not simply to make an application reachable.
It is to make it reachable only from the people and systems that genuinely need it.
Visit hgmssp.com or call 0330 043 0069.