Server Firewall
A server with no firewall is a server that trusts everyone equally. That sounds generous until you remember that the internet is full of automated scanners working their way through IP ranges, probing every port they can reach, looking for anything that responds. Your homelab server will be found. The question is what happens when it is.
UFW is not the interesting part of server security. It is the boring, foundational part that makes the interesting parts possible. You configure it once, you make sure it is correct, and then you mostly stop thinking about it. This article is about getting to that point.
What UFW is and is not
UFW stands for Uncomplicated Firewall. The name is accurate. It is a frontend for iptables, the Linux kernel’s built-in packet filtering system, designed to make common firewall tasks expressible in plain commands rather than iptables syntax. The underlying mechanism is the same. The interface is considerably less painful.
What UFW does: it controls which inbound connections are allowed to reach your server, and from where.
What UFW does not do: it does not replace SSH key authentication, it does not monitor for intrusion, it does not apply security patches, and it does not protect you from services that are running and misconfigured. It is a door with a lock. The lock is necessary. The lock is not sufficient.
With that established, here is how to configure it properly.
Before you enable it
The single most common UFW mistake is enabling the firewall before allowing SSH access, then losing the ability to connect to the server remotely. If you are working on a machine you can physically access, this is embarrassing. If you are working on a remote server with no out-of-band access, it is a much bigger problem.
Allow SSH first. Enable the firewall second. Always.
sudo ufw allow OpenSSH
If you have moved SSH to a non-standard port, which is worth doing and worth covering separately, allow that port instead:
sudo ufw allow 2222/tcp
Do not enable UFW until at least this rule is in place.
Setting the defaults
UFW’s default policies are the most important rules you will set, because they determine what happens to any connection that does not match a specific rule.
The correct defaults for a server are:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Deny everything inbound. Allow everything outbound. From that baseline, you explicitly open only what you need. Every port that stays closed is an attack surface that does not exist.
Some people argue about whether to deny or reject by default. Deny drops packets silently; reject returns an error. For an internet-facing server, deny is the right choice. There is no reason to confirm to a scanner that a port exists on this host.
Rate limiting SSH
A plain allow rule for SSH will accept connections from any source, as many as want to arrive. That is fine for legitimate use, but it also means a brute-force attack against your SSH daemon can proceed unrestricted.
UFW has a built-in rate limiter that blocks an IP after six failed connection attempts within 30 seconds. Use it:
sudo ufw limit OpenSSH
Or, if on a custom port:
sudo ufw limit 2222/tcp
Replace the plain allow rule with a limit rule. It costs nothing and meaningfully reduces the noise hitting your authentication logs.
Opening ports for services
Beyond SSH, only open what actually needs to be accessible from outside the server. For each service, think about who needs to reach it and from where.
For services that are genuinely public, such as a web server behind a reverse proxy:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
For services that should only be accessible from your local network, restrict by source subnet rather than opening them to the world:
sudo ufw allow from 192.168.1.0/24 to any port 1883
sudo ufw allow from 192.168.1.0/24 to any port 9000
Replace 192.168.1.0/24 with your actual LAN subnet. A service restricted this way is completely invisible to anything outside your network, regardless of whether it is running and listening.
For services that should only be accessible from specific hosts, use individual IPs:
sudo ufw allow from 192.168.1.10 to any port 5432
That is the correct approach for a database port. Databases are not public services. They should never be reachable from outside a trusted source.
Enable it
Once the rules are in place, enable UFW:
sudo ufw enable
Confirm it is active and review the rules:
sudo ufw status verbose
The output should show your default policies, your explicit rules, and a status of active. Read through it. Make sure it reflects what you intended. Rule sets that accumulate over time develop gaps, and the time to catch them is now rather than later.
Logging
UFW can log connection attempts. Logging at medium level is a reasonable baseline:
sudo ufw logging medium
Logs end up in /var/log/ufw.log. Reading them occasionally is worth doing, not because every blocked connection means something is wrong, but because the pattern of what is being attempted tells you something about what is pointed at your server. It is also useful for debugging rules that are not behaving as expected.
A note on Docker
UFW has a well-documented interaction with Docker that is worth knowing about before it surprises you. Docker modifies iptables rules directly, and by default those modifications bypass UFW entirely. A port that Docker exposes will be reachable from the internet even if UFW has no rule permitting it.
If you are running Docker on this server, the firewall configuration above is not the complete picture. Restricting Docker’s iptables access is a separate step, and one that deserves its own treatment. The short version is that you should set "iptables": false in Docker’s daemon configuration and manage routing manually, or use a tool like ufw-docker to bridge the two systems. The details will land in the Docker article in this series.
For now, the important thing is to know the interaction exists. Do not assume that a UFW deny rule will block a Docker-exposed port.
The baseline in one block
This is what a clean starting state looks like for a server running SSH, a reverse proxy, an MQTT broker, and a few LAN-only services:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw limit OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 192.168.1.0/24 to any port 1883
sudo ufw allow from 192.168.1.0/24 to any port 9000
sudo ufw logging medium
sudo ufw enable
From here, add rules as services are added. Remove rules when services are removed. Check the rule set periodically and delete anything that no longer corresponds to something actually running.