Recover from a broken ufw ruleset
Locked out by ufw? Get back in through the provider console, disable the firewall, read the rules that actually applied, and stop it happening again.
Get back in first
If ufw has locked you out of your VPS, the way back in is the provider console or rescue mode, because no SSH based fix exists once the blocking rule is live. The kernel drops your packet before sshd ever sees it, so there is nothing to log in to and nothing to repair over the network. Open the console in your provider's control panel, log in at that prompt, and run one command.
sudo ufw disableYou should see Firewall stopped and disabled on system startup. New SSH connections work again within a second or two. Nothing you configured is lost: disable unloads the rules from the kernel and writes ENABLED=no into /etc/ufw/ufw.conf, while your rules stay on disk in /etc/ufw/user.rules, waiting for the next ufw enable.
Do not reboot and hope. ufw starts itself at boot, so ENABLED=yes means the same ruleset loads again before the network is up. A reboot changes nothing about a ufw lockout.
The console needs a password you may not have
The web console (VNC or serial) is a keyboard attached to the machine. It is not a network path, so no firewall rule can block it. It does need a local login, and this is where key only setups fail: if you never set a password for your sudo user, and root login is locked, the console shows you a prompt you cannot answer. Set that password now, while you still have SSH: sudo passwd yourname. Most panels can also reset the root password, which normally forces a reboot.
If the console is unusable, boot the provider's rescue system. It runs a separate operating system with your disk unmounted, so you can turn ufw off from outside.
lsblk
sudo mount /dev/vda1 /mnt
sudo sed -i 's/^ENABLED=yes/ENABLED=no/' /mnt/etc/ufw/ufw.conf
sudo umount /mntRun lsblk first, because the root partition is not always /dev/vda1. Reboot into the normal system and ufw stays off until you enable it by hand.
The minimal recovery sequence
Work in this order. The first four steps are safe. The one after them is not.
sudo ufw disableto unload the rules and get your access back.sudo ufw show addedto print the rules you added, in the form of the commands that added them. This works while ufw is inactive, whichufw statusdoes not.sudo sshd -T | grep -i '^port'to confirm the port sshd really listens on. It printsport 22unless you changed it.sudo ufw allow 22/tcp, using your real port, so the next enable cannot repeat the lockout.sudo ufw enable, with a rollback scheduled first. That is further down this page.
What ufw reset really does
ufw reset is the last resort, not the first move. It disables the firewall, backs up every rules file, and returns the defaults to deny incoming and allow outgoing. It prints one backup line per file:
Backing up 'user.rules' to '/etc/ufw/user.rules.20260813_101500'After a reset you have no allow rules at all, so run it from the console rather than over SSH, and add the SSH rule before you enable again. Those backups are plain text. sudo grep -n dport /etc/ufw/user.rules.20260813_101500 shows what the old rules were, which is how you rebuild a ruleset you did not mean to throw away.
Where ufw keeps its rules
Reading the files beats guessing from memory. Five paths hold the whole state:
/etc/ufw/user.rulesand/etc/ufw/user6.rules: the rules you added, in the order they are evaluated./etc/ufw/before.rulesand/etc/ufw/after.rules, plus the6variants: the framework ufw wraps around your rules, including the accept for established connections and the loopback rules./etc/default/ufw: the default policies and theIPV6switch./etc/ufw/ufw.conf:ENABLEDand the log level./var/log/ufw.log: what was blocked, once logging is on.
ufw writes a timestamped copy of a file before it rewrites it, so ls /etc/ufw/ fills up with names like user.rules.20260813_101500. That is your undo history, and it is worth reading before you start changing things back.
To see what is loaded in the kernel rather than what is on disk, use sudo ufw show raw, or sudo iptables -S and sudo ip6tables -S. On Ubuntu 22.04 and 24.04 those commands are the nft backed versions, so sudo nft list ruleset prints the same rules in the newer syntax.
Why did enabling ufw drop my SSH session?
The default incoming policy is deny. Enabling ufw with no rule for your SSH port cuts off every new connection. ufw does warn you: Command may disrupt existing ssh connections. Proceed with operation (y|n)? Answering y with no SSH allow rule in place is the most common cause of everything on this page.
The confusing part is the delay. /etc/ufw/before.rules accepts packets in state ESTABLISHED,RELATED before your own rules are reached, so the session you typed the command in keeps working normally. The lockout only appears on the next connection, which may be hours later, and by then the firewall change no longer feels related. Always open a second SSH session and confirm it works before you close the first one.
Why did apt and DNS stop working after a policy change?
sudo ufw default deny outgoing blocks outbound DNS (domain name system) queries and outbound HTTP, so name resolution dies and package updates stop. apt update reports Temporary failure resolving 'archive.ubuntu.com'. Inbound SSH still works, because the replies to it are ESTABLISHED and pass the framework rules, which makes the firewall look innocent while it is the cause.
If you want a deny outgoing policy, open what the machine actually needs:
sudo ufw allow out 53
sudo ufw allow out 80/tcp
sudo ufw allow out 443/tcp
sudo ufw allow out 123/udpWithout the last rule the clock drifts, and a wrong clock breaks TLS (transport layer security) certificate validation, so curl starts failing on dates rather than on ports. That symptom appears days after the change, which is why deny outgoing is a policy for machines you monitor, not for a box you set up once.
Why does my ufw rule never match?
ufw evaluates user rules in order and stops at the first match. A deny added after a broad allow never fires, because the allow already decided the packet. Print the order with numbers, then insert at the position you need.
sudo ufw status numbered
sudo ufw insert 1 deny from 203.0.113.10 to any port 22
sudo ufw delete 4sudo ufw --dry-run allow 8080/tcp prints the rules that would be written and changes nothing. That is the safe way to read a rule before it goes live.
One more trap sits in the application profiles. sudo ufw allow OpenSSH uses the profile in /etc/ufw/applications.d/openssh-server, and that profile means port 22. If sshd listens on 2222, the rule opens a port nothing is using and leaves you locked out with a ruleset that looks correct. Use the port number once you have moved the port. The rest of the syntax is covered in the ufw firewall basics for a VPS.
Why do the IPv4 rules not explain what I see?
Because half the traffic is not IPv4. Ubuntu ships IPV6=yes in /etc/default/ufw, and ufw then keeps a parallel v6 ruleset in /etc/ufw/user6.rules. A rule written with an IPv4 address, such as ufw allow from 203.0.113.10 to any port 22, creates no v6 rule at all. If your VPS has an AAAA record, your client prefers IPv6, and your connection times out while ufw status shows a rule that looks right. Test the difference with ssh -4 user@host against ssh -6 user@host. If the first works and the second does not, the gap is the v6 ruleset.
The reverse case is worse for security. With IPV6=no, ufw does not manage ip6tables at all, so the v6 policy stays at the kernel default of ACCEPT. A port you believe is closed answers on its IPv6 address, and no ufw command will ever mention it. Check with sudo ip6tables -S and ss -tlnp, and read how ufw handles IPv6 ports for the full picture.
Why is a Docker port open when ufw denies it?
Docker publishes a port by writing DNAT (destination network address translation) rules into the nat table and inserting its own chain into FORWARD. ufw's rules sit in the INPUT path. Traffic to a container is forwarded rather than delivered to the host, so it never reaches the chain your deny rule is in. docker run -p 5432:5432 is reachable from the internet with ufw active and denying everything.
sudo iptables -t nat -S DOCKERThe simplest fix is to publish on loopback: -p 127.0.0.1:5432:5432 binds the host side to 127.0.0.1, and nothing external can reach it whatever ufw says. Docker publishing ports around ufw covers the cases where the service does need to be public.
Schedule the rollback before you apply the rule
This is the habit that makes firewall work survivable. Before any risky change, schedule the undo. If the change locks you out, the machine heals itself in five minutes and you never open the console.
sudo systemd-run --on-active=5m --unit=ufw-rollback /usr/sbin/ufw disablesystemd prints Running timer as unit: ufw-rollback.timer. Now make your change. If you can still open a new SSH session afterwards, cancel the rollback:
sudo systemctl stop ufw-rollback.timerIf you cannot open that session, wait. ufw goes down on its own and your next attempt connects. The classic shutdown -r +5 trick does not help with ufw, because ufw loads the same ruleset again at boot.
Keep a second way in
- Log in to the provider console once, before you need it, and confirm the password works. A console you have never tested is not a backup.
- Keep a second sudo user with its own key, so one broken
authorized_keysfile is not the end of your access. - Check whether your provider runs a network firewall in the panel, separate from ufw. It blocks the same ports, and
ufw statuswill never mention it. - Do not make
ufw allow from <your home address>your only SSH rule if that address is dynamic. Your provider changes it overnight and you are out.
The cheapest time to do all of this is on a fresh server, alongside the other setup work in the first ten minutes on a new VPS.
Refused or timed out tells you which layer failed
Connection refused means a packet reached the server and something sent a TCP reset back. The network path is fine, so sshd is stopped or listening on a different port. A firewall is rarely the cause, because ufw drops by default rather than rejecting.
Connection timed out means nothing came back at all. That is the signature of a drop: ufw, a provider network firewall, or a wrong address. Reading these two errors correctly saves an hour of guessing, and the difference between connection refused and timed out works through the remaining cases.
Turn logging on before the next change
sudo ufw logging on
sudo tail -f /var/log/ufw.logA blocked packet appears like this:
[UFW BLOCK] IN=eth0 OUT= SRC=203.0.113.10 DST=192.0.2.5 LEN=60 PROTO=TCP SPT=51234 DPT=22 WINDOW=64240 SYNDPT=22 with your own address in SRC= is proof that ufw is the thing blocking you, not the network and not sshd. On a minimal image with no rsyslog there is no /var/log/ufw.log, and the same lines come from sudo journalctl -k | grep UFW. ufw rate limits its own logging rules, so a missing line is not proof that a packet was allowed.
If you find rules you never added
A ruleset that changed on its own is not a firewall problem. Someone with root wrote it. Run sudo grep ufw /var/log/auth.log to see which sudo commands ran and under which account, then last for the logins around that timestamp. If the accounts do not match anyone you know, stop debugging the firewall and work through a compromised VPS checklist instead. Re-enabling a firewall on a box someone else controls only hides the problem.
Put it back together
Once you know the cause, enable ufw again in a way that cannot repeat the lockout. Allow your real SSH port, schedule the rollback, enable, then open a brand new SSH session from another terminal and confirm it connects. Only after that new session is up should you close the one you are working in. Leave logging on for a day, because the log tells you what you forgot to allow far faster than reading user.rules does.
FAQ
Does ufw disable delete my rules?
No. disable unloads the ruleset from the kernel and writes ENABLED=no into /etc/ufw/ufw.conf. Your rules stay in /etc/ufw/user.rules and /etc/ufw/user6.rules, and sudo ufw show added lists them while the firewall is inactive. ufw reset is the command that clears them, and it backs each file up first, printing a line like Backing up 'user.rules' to '/etc/ufw/user.rules.20260813_101500'.
Will rebooting my VPS undo a ufw lockout?
No. ufw starts at boot from ENABLED=yes in /etc/ufw/ufw.conf, so the same rules load before the network comes up and you are locked out again. A reboot only helps after you have turned ufw off, or after you edited that file from rescue mode with the disk mounted. Use the provider console and run sudo ufw disable there.
Why is my Docker container reachable when ufw denies the port?
Docker writes its own DNAT and FORWARD rules for every published port. That traffic is forwarded to the container rather than delivered to the host, so it never passes through the INPUT chain where your ufw deny rule lives. Publish on loopback with -p 127.0.0.1:5432:5432 when the port is only for the host, and inspect what Docker installed with sudo iptables -t nat -S DOCKER.
I have no console password and no rescue mode. What are my options?
The remaining options belong to your provider: a password reset from the control panel, which usually reboots the server, or attaching the disk to another instance so you can edit /etc/ufw/ufw.conf from there. Ask support before you rebuild the server, because a rebuild destroys the data on it. Once you are back in, run sudo passwd yourname and test the console login once, so the next lockout costs you two minutes.