UFW, short for Uncomplicated Firewall, is Ubuntu's standard command-line frontend for building a host-based firewall. A safe Ubuntu 24.04 baseline is straightforward: identify the SSH port you are actually using, allow that path before enabling UFW, keep the default inbound policy restrictive, open only the services the server needs, and verify the result from a second connection before closing your original SSH session.
Raff Technologies is the VM platform used by the original tutorial. The saved tested-on record remains Ubuntu 24.04 LTS on Raff 2 vCPU / 2 GB RAM Linux VM; UFW, OpenSSH, IPv6, logging, and Docker guidance reviewed in July 2026. This revision re-verifies the firewall workflow against current Ubuntu, UFW, and Docker documentation on September 4, 2026 without claiming a new server-side test.
The most important caveat is Docker: a normal UFW policy does not reliably protect Docker-published container ports because Docker creates its own NAT/firewall rules and can divert that traffic before UFW's normal host INPUT rules. For private container services, bind published ports to loopback or use Docker-aware/upstream filtering instead of assuming UFW will block them.
Prerequisites:
- An Ubuntu 24.04 VM with SSH access
- A non-root user with sudo privileges
- Knowledge of every service that must remain reachable
- A second terminal or recovery path for SSH verification
Step 1 — Inspect the current SSH, network, and firewall state
Do not enable a firewall before you understand how you are currently connected.
Check Ubuntu:
Check UFW state:
List listening TCP and UDP sockets:
Ask OpenSSH for its effective configured port or ports:
sudo sshd -T | awk '$1 == "port" {print $2}'
If you use a provider-side firewall, security group, VPN, Tailscale, or a nonstandard SSH port, document that path before changing UFW.
Keep the current SSH session open throughout the initial enablement. A second connection will be your proof that the firewall did not lock you out.
Verify: You should know the active SSH port, which applications are listening, whether UFW is already active, and which services must remain reachable.
Step 2 — Install UFW and understand its installation defaults
Install UFW if it is not already present:
sudo apt update
sudo apt install -y ufw
Check its version:
Ubuntu's current UFW documentation states that UFW is initially disabled. Its installation defaults are already designed around a common server baseline: deny incoming traffic, deny forwarded/routed traffic, and allow outgoing traffic. This tutorial still sets the intended host defaults explicitly later so the final policy is easy to audit.
UFW is a frontend for Linux netfilter. It is useful for host firewall rules, but it is not intended to replace every advanced packet-filtering or routing feature.
Verify: ufw --version should return an installed version and sudo ufw status should return a valid status rather than command not found.
Step 3 — Keep IPv6 firewalling aligned with the server's network configuration
Check UFW's IPv6 setting:
grep '^IPV6=' /etc/default/ufw
Ubuntu 24.04's UFW framework enables IPv6 support by default. A typical result is:
When IPv6 support is enabled, generic rules such as:
can create rules that apply to both IPv4 and IPv6.
If your server has working IPv6 or an AAAA DNS record, keep IPv6 firewalling enabled and test both address families. Do not treat IPV6=no as a harmless way to ignore IPv6: current UFW framework documentation states that disabling IPv6 support changes how non-loopback IPv6 traffic is handled.
Inspect the host addresses:
If the VM intentionally has no IPv6 service, document that choice at both the host and upstream network layers instead of leaving stale public IPv6 DNS/routes.
Verify: You should know whether the VM is single-stack or dual-stack, and /etc/default/ufw should match that intended design.
Step 4 — Allow the active SSH path before enabling UFW
If SSH uses the normal OpenSSH profile on TCP 22, inspect the profile first:
sudo ufw app info OpenSSH
Then add the rule:
sudo ufw allow OpenSSH comment 'SSH administration'
If SSH uses a custom port, allow that exact port instead. Example:
sudo ufw allow 2222/tcp comment 'SSH administration'
Do not add both rules unless sshd intentionally listens on both ports.
You can preview the effect of a rule without applying it by using UFW's --dry-run option. For example:
sudo ufw --dry-run allow 2222/tcp
Review rules that have been added from the command line:
This step is the critical lockout safeguard. UFW's own remote-management documentation recommends allowing the SSH port before ufw enable because enabling UFW rebuilds its chains and can interrupt existing remote connections.
Verify: sudo ufw show added should contain an allow rule for the exact SSH path you are using before you enable the firewall.
Step 5 — Set and review the default policies
Apply a common application-server baseline:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny routed
The routed policy matters when the host forwards traffic between interfaces or networks. A normal standalone application VM generally does not need to be an IP router.
Review the pending rules again:
Preview enablement without changing the live firewall:
sudo ufw --dry-run enable
Default policy changes can affect existing rules, so make them deliberately rather than toggling defaults repeatedly on a production server.
Verify: Your intended defaults should be deny incoming, allow outgoing, deny routed, and the SSH allow rule must still be present.
Step 6 — Enable UFW and test a second SSH connection
Enable the firewall:
UFW may warn that enabling it can disrupt existing SSH connections. Continue only because the correct SSH rule was added first.
Check the live policy:
Then, without closing the original session, open a second terminal from your workstation and connect again:
ssh your_user@your_server_ip
If SSH uses a custom port:
ssh -p 2222 your_user@your_server_ip
Do not consider the firewall rollout successful until this new session works.
Verify: UFW should report Status: active, the correct defaults, and the new independent SSH session should connect successfully.
Step 7 — Allow only the ports and application profiles the server needs
To allow a public TCP port explicitly:
sudo ufw allow 443/tcp comment 'HTTPS'
For a normal public web server using both HTTP and HTTPS:
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
This is the core ufw allow port pattern: specify the port and protocol you actually need rather than opening a broad range.
Applications can also install UFW profiles. List them:
For Nginx, inspect the profile before allowing it:
sudo ufw app info 'Nginx Full'
Then, if it matches your intended ports:
sudo ufw allow 'Nginx Full'
Do not keep duplicate numeric rules and application-profile rules without a reason. Application profiles live under /etc/ufw/applications.d, and UFW's current documentation supports ufw app info, app list, and app update for inspecting and maintaining them.
For a complete web-server path, see Install Nginx on Ubuntu 24.04.
Verify: sudo ufw status numbered should show only the public services you deliberately intend to expose.
Step 8 — Restrict databases and admin services by source address
Do not expose a database or administrative port to Anywhere merely because an application needs remote access.
Allow PostgreSQL only from a private subnet:
sudo ufw allow proto tcp from 10.0.0.0/24 to any port 5432 \
comment 'Private PostgreSQL'
Allow an admin service only from one trusted public address:
sudo ufw allow proto tcp from 203.0.113.5 to any port 9090 \
comment 'Admin access'
203.0.113.5 is a documentation example. Replace it with the real trusted source.
For multi-VM workloads, combine a Raff VPC with correct service binding. For example, a database can bind to its private address rather than 0.0.0.0. UFW source filtering is an additional control, not a substitute for application authentication and correct bind addresses.
Verify: Private/admin ports should be allowed only from the expected source IP or subnet, and the application itself should listen only on the intended interface.
Step 9 — Use rule order deliberately because the first matching rule wins
UFW rule order matters. Current UFW documentation states that the first matching rule wins, so a broad allow placed before a more specific deny can defeat the intended restriction.
Display rules in order:
To insert a rule at a specific position:
sudo ufw insert 1 deny from 203.0.113.25
To place an address-family-specific rule before existing rules of the same IP type:
sudo ufw prepend deny from 203.0.113.25
Before inserting a production rule, preview it:
sudo ufw --dry-run insert 1 deny from 203.0.113.25
Use rule ordering to express actual policy, not as a substitute for simplifying an overly complicated ruleset.
Verify: sudo ufw status numbered should place narrow exceptions/restrictions where they will be evaluated before broader rules that could otherwise match first.
Step 10 — Rate-limit SSH only after the basic SSH policy is correct
UFW supports a limit action for connection-rate limiting, commonly used for SSH:
sudo ufw limit OpenSSH comment 'Rate-limited SSH'
For a custom port:
sudo ufw limit 2222/tcp comment 'Rate-limited SSH'
Do not add a limit rule alongside a broad earlier allow rule for the same traffic and assume the later rule will win. Check the actual ordering.
UFW's rate limiting is a basic brute-force mitigation, not a substitute for SSH keys, disabling unnecessary password authentication, account controls, or upstream DDoS protection. It can also affect legitimate automation or multiple users sharing one NAT address.
For SSH-key setup, use Configure SSH Keys on Ubuntu.
Verify: sudo ufw status numbered should show the intended LIMIT rule in an order where a broader prior ALLOW does not bypass it.
Step 11 — Delete rules safely and understand IPv4/IPv6 deletion behavior
Show numbered rules:
Delete a single numbered rule:
Run status numbered again after every numbered deletion because rule numbers change.
When IPv6 is enabled, a generic rule can produce both IPv4 and IPv6 entries. Deleting one numeric entry removes only that listed rule. If you want to remove the generic rule across the address families UFW created, delete it by repeating its original syntax:
sudo ufw delete allow 443/tcp
Reload UFW after changes that require it:
Avoid ufw reset for routine cleanup. Reset disables the firewall and returns it to installation defaults, which means you must rebuild SSH and service rules.
Verify: The unwanted rule should be gone from both the numbered status and, when relevant, its IPv4/IPv6 counterpart.
Step 12 — Enable useful logging and inspect more than ufw status
Enable low-level logging:
UFW supports off, low, medium, high, and full. Current documentation warns that higher levels can generate substantial log volume, so low is a sensible default for a public server.
Inspect recent kernel firewall events:
sudo journalctl -k -g 'UFW' --since '15 minutes ago'
On systems where rsyslog writes a dedicated UFW file:
sudo test -f /var/log/ufw.log && \
sudo tail -n 50 /var/log/ufw.log
Use UFW's reports for deeper inspection:
sudo ufw show listening
sudo ufw show raw
ufw status shows rules managed through the UFW CLI, but current UFW documentation notes that it does not show every rule loaded from /etc/ufw/*.rules. ufw show raw is the stronger view when you need the complete firewall state.
Verify: Logging should be enabled at the intended level, show listening should correlate services with firewall rules, and show raw should return the complete live netfilter view UFW exposes.
Step 13 — Do not assume UFW protects Docker-published ports
This is the most common advanced UFW mistake on application servers.
List Docker-published ports:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
A publication such as:
can be reachable even when UFW appears to deny TCP 8080. Docker's current documentation explains that Docker creates NAT/firewall rules for bridge networks and that published traffic can be diverted before it reaches the INPUT and OUTPUT chains UFW normally manages.
For a service intended only for a host-side reverse proxy, publish it to loopback instead:
or in Compose:
ports:
- "127.0.0.1:8080:80"
For advanced source filtering of Docker bridge traffic, use filtering appropriate to the Docker firewall backend. With Docker's iptables backend, DOCKER-USER is the documented pre-Docker user chain. Docker 29 also has an experimental nftables backend, where there is no DOCKER-USER chain and custom nftables chains/tables are used instead.
Do not set Docker's iptables/ip6tables options to false casually. Docker warns that disabling its firewall-rule management without a complete replacement policy is likely to break container networking.
See Install Docker on Ubuntu 24.04 for the container baseline.
Verify: Private containers should not publish to 0.0.0.0 unnecessarily, and any public Docker ports must be treated as a separate firewall exposure rather than assumed protected by UFW.
Step 14 — Run the final firewall audit and keep a safe rollback path
Audit the live system:
sudo ufw status verbose
sudo ufw status numbered
sudo ufw show listening
sudo ss -tulpn
sudo systemctl is-enabled ufw
The firewall is healthy when:
- UFW is active;
- incoming traffic is denied by default;
- outgoing traffic is allowed unless you intentionally chose otherwise;
- routed traffic is denied unless the VM is meant to forward packets;
- SSH works from a fresh second connection;
- public services have explicit rules;
- private/admin services use source restrictions and/or private binding;
- Docker-published ports have been audited separately;
- IPv4 and IPv6 behavior match the server's actual network design.
Test important public ports from another machine. A localhost curl does not prove that the same traffic is reachable from the internet.
To temporarily disable UFW while preserving the configured rules:
To re-enable it after correcting a rule:
Use a full reset only when you intentionally want to rebuild the entire policy:
If you are locked out of SSH, use an existing open session or provider recovery console to correct the rule. Do not remove the last known-good remote-management path before a replacement has been tested.
Verify: A fresh SSH session and every required application path should work, unnecessary ports should fail from an external client, and the rules should remain enabled across a reboot.
Troubleshooting
SSH stops accepting new connections after UFW is enabled
Keep the original session open and check:
sudo sshd -T | awk '$1 == "port" {print $2}'
sudo ufw status numbered
Add the correct port if it is missing, then test a new session before closing the original one.
ufw status says a Docker port is blocked but the container is reachable
That can be expected with Docker port publishing. Inspect:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
sudo ufw show raw
Bind private container ports to 127.0.0.1, use an upstream firewall, or implement Docker-backend-aware filtering.
A deny rule does not work
Check rule ordering:
A broader allow may match first. Insert or prepend the specific restriction before the broad rule, then retest externally.
IPv4 works but IPv6 behaves differently
Check:
grep '^IPV6=' /etc/default/ufw
ip -6 address
sudo ufw status numbered
Also verify public AAAA DNS and upstream IPv6 routing/firewall policy.
Logging is filling the disk
Reduce the level:
or temporarily disable UFW-managed logging:
medium, high, and full can be noisy on busy hosts.
A custom rule is active but missing from ufw status
Rules placed directly in /etc/ufw/before.rules, after.rules, or their IPv6 counterparts are not fully represented by normal ufw status. Inspect:
Conclusion
You now have a practical Ubuntu 24.04 UFW baseline that protects the active SSH path before enablement, denies unsolicited inbound traffic, handles IPv4 and IPv6 deliberately, opens only required ports, supports source-restricted access, respects first-match rule ordering, provides useful logging, and includes an external verification and rollback workflow.
The key operational habit is to compare three views whenever the server changes: ufw status numbered, ufw show listening, and ss -tulpn. Firewall rules should evolve with the applications that are actually listening. Docker-published ports require a separate audit because Docker's own networking rules can bypass the assumptions behind ordinary UFW host filtering.
For the next layer, continue with Configure SSH Keys on Ubuntu, Secure Ubuntu 24.04 Server, and Install Docker on Ubuntu 24.04.
Sources