What is it? #
A firewall on a VPS decides which ports the internet can reach. The correct configuration is short: allow SSH, HTTP and HTTPS, deny everything else inbound.
There are two layers on most providers. The security group or cloud firewall sits outside the machine; ufw or firewalld runs on it. Both must allow the traffic.
The ordering rule that matters: allow SSH before enabling default deny. Getting this wrong on a remote machine disconnects you immediately.
Anything internal — databases, caches, application ports — should not be in the allow list at all. They belong on localhost or a private network.
Think of it like this #
A building with one staffed entrance and every other door locked from the inside.
Adding a door for a delivery is deliberate and rare. Leaving several unlocked because it was convenient during the fit-out is how things go missing.
Simple example #
A web server needs SSH from your office network, HTTP and HTTPS from anywhere, and PostgreSQL reachable only from the application server on the private network.
Code #
# 1. Allow SSH FIRST — this is the step people skip
sudo ufw allow OpenSSH
# 2. Default deny inbound, allow outbound
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 3. Public web traffic
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 4. Enable and verify
sudo ufw enable
sudo ufw status verbose
# Tighter rules for a real setup
sudo ufw delete allow OpenSSH
sudo ufw allow from 198.51.100.0/24 to any port 22 proto tcp comment 'SSH from office'
sudo ufw limit 22/tcp # rate limit, if SSH is public
# Database reachable only from the app server's private address
sudo ufw allow from 10.0.1.20 to any port 5432 proto tcp comment 'app server'
sudo ufw status numbered
Both layers must allow the traffic
internet
│
provider security group ← configured in the provider console
│ allow 22 (from your range), 80, 443
ufw on the server ← configured above
│
the service's bind address ← 127.0.0.1 for app and database
A timeout that persists after fixing ufw is almost always the security group.
# A safety net when editing rules remotely
sudo apt install at
echo "ufw --force reset && ufw allow OpenSSH && ufw --force enable" \
| sudo at now + 5 minutes
# make your changes, open a NEW session to confirm access still works
sudo atq # find the job id
sudo atrm <job-id> # cancel the restore once confirmed
How it works #
ufw allow OpenSSH uses the application profile for port 22. It must exist before ufw enable, because enabling default deny without it drops your current connection and prevents a new one.
Default deny inbound is what makes the firewall meaningful. Every port that is not explicitly allowed is unreachable, including one you accidentally start later.
Allowing all outbound is the practical default, since the server needs package repositories, DNS and external APIs. Restricting outbound is a hardening step for sensitive systems.
Replacing the general SSH rule with a source-restricted one removes SSH from the public internet entirely. Where your source address is not fixed, ufw limit at least slows automated attempts.
The database rule allows exactly one source address on the private network. Combined with binding PostgreSQL to the private interface rather than 0.0.0.0, that is two independent controls.
The two-layer diagram explains the most common confusion. Fixing ufw and still getting a timeout nearly always means the provider security group is blocking it.
The at safety net schedules a restore of a known-good rule set. If a change locks you out, it repairs itself in five minutes; if everything is fine, you cancel it.
Real-world use #
This configuration takes two minutes and prevents an entire category of incident. Exposed databases with default credentials are found by scanners within minutes of becoming reachable.
Provider security groups have an advantage over host firewalls: they keep working even if the machine is misconfigured, and they are managed outside the box.
Restricting SSH by source address is the strongest single control if your team has stable addresses. Where it does not, a bastion host or a VPN achieves the same.
Lockouts do happen. Knowing in advance whether your provider offers console access, and how to reach it, converts a crisis into an inconvenience.
Reviewing rules periodically catches drift — the port someone opened for a demo and never closed is a recurring finding in audits.
Common mistakes #
- Enabling default deny before allowing SSH.
- Opening a database port to the internet instead of restricting it to a private address.
- Configuring ufw and forgetting the provider security group.
- Leaving temporary rules in place after the reason for them has passed.
- Editing rules remotely with no console access and no safety net.
Practice #
Configure default deny with SSH restricted to one source range, plus HTTP and HTTPS open. Add a rule allowing a database port only from one private address. Then verify from an unauthorised address that the database port times out while the web ports respond.