Configuring UFW Firewall Rules On Linux Servers
What You’ll Need
- Hetzner VPS or Contabo VPS running Ubuntu 22.04 LTS or Debian 12
- DigitalOcean droplet (alternative infrastructure provider)
- Root or sudo privileges on your remote server
- Namecheap registered domain name (if routing traffic for public web applications)
Table of Contents
- Understanding UFW Architecture and Default Policies
- Setting Up Core Service Rules and Application Profiles
- Advanced UFW Rule Management and IP Filtering
- NAT Port Forwarding and UFW Log Management
- Getting Started
Understanding UFW Architecture and Default Policies
Securing a Linux server starts with controlling network access. Uncomplicated Firewall (UFW) serves as a user-friendly frontend to iptables and nftables. It handles firewall rules without requiring you to master complex table syntax. When I provision a new instance on a Hetzner VPS or DigitalOcean, UFW is the very first defensive layer I enable.
Before enabling UFW, you must understand packet evaluation order and default behaviors. By default, UFW drops incoming connection requests and allows outgoing connection requests. If you turn UFW on without configuring an explicit SSH rule, you will immediately lock yourself out of your server.
UFW uses configuration files stored in /etc/default/ufw and /etc/ufw/. The main control file defines how default chains behave. You can inspect your current configuration file with this command:
cat /etc/default/ufw
The output defines system policies across all interfaces:
IPV6=yes
DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"
DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_APPLICATION_POLICY="SKIP"
MANAGE_BUILTINS=no
IPT_SYSCTL=/etc/ufw/sysctl.conf
MANAGE_IPS=yes
If your configuration shows DEFAULT_INPUT_POLICY="ACCEPT", modify it immediately using CLI commands. I prefer setting defaults explicitly through commands rather than manually editing the configuration file to prevent syntax errors.
Reset UFW to clean state if you want to eliminate existing rules:
sudo ufw reset
The terminal prompts for confirmation before wiping custom rules:
Resetting all rules to installed defaults. This may disrupt existing ssh connections. Proceed with operation (y|n)? y
Backing up 'user.rules' to '/etc/ufw/user.rules.20260330_120000'
Backing up 'before.rules' to '/etc/ufw/before.rules.20260330_120000'
Backing up 'after.rules' to '/etc/ufw/after.rules.20260330_120000'
Backing up 'user6.rules' to '/etc/ufw/user6.rules.20260330_120000'
Backing up 'before6.rules' to '/etc/ufw/before6.rules.20260330_120000'
Backing up 'after6.rules' to '/etc/ufw/after6.rules.20260330_120000'
Set default security policies across the system:
sudo ufw default deny incoming
sudo ufw default allow outgoing
This configuration ensures that any inbound traffic not explicitly allowed gets rejected, while internal applications can pull system updates, make API requests, and send outbound logs.
Setting Up Core Service Rules and Application Profiles
With default drop policies configured, you must open essential infrastructure ports before enabling the firewall daemon. Always permit SSH connections first to safeguard remote administration sessions.
To allow standard SSH over port 22:
sudo ufw allow 22/tcp
If your server runs SSH on a non-standard port like 2222 to reduce automated brute-force attempts, allow that custom port explicitly:
sudo ufw allow 2222/tcp
Public web platforms require open ports for standard HTTP and HTTPS traffic. If you are running web services, follow our detailed guide on Configuring Nginx Reverse Proxy with Certbot SSL to complete your public web stack setup. Open standard web ports with these commands:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
UFW includes application profiles stored in /etc/ufw/applications.d/. These profiles map human-readable application names directly to specified protocol ports. Check available application profiles on your system:
sudo ufw app list
You will see output similar to this:
Available applications:
Nginx Full
Nginx HTTP
Nginx HTTPS
OpenSSH
Allow an entire profile directly by specifying its name:
sudo ufw allow "Nginx Full"
If you manage custom backend systems, you can define your own application profile. For instance, if you are Deploying Self-Hosted PocketBase on Cloud Servers, you can create a profile for PocketBase on port 8090. Create a file named /etc/ufw/applications.d/pocketbase:
[PocketBase]
title=PocketBase Backend Server
description=Self-contained database and real-time backend engine.
ports=8090/tcp
Verify your custom application profile is loaded:
sudo ufw app info PocketBase
The console displays the profile parameters:
Profile: PocketBase
Title: PocketBase Backend Server
Description: Self-contained database and real-time backend engine.
Port:
8090/tcp
Allow the new profile through UFW:
sudo ufw allow PocketBase
Now enable UFW. Review your terminal carefully because enabling the service activates all rules immediately:
sudo ufw enable
Confirm activation status:
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
Verify the detailed list of active rules:
sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: loaded
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp (Nginx Full) ALLOW IN Anywhere
443/tcp (Nginx Full) ALLOW IN Anywhere
8090/tcp (PocketBase) ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
80/tcp (Nginx Full (v6)) ALLOW IN Anywhere (v6)
443/tcp (Nginx Full (v6)) ALLOW IN Anywhere (v6)
8090/tcp (PocketBase (v6)) ALLOW IN Anywhere (v6)
💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.
Advanced UFW Rule Management and IP Filtering
Broad rule definitions allowing full access from anywhere are acceptable for public websites, but administrative endpoints require strict network boundaries. Restricting infrastructure access to single static IPs or specific subnets minimizes exposure.
Allow connection to SSH (port 22) only from a trusted administrator IP address 198.51.100.45:
sudo ufw allow from 198.51.100.45 to any port 22 proto tcp
Allow an internal corporate subnet 192.168.1.0/24 access to a self-hosted PostgreSQL database on port 5432:
sudo ufw allow from 192.168.1.0/24 to any port 5432 proto tcp
If your server features multiple network interfaces (such as eth0 for external traffic and eth1 or tailscale0 for internal traffic), bind rules directly to specific network adapters.
Allow access to MySQL port 3306 exclusively over internal interface eth1:
sudo ufw allow in on eth1 to any port 3306 proto tcp
Deny traffic explicitly from malicious or offending subnets:
sudo ufw deny from 203.0.113.0/24
To prevent automated scripts from hammering exposed endpoints, use built-in rate limiting. UFW limit rules drop connections from IP addresses that attempt 6 or more connections within a 30-second window:
sudo ufw limit 22/tcp
Managing rules over time requires working with rule numbers. List active rules sequentially:
sudo ufw status numbered
The output shows numbered indices:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp LIMIT IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 5432/tcp ALLOW IN 192.168.1.0/24
[ 5] 203.0.113.0/24 DENY IN Anywhere
Delete a specific rule by supplying its exact index number:
sudo ufw delete 5
Insert a high-priority rule at position 1 to make sure evaluation takes place before downstream wildcard rules:
sudo ufw insert 1 allow from 198.51.100.50 to any port 22 proto tcp
Check the updated numbered list to verify rule order:
sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN 198.51.100.50
[ 2] 22/tcp LIMIT IN Anywhere
[ 3] 80/tcp ALLOW IN Anywhere
[ 4] 443/tcp ALLOW IN Anywhere
[ 5] 5432/tcp ALLOW IN 192.168.1.0/24
NAT Port Forwarding and UFW Log Management
UFW can operate as a edge router or internal proxy by forwarding incoming packets on external interfaces directly to underlying private networks, container networks, or virtual machines.
First, enable kernel-level IP forwarding. Open /etc/ufw/sysctl.conf:
sudo nano /etc/ufw/sysctl.conf
Uncomment or insert these kernel variables to allow packet routing across interfaces:
net/ipv4/ip_forward=1
net/ipv6/conf/default/forwarding=1
net/ipv6/conf/all/forwarding=1
Next, enable packet forwarding in /etc/default/ufw:
sudo nano /etc/default/ufw
Change DEFAULT_FORWARD_POLICY from "DROP" to "ACCEPT":
DEFAULT_FORWARD_POLICY="ACCEPT"
Configure Network Address Translation (NAT) rules inside /etc/ufw/before.rules. Place your NAT definitions before the standard filter table configuration.
Open /etc/ufw/before.rules:
sudo nano /etc/ufw/before.rules
Add the complete NAT block at the very top of the file, above the default *filter line:
# NAT table configuration
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# Forward traffic coming into port 8080 on eth0 to internal container IP 10.8.0.5 on port 80
-A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 10.8.0.5:80
# Masquerade internal network traffic egressing out eth0
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
Reload UFW to apply kernel routing and NAT rule structures:
sudo ufw reload
Firewall visibility is essential for tracking suspicious activity and auditing connection patterns. Manage logging levels with the logging command:
sudo ufw logging medium
Available logging levels include:
off: Disables logging entirely.low: Logs blocked packets that match default policies or explicit block rules.medium: Logs low level activity plus allowed connections, invalid packets, and new connections.high: Logs everything with rate limiting.full: Logs all packets without rate limits (generates massive log volume).
UFW writes packet decision logs directly to /var/log/ufw.log. Monitor raw log entries in real-time:
sudo tail -f /var/log/ufw.log
Log lines contain fields detailing matched rules, source networks, and ports:
Mar 30 12:45:10 server01 kernel: [UFW BLOCK] IN=eth0 OUT= MAC=52:54:00:12:34:56:52:54:00:65:43:21:08:00 SRC=198.51.100.99 DST=192.0.2.1 LEN=40 TOS=0x00 PREC=0x00 TTL=245 ID=54321 PROTO=TCP SPT=44123 DPT=23 WINDOW=65535 SYN URGP=0
Field definitions:
IN: Interface where packet entered the host machine (eth0).SRC: Originating IP address of the traffic payload (198.51.100.99).DST: Target IP address belonging to your server (192.0.2.1).SPT: Source port assigned by requesting client (44123).DPT: Destination port targeted on your host server (23/ Telnet).SYN: Indicates an initial connection establishment attempt.
For production clusters, parsing raw text files manually on each server quickly becomes unmanageable. Standardize your infrastructure monitoring by Configuring Centralized Log Aggregation with Grafana Loki to stream /var/log/ufw.log entries directly into a central dashboard for real-time security alerts and threat analysis.
Getting Started
Building a secure Linux server starts with choosing host infrastructure, locking down unused ports, and auditing access controls.
- Deploy a secure server instance using a provider like Hetzner VPS, Contabo VPS, or a DigitalOcean droplet.
- Domain resolution for web services can be managed via Namecheap.
- Connect over SSH and apply default deny policies immediately.
- Open administrative SSH and web application ports before turning UFW on.
- Set up logging to monitor unexpected traffic patterns.
Outsource Your Automation
Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.
Want to automate this yourself?
Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.
Want this engine running on your own VPS?
This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.
Get System 3