VPSSPark Blog
← Back to Dev Diary

How to Configure a Lightweight Firewall on Your Cloud Server (UFW Guide)

Server Notes · 2026.07.20 · ~12 min read

Common searches: UFW firewall · cloud server firewall · Ubuntu VPS security · ufw allow SSH

Server racks and network cabling—cloud server firewall and network perimeter scene
The first job on a new cloud server is not installing apps—it is drawing the network boundary. UFW is the easiest place to start.

A freshly installed Linux VPS hits the public internet the moment it boots. We once watched a brand-new Ubuntu 22.04 box: within four hours of going live, /var/log/auth.log had logged more than a thousand SSH brute-force attempts from all over the world. Cloud security groups block some of that noise, but the host firewall is the second gate you actually control.

On Ubuntu and Debian servers, UFW (Uncomplicated Firewall) is the default answer: simple syntax, seamless iptables integration, and solid documentation. This guide skips the dozens of chained iptables rules and instead lays out the UFW workflow we have validated in production—a checklist from install and SSH access through web ports, Docker coexistence, cloud security group alignment, and the part that matters most: how to configure a firewall without locking yourself out.

Prerequisites: you can log in via SSH or the cloud console VNC, and you have sudo. If SSH is already failing, start with our Linux server SSH connection refused troubleshooting guide to tell network issues from firewall issues, then come back here.

2 layers
Cloud SG + UFW
5 steps
Standard setup flow
~2 min
Basic rules live

Why every cloud server needs a firewall

Many operators assume that buying cloud infrastructure with a security group means the OS layer is optional. In practice the two layers do different jobs: the cloud security group filters traffic outside the virtual NIC and is managed in the console; UFW filters at the OS netfilter layer and is managed from the command line. Running only one layer is like locking the front door but leaving bedroom doors open—lateral movement on the network, accidentally exposed services, and post-compromise reverse shells all need host-level rules as a backstop.

Under the hood, UFW is still the iptables front end described in the official Ubuntu UFW documentation: when you run ufw allow 22, it translates into the matching ACCEPT rules. The syntax reads almost like plain English, ufw status numbered gives you a clear view of current policy, and editing rules is far less error-prone than hand-writing iptables chains.

Cloud VPS UFW firewall setup in five steps: install, allow SSH, set default policy, open service ports, enable and verify
Order matters: allow the management path first, set default deny, then enable—otherwise you will lock yourself out.
Layer Tool Management Typical use
Cloud provider (outer) Security group / Network ACL Web console Coarse inbound: 22/80/443 only
Operating system (inner) UFW / firewalld SSH CLI Fine-grained: by IP, port, rate limit
Application fail2ban / CrowdSec Config files Dynamic blocks for brute-force sources

Step 1: Install and inspect UFW

Ubuntu desktop and most server images ship with UFW preinstalled, but minimal images may not. Confirm first:

Install UFW (Debian/Ubuntu)
sudo apt update
                sudo apt install ufw -y

                # Check current status (inactive means not yet enabled)
                sudo ufw status verbose

                # Enable on boot (configure rules before enable)
                sudo systemctl enable ufw

If you see Status: inactive, you can add rules safely one by one without them taking effect yet—that is the best configuration window. Do not run ufw enable before SSH is allowed, or you will lose remote access immediately.

Golden rule: do not lock yourself out
Before enabling UFW, open a second SSH session (or keep the cloud console VNC window open). After applying rules, test the new session, then close the old one. We have seen far too many tickets from a single command that cut off all access.

Step 2: Allow SSH (the most important rule)

SSH is your management channel and must be allowed first. If you moved sshd to a non-standard port (for example 2222), the rule port must match sshd_config, and the cloud security group must be updated to match.

Allow SSH
# Default port 22
                sudo ufw allow 22/tcp comment 'SSH'

                # Custom port
                sudo ufw allow 2222/tcp comment 'SSH custom'

                # Office egress IP only (recommended for production)
                sudo ufw allow from 203.0.113.50 to any port 22 proto tcp

                # By service name (when defined in /etc/services)
                sudo ufw allow OpenSSH

Verify sshd is actually listening: sudo ss -tlnp | grep ssh. If you see 0.0.0.0:22 or [::]:22, the service is fine. If ListenAddress is set to 127.0.0.1, UFW cannot help—that is an sshd configuration issue, not a firewall issue.

For how SSH ports and security groups fit together, see our Linux cloud host minimal exposure firewall decision FAQ, which includes an SSH/HTTPS trade-off matrix for long-term planning.

Step 3: Set default policy

The recommended UFW baseline is deny all incoming, allow all outgoing. Outbound stays open so the server can pull apt packages, call APIs, and resolve DNS; inbound is tightened so only ports you explicitly need are reachable.

Default policy
sudo ufw default deny incoming
                sudo ufw default allow outgoing

                # List rules with numbers (for easy deletion)
                sudo ufw status numbered

Some environments tighten outbound too (compliance requirements): you can run ufw default deny outgoing and then allow out DNS (53), HTTPS (443), and so on. For most web and API servers, allow-all outbound plus selective inbound is the best cost-to-benefit ratio.

Step 4: Open service ports

Add rules for what you actually run. Common patterns:

Web and common services
# HTTP / HTTPS (Nginx, Caddy, Apache)
                sudo ufw allow 80/tcp
                sudo ufw allow 443/tcp

                # Or use the bundled service profile
                sudo ufw allow 'Nginx Full'

                # Temporarily open Node / other ports in dev
                sudo ufw allow 3000/tcp comment 'dev API'

                # Restrict admin panel to a source subnet
                sudo ufw allow from 198.51.100.0/24 to any port 8080 proto tcp

