VPS nftables Firewall With Docker

VPS nftables Firewall With Docker

This note records a small nftables firewall setup for a VPS that also runs Docker.

The goal is not to write a clever universal ruleset. The goal is simpler: keep the host input path small, expose only the ports that should be public, and avoid fighting Docker's own NAT rules.

The example uses documentation IP addresses:

  • 203.0.113.10 as the admin's trusted IP
  • 198.51.100.20 as the server IP
  • 22222 as the SSH port

Replace them with your own values.

Basic Rules

Use the inet family so the same table can handle IPv4 and IPv6:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
set trusted_ipv4 {
type ipv4_addr
flags interval
elements = {
203.0.113.10,
}
}

chain input {
type filter hook input priority filter; policy drop;

iif "lo" accept
ct state established,related accept
ct state invalid drop

ip protocol icmp accept
ip6 nexthdr icmpv6 accept

# SSH: only allow the trusted admin IP.
ip saddr @trusted_ipv4 tcp dport 22222 accept

# Public web entry.
tcp dport { 80, 443 } accept
}

chain forward {
type filter hook forward priority filter; policy accept;
}

chain output {
type filter hook output priority filter; policy accept;
}
}

Docker Notes

Docker manages its own forwarding and NAT rules. If you blindly flush everything and then set forward to drop, containers may lose network access or published ports may stop working.

For a small VPS, I usually keep the boundary simple:

  • protect the host through the input chain
  • let Docker manage container NAT
  • expose public services through Caddy on 80/443
  • bind internal app ports to 127.0.0.1 when possible

Example Docker port mapping:

1
2
ports:
- "127.0.0.1:8080:8080"

Then let Caddy publish it over HTTPS.

Apply Safely

Validate before loading:

1
sudo nft -c -f /etc/nftables.conf