Back to blog

UFW on Linux: Secure Your VPS Like a Pro

linuxubuntusecurityvpsdevopsfirewall
UFW on Linux: Secure Your VPS Like a Pro

You just spun up a fresh VPS. You SSH in, deploy your app, and everything works. Life is good — until you check the logs and find thousands of brute-force attempts on port 22, random bots scanning every port, and suspicious traffic from IPs you've never seen.

Welcome to the internet. Your server is under attack from the moment it goes online.

The first line of defense? A firewall. And on Ubuntu, the simplest way to set one up is UFW — Uncomplicated Firewall.

What is UFW?

UFW is a user-friendly frontend for iptables, the powerful but notoriously complex Linux firewall. Ubuntu ships with UFW pre-installed (but disabled by default).

Think of it this way:

  • iptables = the engine (powerful, low-level, hard to configure)
  • UFW = the steering wheel (simple commands, sane defaults)

UFW doesn't replace iptables — it generates iptables rules for you, so you get the same security with a fraction of the complexity.

Getting Started

Installation

UFW comes pre-installed on Ubuntu. If it's missing for some reason:

sudo apt update
sudo apt install ufw

Check the status:

sudo ufw status
# Status: inactive

The Golden Rule: Allow SSH Before Enabling

This is the most important step. If you enable UFW without allowing SSH, you'll lock yourself out of your own server.

# ALWAYS do this first
sudo ufw allow ssh
# or equivalently
sudo ufw allow 22/tcp

Enable UFW

sudo ufw enable

You'll see a warning: "Command may disrupt existing ssh connections." Type y — your SSH rule is already in place.

Verify:

sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
 
To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

Essential Commands

Allow and Deny Rules

# Allow a specific port
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
 
# Allow a port range
sudo ufw allow 8000:8100/tcp
 
# Allow from a specific IP
sudo ufw allow from 203.0.113.50
 
# Allow from a specific IP to a specific port
sudo ufw allow from 203.0.113.50 to any port 5432
 
# Allow a subnet
sudo ufw allow from 10.0.0.0/24
 
# Deny a specific IP (block an attacker)
sudo ufw deny from 23.94.168.0/24
 
# Deny a specific port
sudo ufw deny 3306/tcp

Using Application Profiles

UFW comes with pre-configured profiles for common applications:

# List available profiles
sudo ufw app list
 
# Allow by profile name
sudo ufw allow 'Nginx Full'    # ports 80 + 443
sudo ufw allow 'Nginx HTTP'    # port 80 only
sudo ufw allow 'Nginx HTTPS'   # port 443 only
sudo ufw allow 'OpenSSH'       # port 22
 
# View profile details
sudo ufw app info 'Nginx Full'

Deleting Rules

# List rules with numbers
sudo ufw status numbered
 
# Delete by rule number
sudo ufw delete 3
 
# Delete by rule specification
sudo ufw delete allow 8080/tcp

Reset Everything

# Nuclear option — removes ALL rules and disables UFW
sudo ufw reset

Real-World VPS Configuration

Here's a complete setup for a typical web server running Nginx + a backend app + PostgreSQL:

# 1. Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
 
# 2. Allow SSH (ALWAYS first)
sudo ufw allow ssh
 
# 3. Allow web traffic
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
 
# 4. Allow your backend app (e.g., on port 3000) only from localhost
# This means Nginx reverse-proxies to it — no direct external access
sudo ufw allow from 127.0.0.1 to any port 3000
 
# 5. Allow PostgreSQL only from your app server's IP
sudo ufw allow from 10.0.0.5 to any port 5432
 
# 6. Enable UFW
sudo ufw enable

The key principle: deny everything by default, then allow only what's needed.

Docker and UFW — The Gotcha

If you run Docker on your VPS, there's a critical issue you need to know:

Docker bypasses UFW entirely. When Docker publishes a port (-p 5432:5432), it writes its own iptables rules that UFW doesn't control. Your "deny all" policy won't block traffic to Docker-exposed ports.

Solutions:

Option 1: Bind Docker ports to localhost only

# docker-compose.yml
services:
  postgres:
    ports:
      - "127.0.0.1:5432:5432"  # Only accessible from the host
# Or with docker run
docker run -p 127.0.0.1:5432:5432 postgres

Option 2: Use Docker networks instead of published ports

# docker-compose.yml
services:
  app:
    networks:
      - internal
 
  postgres:
    networks:
      - internal
    # No 'ports' section — only accessible within the Docker network
 
networks:
  internal:
    driver: bridge

Option 3: Configure Docker to respect UFW

Edit /etc/docker/daemon.json:

{
  "iptables": false
}

Then restart Docker:

sudo systemctl restart docker

Warning: Setting "iptables": false means Docker can't manage its own networking rules. You'll need to manually configure iptables rules for container communication. This approach requires more expertise.

Recommended approach: Use Option 1 (bind to localhost) for databases and internal services, and let Nginx handle external traffic. This is the simplest and most reliable method.

