ufw = Uncomplicated Firewall, a friendly front-end to nftables/iptables (powerful, but unpleasant to write by hand). Covers reading the rules, removing one safely, checking a port is really closed, and not locking yourself out. Why a VPN client needs no inbound port: vpn-how-it-works.

The big picture

INTERNETUFW · THE DOORMANTHIS PCNew connection to :22someone else starts itNew connection to :51820someone else starts itSites you contactgithub.com, VPN serverRules, checked top-down[1] 22/tcp ALLOW IN · matchNo rule matcheddefault: deny incomingDefault: allow outgoingreplies let back in by statesshdlistening on :22Your apps (clients)need no allow ruleinbound✓ allowedinbounddroppedno match① out① out② reply② reply
  • A doorman with two lists and a memory: default policies (anyone not on a list) · rules (named exceptions) · state (conversations you started, so replies get in; see vpn-how-it-works §5).
  • Ubuntu’s sane defaults: deny (incoming) → nobody may start a conversation with you · allow (outgoing) → you may start one with anyone · replies come back automatically, thanks to state.
  • Consequence: you only need allow rules for services other machines must reach (a web server, an SSH daemon). A client of anything needs no rule at all.

1. Check the current state

sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
 
To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
51820/udp                  ALLOW IN    Anywhere
  • verbose over plain status → it shows the default policies, without which the rules can’t be interpreted.
sudo ufw status numbered     # with numbers, for editing
     To                         Action      From
     --                         ------      ----
[ 1] 22/tcp                     ALLOW IN    Anywhere
[ 2] 51820/udp                  ALLOW IN    Anywhere

2. Remove a rule

sudo ufw delete allow 51820/udp   # A. by specification (preferred)
 
sudo ufw status numbered          # B. by number: look again, every time
sudo ufw delete 2                 #    confirms before acting

The renumbering trap

Rule numbers shift after every delete: delete [1] and the old [2] becomes the new [1]. Delete [1] twice and you’ve removed two different rules, the second probably not the one you meant.

  • A. By spec → immune to renumbering, and documents what you removed.
  • B. By number → re-run status numbered before each delete; removing several, work from the highest number downward so the ones you haven’t reached don’t move.
  • ufw delete asks Proceed with operation (y|n)? → read what it’s about to remove before answering.

3. delete vs deny: not the same thing

sudo ufw delete allow 51820/udp   # removes the exception: port falls back to default (deny)
sudo ufw deny 51820/udp           # adds an explicit deny rule
  • With the default already deny incoming, deleting the allow rule is sufficient: the port is closed either way.
  • An explicit deny records the intent, so a future you doesn’t re-add the allow by accident. Belt-and-braces, not a correction.

4. Verify a port is really closed

Two different questions, both worth asking:

sudo ufw status | grep 51820      # 1. firewall: no output = no rule = default deny applies
sudo ss -lunp | grep 51820        # 2. listening: no output = nothing is listening
  • ss -lunp, not ss-lunp: ss is the command, the rest are flags.
  • -l listening only · -u UDP · -t TCP (swap for -u, or use both) · -n numeric (51820, not a service name) · -p owning process (needs sudo).
  • sudo ss -lunp → all UDP · sudo ss -ltnp → all TCP · sudo ss -ltunp → both.
  • They’re independent. Allowed with nothing listening → harmless but untidy. Listening but blocked → fine, still reachable from localhost. Belt and braces means closing both.

5. Don’t lock yourself out

sudo ufw allow 22/tcp     # FIRST: or your custom SSH port
sudo ufw enable

ufw enable on a remote machine without allowing SSH

Default deny incoming includes your own SSH session: you’re locked out, and fixing it needs physical or console access.

  • ufw enable warns “Command may disrupt existing ssh connections” → the warning is real.
  • On this local desktop the risk is nil, but the habit is worth having before it matters.

6. Worked example: close the unused VPN port

This PC is a VPN client, not a server, so an inbound 51820/udp ALLOW rule serves no purpose (why: vpn-how-it-works §4).

sudo ufw status verbose                                                    # 1. what's there + defaults
sudo ufw delete allow 51820/udp                                            # 2. remove BY SPEC, no renumbering risk
sudo ufw status | grep 51820 || echo "no rule for 51820 - default deny applies"   # 3. rule gone?
sudo ss -lunp | grep 51820 || echo "nothing listening on 51820"           # 4. nothing listening?
sudo ufw deny 51820/udp                                                    # 5. optional: record the intent
  • Nothing breaks: outbound connections and their replies are unaffected; only a stranger’s ability to knock on that port is removed.
  • If you ever allow 22/tcp, ssh-keys-and-github is what’s behind it.

Quick reference

sudo ufw status verbose                     # rules + default policies  <- start here
sudo ufw status numbered                    # rules with numbers, for editing
sudo ufw show added                         # rules as the commands that created them
sudo ufw allow 22/tcp                       # allow a port
sudo ufw allow ssh                          # by service name (/etc/services)
sudo ufw allow 51820/udp
sudo ufw allow from 192.168.1.0/24          # a whole subnet, any port
sudo ufw allow from 192.168.1.50 to any port 22 proto tcp   # specific
sudo ufw delete allow 51820/udp             # remove by spec  <- safer
sudo ufw delete 2                           # remove by number (re-check numbers first!)
sudo ufw deny 51820/udp                     # explicit deny rule
sudo ufw enable                             # ! allow SSH FIRST on a remote box
sudo ufw disable
sudo ufw reload
sudo ufw reset                              # ! DESTRUCTIVE: wipes every rule
sudo ufw logging on
sudo ufw logging medium                     # off | low | medium | high | full
sudo tail -f /var/log/ufw.log               # watch the log