Production recommendation: if traffic can go through 443 behind a reverse proxy, do not expose 3000/8080 directly. Open only 22 + 80 + 443 in UFW, bind the app to 127.0.0.1, and terminate TLS with Nginx or Caddy—the attack surface shrinks immediately.

Step 5: Enable and verify

Once rules are in place, enable UFW:

Enable UFW
sudo ufw enable
                # Prompt warns SSH may be interrupted; type y to confirm

                sudo ufw status verbose
                sudo ufw status numbered

From another machine, test: nc -zv your.server.ip 22 should report open; ports you did not allow (for example 3306) should time out or be refused. If SSH drops, use the cloud console VNC immediately and run sudo ufw disable to roll back, find the missing rule, and start over.

Advanced: rate limits, deletion, and rule order

UFW supports more than plain allow. High-frequency patterns:

Advanced commands
# Rate limit: mitigate SSH brute force (6 attempts / 30s)
                sudo ufw limit 22/tcp

                # Delete rule by number (check status numbered first)
                sudo ufw delete 3

                # Block a specific IP
                sudo ufw deny from 192.0.2.100

                # Reset all rules (use with care)
                sudo ufw reset

ufw limit uses iptables recent under the hood to cap connection rates—effective for SSH, but not a substitute for key-based login and disabling password authentication. Rules match in the order they were added; more specific rules should come first. If an allow rule seems ignored, check whether a later deny overrides it.

Coexisting with Docker / Kubernetes

This is where UFW most often looks configured but does nothing. Docker by default inserts its own iptables chains and can bypass UFW rules, so you think 3306 is closed while the container port is still visible on the public internet.

Mitigations, in order of preference:

  • Bind containers to 127.0.0.1 only-p 127.0.0.1:3000:3000, with the host reverse proxy facing the world
  • Omit ports in docker-compose and use internal networks plus a reverse proxy
  • Set Docker "iptables": false (you maintain forwarding rules yourself—advanced users)
  • Use the cloud security group as final backstop—even if Docker bypasses UFW, the outer layer still filters

Kubernetes nodes add another layer: Calico/Cilium NetworkPolicy usually owns pod traffic, with UFW as a node-level supplement. For a single VPS running Docker Compose, binding to loopback is usually enough.

How cloud security groups and UFW work together

Configure both layers, and keep policy aligned: if the security group allows 22, UFW must allow 22 too; if the security group blocks 3306, UFW allow alone will not let the public internet in—but a compromised peer on the network might still reach it, so UFW should deny by default.

Suggested division of labor:

  • Security group: coarse grain—22/80/443 only; tighten source to office IPs or CDN ranges where possible
  • UFW: fine grain—service comments, SSH rate limits, block malicious IPs
  • fail2ban: dynamic layer—read logs and auto-ban

After changing either layer, verify from outside: nmap -p 22,80,443,3306 your.server.ip (scan only your own hosts). If 3306 shows open and you never meant to expose a database, check Docker bindings and application listen addresses immediately.

Troubleshooting: service unreachable after UFW is enabled

Work through this list in order—most issues surface within ten minutes:

  • sudo ufw status verbose—are rules actually active? Correct port and protocol (tcp/udp)?
  • sudo ss -tlnp—is the app listening on 0.0.0.0 rather than 127.0.0.1?
  • Are cloud security group inbound rules in sync?
  • Is Docker bypassing UFW?
  • Temporarily run sudo ufw disable for an A/B test (re-enable when done)

If only one source IP cannot connect, look for deny from rules or a fail2ban ban. UFW logging is sparse by default; when you need detail, set LOGLEVEL=medium in /etc/ufw/ufw.conf, run sudo ufw reload, then inspect /var/log/ufw.log for dropped packets.

Go-live checklist (copy and run)

On a new machine or after a reinstall, run through this sequence:

UFW new-server bootstrap (reference)
sudo apt install ufw -y
                sudo ufw default deny incoming
                sudo ufw default allow outgoing
                sudo ufw allow 22/tcp comment 'SSH'      # or your custom port
                sudo ufw allow 80/tcp
                sudo ufw allow 443/tcp
                sudo ufw limit 22/tcp                    # optional: SSH rate limit
                sudo ufw enable
                sudo ufw status verbose

Do not skip host hardening: SSH key login, disable root password login, and keep unattended-upgrades current. A firewall is the perimeter, not a cure-all—but it stops most automated scans cold and buys time for deeper security work.

One-line summary
Cloud security group on the outside, UFW on the inside: allow SSH first, default deny incoming, add service ports, enable with a second session open. Bind Docker to 127.0.0.1 so ports do not leak to the public internet.

A stable ops node: less anxiety around firewall changes

Managing multiple Linux VPS instances, many teams keep a fixed-egress-IP bastion for SSH jump access—security groups and UFW allow only the bastion IP, and local machines chain in with keys. A Mac mini fits this role well: native Unix on macOS, Terminal and OpenSSH ready out of the box; M4 silicon idles around 4W, quiet enough to run 7×24 on a desk as a relay node—more power-efficient and silent than another x86 mini PC.

A Windows box at the same price point draws more power and fan noise when left on as a jump host; macOS rarely crashes, and with FileVault and Gatekeeper, private key storage feels safer. If you also run Xcode or Docker for releases on the Mac, a cloud Mac mini can combine bastion and build duties in one node and cut context switching.

If you are planning a dependable remote ops setup, VPSSPark cloud Mac mini M4 is a practical low-power bastion and dev node see plans and pricing so server hardening does not have to be a solo late-night exercise.

Limited offer

Firewall done—your jump host should be solid too

Low-power Mac mini · native Unix jump box · silent 24/7

Back to home
Limited offer See plans