ufw Setup in Linux: Server Firewall and nftables Basics
Setting up ufw comes down to three steps: set the default policies (sudo ufw default deny incoming and allow outgoing), allow SSH and the required ports (sudo ufw allow 22/tcp, sudo ufw allow 443/tcp), then enable the firewall with sudo ufw enable. Rules are checked with sudo ufw status numbered, and under the hood ufw manages netfilter through iptables or nftables.
A Linux firewall decides which network packets to let into the server and which to drop. The kernel does this through the netfilter subsystem, and it is controlled by utilities: the low-level iptables and nftables, and the ufw front end (Uncomplicated Firewall). On most Ubuntu and Debian servers, ufw setup takes five minutes: block all incoming traffic, open SSH and the service ports, and turn the firewall on. Below are a step-by-step scenario, typical mistakes and the minimum of nftables you need to understand what happens under the hood.
How the firewall works in Linux
Packet filtering lives in the kernel, in netfilter. It provides interception points (hooks): incoming traffic to the host itself, outgoing traffic and transit traffic (forward). User-space utilities only write rules into the kernel for these points.
| Tool | Level | When to use |
|---|---|---|
ufw | front end, simple syntax | a single server on Ubuntu/Debian, typical rules |
firewalld | front end with zones | RHEL, Rocky, Fedora |
nftables (nft) | low-level, modern | complex rules, NAT, address sets |
iptables | low-level, being phased out | old systems and scripts |
Important: use one management tool. If you edit rules through ufw and at the same time by hand through iptables, they will overwrite each other and the picture becomes unpredictable.
Installing and checking ufw
On Ubuntu ufw is usually already installed but disabled. On Debian you install it separately:
$ sudo apt update
$ sudo apt install ufw
$ sudo ufw status
Status: inactive
The inactive status means ufw rules are not applied. It is too early to enable the firewall: first allow SSH, otherwise on a remote server you will cut off your own access.
Step-by-step ufw setup on a server
Step 1. Default policies
The default policy is what to do with a packet if no rule matches. The safe scheme for a server: incoming is denied, outgoing is allowed.
$ sudo ufw default deny incoming
Default incoming policy changed to 'deny'
$ sudo ufw default allow outgoing
Default outgoing policy changed to 'allow'
Replies to your own outgoing connections pass automatically: ufw tracks connection state (connection tracking), so there is no need to open ports for responses.
Step 2. Allow SSH
$ sudo ufw allow OpenSSH
Rules updated
Rules updated (v6)
OpenSSH is an application profile. If SSH listens on a non-standard port, allow that port explicitly: sudo ufw allow 2222/tcp. To check which port sshd actually listens on, run sudo ss -tlnp | grep sshd.
Step 3. Open the service ports
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
Always specify the protocol: without /tcp ufw opens the port for both TCP and UDP, and an extra open door is not needed.
Step 4. Enable the firewall
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
ufw starts right away and will come up on every boot. Do not close your current SSH session: open a second one and make sure the connection works.
Step 5. Check the rules
$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)
443/tcp (v6) ALLOW IN Anywhere (v6)
ufw creates the IPv6 rules itself if /etc/default/ufw contains IPV6=yes (the default value).
How to open a port in ufw: all the syntax options
The ufw allow command understands a short and a full form. The short form suits typical cases; the full form is for when you need to specify a source, an interface or a destination address.
| Task | Command |
|---|---|
| TCP port | sudo ufw allow 443/tcp |
| UDP port | sudo ufw allow 51820/udp |
| Port range | sudo ufw allow 6000:6007/tcp |
| By service name | sudo ufw allow https |
| From a single IP | sudo ufw allow from 203.0.113.10 |
| From a subnet to a port | sudo ufw allow from 10.0.0.0/24 to any port 5432 proto tcp |
| On an interface | sudo ufw allow in on eth1 to any port 3306 proto tcp |
| Deny | sudo ufw deny 23/tcp |
| Reject with a response | sudo ufw reject 25/tcp |
The difference between deny and reject: deny silently drops the packet, and the client waits until a timeout; reject sends a response saying the port is closed. For the outside world deny is usually the choice; for internal networks reject is more convenient, because clients see the error immediately.
Database access from a single server only
A common task: PostgreSQL or MySQL must not be visible from the internet, but an application on a neighbouring server must be able to connect.
$ sudo ufw allow from 10.0.0.5 to any port 5432 proto tcp
Rule added
Port 5432 stays closed to everyone else thanks to the deny incoming policy. It is also worth configuring the database itself to listen only on the internal address: the firewall and the service settings protect each other.
Application profiles
Packages can place descriptions of their ports in /etc/ufw/applications.d/. The list and the contents of the profiles:
$ sudo ufw app list
Available applications:
Nginx Full
Nginx HTTP
Nginx HTTPS
OpenSSH
$ sudo ufw app info "Nginx Full"
Profile: Nginx Full
Title: Web Server (Nginx, HTTP + HTTPS)
Description: Small, but very powerful and efficient web server
Ports:
80,443/tcp
To allow a profile: sudo ufw allow "Nginx Full". Put names with spaces in quotes. You can describe your own profile in INI format in the same directory and apply it with sudo ufw app update name.
How to delete a ufw rule
The most reliable way is by number:
$ sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] OpenSSH ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] OpenSSH (v6) ALLOW IN Anywhere (v6)
[ 5] 80/tcp (v6) ALLOW IN Anywhere (v6)
[ 6] 443/tcp (v6) ALLOW IN Anywhere (v6)
$ sudo ufw delete 2
Deleting:
allow 80/tcp
Proceed with operation (y|n)? y
Rule deleted
After a deletion the numbers shift, so run status numbered again before the next deletion. The second way is to repeat the rule with the word delete: sudo ufw delete allow 80/tcp; it removes both the IPv4 and the IPv6 variant at once.
Rule order and inserting at the top
ufw checks rules from top to bottom, and the first matching one applies. So a deny for a specific address must sit above a general allow:
$ sudo ufw insert 1 deny from 198.51.100.23
Rule inserted
If you add a deny with the usual command, it lands at the end of the list after allow 443/tcp and will not take effect for that port.
Protecting SSH from brute force: ufw limit
The limit rule allows the connection but blocks an address that has opened 6 or more connections within 30 seconds:
$ sudo ufw limit OpenSSH
Rule updated
This is simple protection against mass password guessing. For serious protection, also allow only key-based login and, if you like, add fail2ban, which can ban addresses through ufw.
ufw logs
Logging is enabled with sudo ufw logging on; levels are low, medium, high and full. Entries go to /var/log/ufw.log (through rsyslog) and to the kernel log:
$ sudo journalctl -k | grep 'UFW BLOCK' | tail -2
Oct 14 10:12:03 web kernel: [UFW BLOCK] IN=eth0 OUT= SRC=45.155.205.1 DST=192.0.2.10 PROTO=TCP SPT=51544 DPT=3389
Oct 14 10:12:09 web kernel: [UFW BLOCK] IN=eth0 OUT= SRC=89.248.165.7 DST=192.0.2.10 PROTO=TCP SPT=40312 DPT=23
The SRC and DPT fields show who knocked and on which port. If a service does not respond and the log has UFW BLOCK for its port, the rule was not added.
ufw and Docker: why ports are open despite the rules
This is the most notorious trap. When Docker publishes a port (-p 8080:80), it adds its own NAT rules and the DOCKER chain itself, and traffic to the container goes through forward, bypassing the incoming rules of ufw. As a result ufw status does not show port 8080, yet it is reachable from outside.
Ways to solve it:
- Publish ports only on localhost:
-p 127.0.0.1:8080:80, and expose them to the outside through a reverse proxy (nginx). - Add your own rules to the
DOCKER-USERchain — Docker does not overwrite it. - Do not publish the ports of internal containers at all, and connect them through the Docker network.
Reset and disable
$ sudo ufw disable # turn off, rules are kept
$ sudo ufw reset # delete all rules and turn off
$ sudo ufw reload # re-read the configuration
Before reset, ufw saves copies of the rule files in /etc/ufw/ with a date suffix. The rules themselves are stored in /etc/ufw/user.rules and user6.rules — do not edit them by hand, use the commands only.
nftables basics
nftables is the modern replacement for iptables in the Linux kernel. On Debian 10+ and Ubuntu 20.10+ the iptables command works by default through the iptables-nft compatibility layer, so ufw rules also end up in nftables. To see everything loaded into the kernel:
$ sudo nft list ruleset
The nftables structure: a table (family ip, ip6, or inet for both) contains chains attached to netfilter hook points, and chains contain rules. A minimal server firewall in the file /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
ip protocol icmp accept
meta l4proto ipv6-icmp accept
tcp dport { 22, 80, 443 } accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Apply it and enable it at boot:
$ sudo nft -c -f /etc/nftables.conf # only check the syntax
$ sudo systemctl enable --now nftables
Rules can also be added on the fly, but they disappear after a reboot unless you write them to the file:
$ sudo nft add rule inet filter input tcp dport 5432 ip saddr 10.0.0.5 accept
$ sudo nft -a list chain inet filter input # -a shows the handle of each rule
$ sudo nft delete rule inet filter input handle 9
The main advantage of nftables is sets ({ 22, 80, 443 }) and a single inet table for IPv4 and IPv6. Do not run your own nftables.service at the same time as ufw: flush ruleset will wipe the ufw rules.
Common mistakes
- ufw was enabled without an SSH rule — access is lost. The provider's console (VNC/serial) can save you:
sudo ufw allow OpenSSHorsudo ufw disable. - The port is open, but the service is unreachable — the service listens on
127.0.0.1, not on the external address. Checkss -tlnp. - There is also a cloud firewall — many providers have security groups; the port must be opened there too.
- deny does not work — the rule sits below an allow rule; use
insert 1.
Summary
Basic ufw setup means default deny incoming, allowing SSH and the service ports, ufw enable, and a check with status numbered. For targeted access use rules with from and port, for SSH use limit, and when working with Docker remember that it bypasses ufw. When you need NAT or complex rule sets, move to nftables. Protecting a server with a firewall is covered in practice in our Linux course.
FAQ
How is ufw different from iptables?
ufw is a front end with simple syntax that generates iptables or nftables rules itself. iptables and nftables are low-level tools that give full control but require more knowledge.
Do ufw rules survive a reboot?
Yes. After sudo ufw enable the firewall starts at boot, and the rules are stored in /etc/ufw/user.rules and user6.rules.
Do I need to open ports for outgoing connections?
With the allow outgoing policy, no. ufw passes replies to outgoing connections automatically thanks to connection tracking.
How do I allow ping when ufw is enabled?
By default ufw already passes incoming ICMP echo requests: this rule is defined in /etc/ufw/before.rules. To block ping, change ACCEPT to DROP for that rule in this file and run sudo ufw reload.
Is ufw available on CentOS and Rocky Linux?
Their standard tool is firewalld with the firewall-cmd command. ufw can be installed from EPEL, but it is better not to mix it with firewalld.
This topic is covered in depth with practice in the course “Linux: с нуля до уверенного администрирования”.
Go to course