TL;DR:
- To set up UFW on Ubuntu 26.04, set the default policies, allow SSH with
sudo ufw allow OpenSSH, then runsudo ufw enable. Ubuntu installs UFW turned off. - Ubuntu 26.04 LTS packages UFW 0.36.2 (
0.36.2-9build1), which writes its rules through iptables 1.8.11 on the nf_tables backend. - Before you enable UFW over SSH, schedule
ufw disable10 minutes ahead withsystemd-run, so a rule that locks you out undoes itself. sudo ufw limit OpenSSHrefuses an address that opens 6 or more SSH connections within 30 seconds.- Ports that Docker publishes skip UFW. Publish containers on
127.0.0.1, or filter them in theDOCKER-USERchain from/etc/ufw/after.rules.
Applies to: Ubuntu 26.04 LTS ยท UFW 0.36.2 ยท Docker Engine 29.9.3 ยท checked November 2026
The Uncomplicated Firewall (UFW) is Ubuntu's default firewall configuration tool: a command-line front end, written in Python and licensed under the GPL v3, that writes allow and deny rules for the Linux kernel's netfilter packet filter. This tutorial sets up UFW on an Ubuntu 26.04 server over SSH, with a timed automatic disable while you test, and covers rate limiting, rules for one IP address, logs, Docker-published ports and IPv6.
Does Ubuntu 26.04 have a firewall?
Yes. The Linux kernel includes the netfilter packet filter, and UFW is Ubuntu's default tool for configuring it. Ubuntu's 26.04 server cloud images include the ufw package, and the firewall stays off until you enable it.
UFW's manual and its configuration files set these defaults:
| Setting | Default |
|---|---|
| Firewall | Disabled |
| Incoming connections | Deny |
| Outgoing connections | Allow |
| Routed (forwarded) traffic | Deny |
| IPv6 filtering | On (IPV6=yes in /etc/default/ufw) |
| Logging | On, level low |
Prerequisites
- A server running Ubuntu 26.04 LTS with SSH access. Arct Cloud Linux VPS images include Ubuntu 26.04 LTS and accept an SSH public key at deploy. DDoS protection is included on every plan.
- A non-root user with sudo privileges and SSH key login. How to Generate an SSH Key on Windows, macOS, and Linux creates the key and the sudo user.
- A second terminal on your computer, to test a new SSH login before you close the first session.
- The public IP address you connect from, for Step 7.
- For the Docker section, Docker Engine from Docker's apt repository, as in How to Install Docker and Docker Compose on Ubuntu 24.04 and 26.04.
Commands run as the sudo user. Replace values written in capitals, such as YOUR_SERVER_IP, with your own. The examples use 198.51.100.20 as your own address and deploy as the sudo user.
Step 1: Check that UFW is installed and see its status
Check the UFW version and whether the firewall is on:
ufw --version
sudo ufw status
The output looks similar to this:
ufw 0.36.2
Copyright 2008-2023 Canonical Ltd.
Status: inactive
If the shell cannot find the ufw command, or sudo prints sudo: 'ufw': command not found, install the package:
sudo apt update
sudo apt install ufw
Step 2: Set the default policies
A default policy decides what happens to traffic that no rule matches. Deny incoming connections and allow outgoing ones:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
Default outgoing policy changed to 'allow'
(be sure to update your rules accordingly)
These match the installation defaults, so the commands matter on a server where someone changed them. UFW's built-in rules still accept ping requests and DHCP client traffic.
Step 3: Allow SSH before you turn the firewall on
With incoming connections denied, UFW blocks new SSH logins unless a rule allows them. The openssh-server package installs a UFW application profile named OpenSSH for port 22/tcp. List the profiles on the server:
sudo ufw app list
Available applications:
OpenSSH
Allow SSH:
sudo ufw allow OpenSSH
Rules updated
Rules updated (v6)
UFW saves the rule for IPv4 and IPv6 and applies it when the firewall starts. sudo ufw allow 22/tcp and sudo ufw allow ssh create the same rule by port number and service name. If SSH listens on a port other than 22, allow that port instead, for example sudo ufw allow 2222/tcp.
Step 4: Enable UFW without locking yourself out
A missing or wrong SSH rule blocks your next login. Before you enable UFW, schedule a command that turns it off again in 10 minutes. systemd-run creates a one-time systemd timer:
sudo systemd-run --unit=ufw-undo --on-active=10m /usr/sbin/ufw disable
Running timer as unit: ufw-undo.timer
Will run service as unit: ufw-undo.service
Turn on the firewall. Over SSH, UFW asks for confirmation; type y:
sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
UFW accepts packets that belong to connections already open, so the current session stays up. In a second terminal, log in again:
ssh deploy@YOUR_SERVER_IP
If the new login works, cancel the timer and confirm that it is gone:
sudo systemctl stop ufw-undo.timer
systemctl list-timers ufw-undo.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
0 timers listed.
Pass --all to see loaded but inactive timers, too.
If the login times out, keep the first session open and fix the SSH rule, or wait for the timer to turn UFW off and repeat this step. By default, systemd runs a timer up to 1 minute after its set time. The timer's ufw disable also keeps UFW off after a reboot.
Check the firewall:
sudo ufw status verbose
The output looks similar to this:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp (OpenSSH) ALLOW IN Anywhere
22/tcp (OpenSSH (v6)) ALLOW IN Anywhere (v6)
disabled (routed) means IP forwarding is off. Docker turns forwarding on, and the line then reads deny (routed).
To turn the firewall off and keep your rules, run sudo ufw disable. UFW prints Firewall stopped and disabled on system startup and stays off after a reboot. sudo ufw enable turns the same rules back on.
Step 5: Allow ports for the services you run
Open only the ports that must be reachable from the internet, each with its protocol. For a web server on HTTP and HTTPS and a WireGuard VPN server on UDP port 51820:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 51820/udp
Each command prints Rule added and Rule added (v6). List the rules:
sudo ufw status
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
51820/udp ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW Anywhere (v6)
51820/udp (v6) ALLOW Anywhere (v6)
A port without /tcp or /udp opens both protocols. A range uses a colon and needs a protocol, as in sudo ufw allow 8000:8010/tcp.
Packages can install their own profile: after you install Nginx from the Ubuntu archive, sudo ufw allow 'Nginx Full' opens 80 and 443 over TCP. The Nginx, PostgreSQL, and Redis guide sets up that web stack.
Step 6: Limit repeated SSH connection attempts
A limit rule accepts a connection, but denies an address that starts 6 or more connections within 30 seconds. The UFW manual describes it as protection against brute-force login attacks. Change the SSH rule from allow to limit:
sudo ufw limit OpenSSH
Rule updated
Rule updated (v6)
In testing, the sixth SSH login within 30 seconds failed with Connection refused, and the next login worked after 30 seconds without attempts. The limit applies to new connections only, so open sessions continue. Scripts that open many SSH connections in a row, such as a loop of scp commands, reach the limit too. To exempt their address, insert an allow rule above the limit rule with sudo ufw insert 1 allow from 198.51.100.20 to any port 22 proto tcp. A rule added with sudo ufw allow goes below it, where it never matches.
Step 7: Allow a service only from your own IP address
A rule with from opens a port to one address or network. In your SSH session, print the address you connect from:
echo "${SSH_CONNECTION%% *}"
The first value of SSH_CONNECTION is your client address, such as 198.51.100.20. To let only that address reach a PostgreSQL server on port 5432:
sudo ufw allow from 198.51.100.20 to any port 5432 proto tcp comment 'PostgreSQL from my IP'
Replace 198.51.100.20 with your address, or with a network such as 198.51.100.0/24. UFW prints Rule added once, because an IPv4 source gets an IPv4 rule only, and the comment appears in sudo ufw status.
UFW checks rules in order, and the first match wins. A broader rule for the same port, such as sudo ufw allow 5432/tcp, still lets every address in, so delete it in Step 8. To block one address, put a deny rule at the top with sudo ufw insert 1 deny from 192.0.2.50, because sudo ufw deny adds it after your allow rules.
To allow SSH only from your address, schedule the Step 4 timer again, add the narrower rule and delete the OpenSSH limit rule:
sudo systemd-run --unit=ufw-undo --on-active=10m /usr/sbin/ufw disable
sudo ufw allow from 198.51.100.20 to any port 22 proto tcp
sudo ufw delete limit OpenSSH
Running timer as unit: ufw-undo.timer
Will run service as unit: ufw-undo.service
Rule added
Rule deleted
Rule deleted (v6)
Test a new login from a second terminal, then stop the timer with sudo systemctl stop ufw-undo.timer. The new rule covers IPv4 only, so SSH over IPv6 closes. If your address changes, as it does on many home connections, SSH stops working until you update the rule. The rule numbers in Step 8 then differ from the listing shown.
Step 8: List and delete rules
List the rules with their numbers:
sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] OpenSSH LIMIT IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 51820/udp ALLOW IN Anywhere
[ 5] 5432/tcp ALLOW IN 198.51.100.20 # PostgreSQL from my IP
[ 6] OpenSSH (v6) LIMIT IN Anywhere (v6)
[ 7] 80/tcp (v6) ALLOW IN Anywhere (v6)
[ 8] 443/tcp (v6) ALLOW IN Anywhere (v6)
[ 9] 51820/udp (v6) ALLOW IN Anywhere (v6)
Delete a rule by its number. UFW shows the rule and asks for confirmation:
sudo ufw delete 5
Deleting:
allow from 198.51.100.20 to any port 5432 proto tcp comment 'PostgreSQL from my IP'
Proceed with operation (y|n)? y
Rule deleted
Numbers shift after each delete, so list the rules again before the next one. A delete by number removes one line: deleting rule 4 here would leave 51820/udp (v6) in place. To remove both copies, delete the rule by the text you added it with:
sudo ufw delete allow 51820/udp
Rule deleted
Rule deleted (v6)
sudo ufw reset deletes every rule, turns UFW off and restores before.rules and after.rules to the package versions, which also removes the Docker block below. It keeps the old files in /etc/ufw with a date suffix, such as after.rules.20261106_190013. After a reset, start again from Step 3 and add the Docker block again if you use it.
Step 9: See what the firewall blocks in the UFW logs
UFW logs blocked packets at the low level, and sudo ufw status verbose shows Logging: on (low). If the line says off, turn logging on:
sudo ufw logging on
Logging enabled
Read blocked packets from the kernel log:
sudo journalctl -k -g "UFW BLOCK"
On servers that run rsyslog, which Ubuntu's cloud images include, the rsyslog rule from the ufw package also writes these lines to /var/log/ufw.log:
sudo tail -F /var/log/ufw.log
The file appears with the first logged packet. Until then, tail -F reports No such file or directory and keeps waiting. Each line contains a prefix such as [UFW BLOCK] or [UFW LIMIT BLOCK] and names the source address (SRC=), the destination port (DPT=) and the protocol (PROTO=). The medium level adds new connections, invalid packets and allowed packets that do not match the default policy. The UFW manual warns that levels above medium can fill the disk.
Then compare the ports that programs listen on with the rules that cover them:
sudo ufw show listening
The output looks similar to this:
tcp:
22 * (sshd)
[ 1] limit OpenSSH
80 * (nginx)
[ 2] allow 80/tcp
...
A listening port with no rule under it is closed to other computers, unless its process is docker-proxy: that marks a port Docker published, which the next section covers. To test a web port from your computer, request its headers:
curl --max-time 5 -I http://YOUR_SERVER_IP
HTTP/1.1 200 OK
Server: nginx/1.28.3 (Ubuntu)
...
A port that UFW drops fails after about 5 seconds with curl: (28) Connection timed out. A port that UFW allows but no program listens on fails at once with curl: (7) Failed to connect.
Keep Docker containers from bypassing UFW
Docker's documentation states that Docker routes published container ports in the nat table, before packets reach the INPUT chain that UFW uses, so UFW rules do not apply to them. In a test with Docker Engine 29.9.3 on Ubuntu 26.04, a container started with -p 8080:80 answered requests from another computer, while sudo ufw status had no rule for port 8080.
Option A: Publish containers on 127.0.0.1
A port published on the loopback address is reachable from the server only. Run a reverse proxy on the host, such as Nginx on ports 80 and 443, in front of it:
sudo docker run -d --name web -p 127.0.0.1:8080:80 nginx:1.30.6-alpine
In a Compose file, write the port as "127.0.0.1:8080:80". Docker's Compose reference says a port without a host IP binds to all interfaces and bypasses host firewall rules. Docker also documents that releases older than 28.0.0 let hosts on the same network segment reach ports published to localhost, so run Docker Engine 28.0 or later.
Option B: Filter published ports in the DOCKER-USER chain
For a container port that must stay public, Docker's documentation names the DOCKER-USER chain as the place for your own rules, checked before Docker's. Rules in /etc/ufw/after.rules load each time UFW starts or reloads. Find the server's public network interface:
ip route show default
default via 203.0.113.1 dev eth0
The interface name follows dev, here eth0. Append this block to /etc/ufw/after.rules. Run the command once, and replace eth0 and 198.51.100.20 with your interface and address. The block runs from *filter to COMMIT. To remove it later, delete those lines and run sudo iptables -F DOCKER-USER:
sudo tee -a /etc/ufw/after.rules > /dev/null <<'EOF'
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -i eth0 -p tcp -s 198.51.100.20 -m conntrack --ctorigdstport 8080 -j RETURN
-A DOCKER-USER -i eth0 -m conntrack --ctstate NEW -j DROP
COMMIT
EOF
- The first rule passes packets of open connections, including those that containers start.
- The second rule passes new connections from your address to host port 8080.
--ctorigdstportmatches the port before Docker translates it, as Docker's iptables documentation explains. Remove-s 198.51.100.20to open the port to every address. For another port, add a second--ctorigdstportline above theDROPline withsudo nano /etc/ufw/after.rules, then runsudo ufw reload. - The third rule drops every other new connection on
eth0to a container, the same default deny that UFW applies to the host.
Reload UFW and list the chain:
sudo ufw reload
sudo iptables -S DOCKER-USER
Firewall reloaded
-N DOCKER-USER
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -s 198.51.100.20/32 -i eth0 -p tcp -m conntrack --ctorigdstport 8080 -j RETURN
-A DOCKER-USER -i eth0 -m conntrack --ctstate NEW -j DROP
From another computer, curl --max-time 5 http://YOUR_SERVER_IP:8080 now fails with curl: (28) Connection timed out, and the same request from 198.51.100.20 returns the page. In testing, the three rules stayed in place after sudo ufw reload, a Docker restart and a restart of the server, containers kept their outbound internet access, and sudo ufw disable left the rules loaded.
The bypass and the DOCKER-USER block concern IPv4. On a Docker network without IPv6, IPv6 connections to a published port reach docker-proxy on the host, and in testing UFW's IPv6 rules filtered them like any other host port. The block does not filter IPv6 traffic to Docker networks created with --ipv6.
IPv6 rules (if your server has an IPv6 address)
Check whether the server has a public IPv6 address:
ip -6 addr show scope global
An inet6 address marked scope global that does not start with fd is a public IPv6 address. UFW filters IPv6 by default: /etc/default/ufw contains IPV6=yes, so each rule without an address gets an IPv6 copy, the (v6) lines in sudo ufw status. A rule with an IPv6 address or prefix applies to IPv6 only:
sudo ufw allow from 2001:db8:1234::/48 to any port 22 proto tcp
Rule added (v6)
With IPV6=no, UFW accepts IPv6 traffic on the loopback interface only, which cuts off SSH and web traffic over IPv6. In testing, a reload with IPV6=no also emptied /etc/ufw/user6.rules, and IPV6=yes did not restore the rules. Add them again after you turn IPv6 filtering back on, then run sudo ufw reload and test an IPv6 login from a second terminal. A limit rule added again this way blocked IPv6 SSH until the reload.
How UFW and Fail2ban work together
A UFW limit rule counts new connections from each address, whether the logins succeed or fail. Fail2ban reads failed logins and bans the addresses behind them. With banaction = ufw, each ban is a reject rule at the top of the UFW rule list, ahead of your allow rules. How to Install and Configure Fail2ban on Ubuntu 24.04 and 26.04 sets up that action and covers unbanning your own address.
If you lock yourself out
If the Step 4 timer is still scheduled, wait for it to run ufw disable, then log in over SSH and fix the rule. Schedule the timer again before any change to the SSH rules, such as the Step 7 restriction.
Arct Cloud's VNC Console gives direct browser-based access to the server when SSH is not working or you are locked out. It is available on the server detail page while the server is Running.
To fix the rules, allow SSH again with sudo ufw allow OpenSSH, or turn the firewall off with sudo ufw disable while you find the wrong rule. Both leave UFW off, also after a reboot, so repeat Step 4 to turn it back on and check that sudo ufw status shows Status: active.
Troubleshoot common UFW errors
| Symptom | Cause | Fix |
|---|---|---|
sudo: 'ufw': command not found | The ufw package is not installed | Run sudo apt update and sudo apt install ufw |
ERROR: You need to be root to run this script | ufw ran without sudo | Run the command with sudo |
ssh: connect to host YOUR_SERVER_IP port 22: Connection timed out after ufw enable | No rule allows your SSH port, or a from rule names an old address | Wait for the Step 4 timer or use the console, run sudo ufw allow OpenSSH, then sudo ufw enable |
ssh: connect to host YOUR_SERVER_IP port 22: Connection refused after several quick logins | The limit rule refused the sixth connection within 30 seconds | Wait 30 seconds without retrying. For scripts, put a rule for your address above the limit rule: sudo ufw insert 1 allow from 198.51.100.20 to any port 22 proto tcp |
Failed to start transient timer unit: Unit ufw-undo.timer was already loaded | An earlier undo timer is still scheduled | Run sudo systemctl stop ufw-undo.timer, then schedule it again |
ERROR: Could not find a profile matching 'Apache' | No installed package provides that profile, or the name differs | Run sudo ufw app list and quote the exact name |
Could not delete non-existent rule | The text differs from the rule as it was added | Run sudo ufw status numbered and delete by number |
| A container port answers although UFW has no rule for it | Docker published the port on all addresses | Publish on 127.0.0.1 or add the DOCKER-USER block |
ERROR: problem running ufw-init and iptables-restore: COMMIT expected at line | A table in /etc/ufw/after.rules lost its COMMIT line. The failed reload also sets UFW to stay off at the next boot | Add the missing COMMIT line with sudo nano /etc/ufw/after.rules, then run sudo ufw enable |
Firewall not enabled (skipping reload) after sudo ufw reload | UFW is off, or a failed reload set it to stay off | Run sudo ufw enable |
FAQ
Is UFW enabled by default on Ubuntu 26.04?
No. Ubuntu installs UFW turned off, with default policies that deny incoming and routed traffic and allow outgoing traffic. Allow SSH with sudo ufw allow OpenSSH first, then run sudo ufw enable, and sudo ufw status shows Status: active.
How do I allow SSH in UFW?
Run sudo ufw allow OpenSSH, which opens port 22/tcp through the profile that the openssh-server package installs. sudo ufw allow 22/tcp opens the same port by number. If SSH listens on another port, allow that one instead, such as sudo ufw allow 2222/tcp. Add the rule before you run sudo ufw enable.
Will turning on UFW disconnect my SSH session?
Usually not. UFW accepts packets of connections that are already open, and in testing an open SSH session kept running through sudo ufw enable even with no SSH rule. The next login timed out in that case, so allow SSH first and test from a second terminal.
How do I stop Docker from bypassing UFW?
Publish container ports on 127.0.0.1, such as -p 127.0.0.1:8080:80, and reach them through a reverse proxy or an SSH tunnel. For a port that must stay public, add your rules to the DOCKER-USER chain in /etc/ufw/after.rules. Docker routes published ports before UFW's rules, so UFW rules alone do not apply.
Is UFW or firewalld better?
Neither filters packets better, because both write rules for the kernel's netfilter. UFW is Ubuntu's default firewall tool and uses allow and deny rules per port. firewalld is the default firewall tool on Fedora, RHEL and openSUSE, and it groups rules into zones. On Ubuntu, use UFW.
How do I delete a UFW rule?
Run sudo ufw delete followed by the rule as you added it, such as sudo ufw delete allow 80/tcp, to remove its IPv4 and IPv6 copies. Or list rules with sudo ufw status numbered and delete one by number. sudo ufw reset deletes every rule and turns UFW off.
How do I see what UFW is blocking?
UFW logs blocked packets at the low level by default. Read them with sudo journalctl -k -g "UFW BLOCK", or in /var/log/ufw.log on servers that run rsyslog. Each line shows the source address (SRC), the destination port (DPT) and the protocol (PROTO).
Next steps
- How to Open a Private Dashboard with an SSH Tunnel (Port Forwarding), to reach a service on
127.0.0.1without opening a port - How to Install Docker and Docker Compose on Ubuntu 24.04 and 26.04
- ufw(8) manual for Ubuntu 26.04
- Docker and ufw in Docker's documentation
Arct Cloud Linux images deploy in under 30 seconds on every plan. Compare plans.
This work is licensed under CC BY-NC-SA 4.0.