Firewall (ufw / nftables)
Reference: Ubuntu Docs — ufw · Debian Wiki — nftables · nftables Wiki
Host firewall vs network firewall
This covers host-level firewall rules (on the Linux machine itself). Your OPNsense router handles network-level filtering. Host firewall adds defense-in-depth — if a container exposes a port you forgot about, the host firewall is a second layer of protection.
ufw — Uncomplicated Firewall
ufw is the simplest firewall tool and is the Ubuntu default. It's a frontend to iptables/nftables.
Debian vs Ubuntu
ufw is pre-installed on Ubuntu. On Debian, install it: sudo apt install ufw. Both work identically.
Basic Setup
# Check current status
sudo ufw status
sudo ufw status verbose # more detail
sudo ufw status numbered # with rule numbers
# Enable / disable
sudo ufw enable
sudo ufw disable
# Reset all rules to default
sudo ufw reset
Warning
Before enabling ufw, always allow SSH first or you will lock yourself out.
sudo ufw allow ssh # allow SSH before enabling
sudo ufw enable
Default Policies
# Deny all incoming, allow all outgoing (recommended starting point for servers)
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow Rules
# By service name (uses /etc/services)
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
# By port number
sudo ufw allow 22
sudo ufw allow 8080
sudo ufw allow 443
# By port and protocol
sudo ufw allow 22/tcp
sudo ufw allow 53/udp
# Port range
sudo ufw allow 8000:8100/tcp
# From a specific IP
sudo ufw allow from 192.168.1.0/24
sudo ufw allow from 192.168.1.50
# From specific IP to specific port
sudo ufw allow from 192.168.1.50 to any port 22
sudo ufw allow from 192.168.1.0/24 to any port 5984 # CouchDB — LAN only
Deny Rules
# Deny a port
sudo ufw deny 23 # block telnet
# Deny from an IP
sudo ufw deny from 10.0.0.5
# Deny from IP to specific port
sudo ufw deny from 10.0.0.5 to any port 22
Delete Rules
# By rule number (get numbers from 'ufw status numbered')
sudo ufw delete 3
# By matching the rule
sudo ufw delete allow 8080
sudo ufw delete allow from 192.168.1.50 to any port 22
App Profiles
ufw includes named profiles for common applications:
# List available profiles
sudo ufw app list
# View a profile's ports
sudo ufw app info nginx
# Allow by profile
sudo ufw allow 'Nginx Full' # HTTP + HTTPS
sudo ufw allow 'Nginx HTTP' # HTTP only
sudo ufw allow 'OpenSSH'
Useful ufw Commands
# Check if ufw starts on boot
sudo systemctl is-enabled ufw
# View ufw logs
sudo journalctl -u ufw
sudo tail -f /var/log/ufw.log
# Enable logging
sudo ufw logging on
sudo ufw logging medium # options: off, low, medium, high, full
nftables
nftables is the modern replacement for iptables and is the default on Debian 10+. ufw uses it as its backend on newer systems, but you can also use nftables directly for more advanced rulesets.
Debian vs Ubuntu
Debian 10+ uses nftables natively. Ubuntu still primarily exposes ufw/iptables-nft as the user-facing interface. Direct nft usage is more common on Debian servers.
Basic Concepts
table → top-level namespace (like a rulebook)
chain → ordered list of rules within a table
rule → individual match + action
Checking nftables
# View all current rules
sudo nft list ruleset
# View a specific table
sudo nft list table inet filter
# Flush all rules (careful — removes all filtering)
sudo nft flush ruleset
Basic Ruleset Example
sudo nano /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Accept established/related connections
ct state established,related accept
# Accept loopback
iif lo accept
# Accept ICMP (ping)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Accept SSH from LAN only
ip saddr 192.168.1.0/24 tcp dport 22 accept
# Accept HTTP and HTTPS
tcp dport { 80, 443 } accept
# Accept specific service ports from LAN
ip saddr 192.168.1.0/24 tcp dport { 8080, 8090, 9898 } accept
# Log and drop everything else
log prefix "nftables drop: " drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
# Apply rules
sudo nft -f /etc/nftables.conf
# Enable on boot
sudo systemctl enable nftables
sudo systemctl start nftables
# Test config syntax without applying
sudo nft -c -f /etc/nftables.conf
Docker & Firewall Interaction
Docker bypasses ufw
Docker directly modifies iptables/nftables and will expose ports even if ufw blocks them. A container with -p 0.0.0.0:8080:8080 is publicly accessible regardless of ufw rules.
Options to fix this:
Option 1 — Bind to localhost only (per container)
In your compose file, bind to 127.0.0.1 instead of all interfaces:
ports:
- "127.0.0.1:8080:8080" # only accessible locally
Option 2 — Use a dedicated Docker network + reverse proxy
Don't publish ports at all. Use an internal Docker network and let NPM/nginx handle external access via its own published ports.
Option 3 — Restrict via DOCKER-USER chain (iptables)
# Allow LAN access to Docker ports, block everything else
sudo iptables -I DOCKER-USER -i eth0 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -I DOCKER-USER -i eth0 -j DROP
This is the only iptables chain Docker won't overwrite.