Best Practices for VPS Hardening

1. Change the Default SSH Port

Moving SSH off port 22 won't stop a determined attacker, but it eliminates 99% of automated brute-force bots:

# Edit SSH config
sudo nano /etc/ssh/sshd_config
 
# Change this line:
# Port 22
Port 2222
 
# Restart SSH
sudo systemctl restart sshd

Update UFW:

sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
# or: sudo ufw delete allow ssh

Important: Open a NEW SSH session on port 2222 before closing your current one. If something went wrong, you still have access through the existing connection.

2. Rate Limit SSH Connections

UFW can rate-limit connections — if an IP tries to connect more than 6 times in 30 seconds, it gets blocked:

sudo ufw limit ssh
# or if using a custom port:
sudo ufw limit 2222/tcp

This is a simple but effective defense against brute-force attacks.

3. Disable Password Authentication

Use SSH keys instead of passwords. This alone eliminates nearly all brute-force risks:

# On your local machine — generate a key pair if you haven't
ssh-keygen -t ed25519 -C "your-email@example.com"
 
# Copy the public key to your server
ssh-copy-id -p 2222 user@your-server-ip

Then on the server, disable password login:

sudo nano /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
sudo systemctl restart sshd

4. Enable Logging

# Set logging level (low, medium, high, full)
sudo ufw logging medium

Check logs:

# UFW logs go to syslog
sudo grep UFW /var/log/syslog | tail -20
 
# Or on newer Ubuntu versions
sudo journalctl | grep UFW | tail -20

5. Only Open Ports You Actually Need

Audit your open ports regularly:

# Check what UFW allows
sudo ufw status numbered
 
# Check what's actually listening
sudo ss -tlnp

If ss shows a service listening on a port that UFW doesn't allow, that's good — it means UFW is blocking external access. If UFW allows a port that nothing is listening on, remove the rule — no point having it open.

6. Use Fail2Ban Alongside UFW

UFW handles static rules. Fail2Ban watches logs and dynamically bans IPs that show malicious behavior:

sudo apt install fail2ban

Create a local config:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
banaction = ufw
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Now Fail2Ban will automatically add UFW deny rules for IPs that fail SSH login 3 times within 10 minutes.

Check banned IPs:

sudo fail2ban-client status sshd

Complete VPS Security Checklist

Here's a quick reference for securing a fresh Ubuntu VPS:

Firewall Setup:
✅ Set default deny incoming, allow outgoing
✅ Allow SSH (on a custom port)
✅ Allow only ports 80 and 443 for web traffic
✅ Bind Docker ports to 127.0.0.1
✅ Enable UFW logging

SSH Hardening:
✅ Change SSH port from 22
✅ Disable password authentication
✅ Disable root login
✅ Use Ed25519 SSH keys
✅ Enable UFW rate limiting on SSH port

Additional Protection:
✅ Install and configure Fail2Ban
✅ Keep system updated (sudo apt update && sudo apt upgrade)
✅ Audit open ports regularly with ss -tlnp
✅ Remove unnecessary UFW rules
✅ Set up automatic security updates (unattended-upgrades)

Common Mistakes to Avoid

Enabling UFW before allowing SSH — You'll lock yourself out. Always sudo ufw allow ssh first.

Exposing database ports to the internet — PostgreSQL (5432), MySQL (3306), Redis (6379) should never be publicly accessible. Bind to localhost or use firewall rules to restrict access.

Trusting Docker + UFW together — Docker bypasses UFW by default. Always bind Docker ports to 127.0.0.1 for internal services.

Using ufw allow without specifying the protocolsudo ufw allow 3000 allows both TCP and UDP. Be explicit: sudo ufw allow 3000/tcp.

Forgetting IPv6 — UFW handles IPv6 by default (check /etc/default/ufw for IPV6=yes), but make sure your rules work for both protocols.

Quick Reference

TaskCommand
Check statussudo ufw status verbose
Enable UFWsudo ufw enable
Disable UFWsudo ufw disable
Allow portsudo ufw allow 80/tcp
Deny portsudo ufw deny 3306/tcp
Allow from IPsudo ufw allow from 1.2.3.4
Rate limitsudo ufw limit ssh
Delete rulesudo ufw delete allow 8080/tcp
List numberedsudo ufw status numbered
Reset allsudo ufw reset
View logssudo grep UFW /var/log/syslog

Wrapping Up

UFW is one of those tools that takes 5 minutes to set up but saves you from countless headaches. The defaults are sane, the commands are readable, and combined with Fail2Ban and proper SSH hardening, you've got a solid security foundation for your VPS.

The key takeaway: default deny, explicit allow, and always verify. Your server is only as secure as the rules you define — so define them carefully, audit them regularly, and sleep a little better at night knowing your VPS isn't wide open to the internet.

📬 Subscribe to Newsletter

Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.

We respect your privacy. Unsubscribe at any time.

💬 Comments

Sign in to leave a comment

We'll never post without your permission.