This nftables table can be used for a server running WireGuard. A few notes about the template:
NAT_V4
variable.oifname
and ifname
rather than oif
and iif
. There is a slightly penalty for this type of match but it allows the rules to be applied before creation of the WireGuard interface.To use the template, the various variables at the top of the file need to be edited to suite your environment.
A recent Linux kernel and nftables release is required to use use these rules as written. Earlier kernel releases require a few modifications (eg. to match multiple protocols for DNS and for SNAT). At the time of writing, for Debian Bullseye, the backports kernel is required. This can be installed by making sure the backports apt source is present and installing linux-image-amd64
from that source:
apt -t bullseye-backports -y install linux-image-amd64
#!/usr/sbin/nft -f
## Clear/flush all existing rules
flush ruleset
################################ VARIABLES ################################
## Internet/WAN interface name
define DEV_WAN = eth0
## WireGuard interface name
define DEV_WIREGUARD = wg0
## IPv4 NAT IP for clients
define NAT_V4 = 192.0.2.1
## VPN client allocation - IPv4
define VPN_PREFIX_V4 = 10.199.104.0/24
## VPN client allocation - IPv6
define VPN_PREFIX_V6 = 2001:db8::/64
## WireGuard listen port
define WIREGUARD_PORT = 51820
############################## VARIABLES END ##############################
## List of RFC1918 networks
## Desination traffic from WireGuard clients to these networks will be redirected to the local DNS resolver
define RFC1918 = {
10.0.0.0/8,
172.16.0.0/12,
192.168.0.0/16
}
## Add fail2ban rules
include "/etc/nftables/fail2ban.conf"
# Raw filtering table
table inet raw {
# Prerouting traffic rules
chain prerouting {
type filter hook prerouting priority -300;
## Skip connection tracking for WireGuard inbound
iif $DEV_WAN udp dport $WIREGUARD_PORT \
notrack \
comment "Skip connection tracking for inbound WireGuard traffic"
}
# Output traffic rules
chain output {
type filter hook output priority -300;
## Skip connection tracking for WireGuard
oif $DEV_WAN udp sport $WIREGUARD_PORT \
notrack \
comment "Skip connection tracking for outbound WireGuard traffic"
}
}
# Main inet family filtering table
table inet filter {
# Rules for forwarded traffic
chain forward {
type filter hook forward priority 0; policy drop
## Adjust MSS of connections from WireGuard clients out to internet; this is a work around for devices acting like a router that may have clients sending incorrectly sized packets
## As an example, a host running Docker containers may have SSL connection issues to certain badly configured hosts
iifname $DEV_WIREGUARD oif $DEV_WAN tcp flags & (syn|rst) == syn \
tcp option maxseg size set 1360 \
comment "Adjust TCP MSS for VPN clients to internet; this works around potential MTU related issues"
## Permit connections from WireGuard clients out to internet
iifname $DEV_WIREGUARD oif $DEV_WAN \
counter \
accept \
comment "Permit connections from WireGuard clients out to internet"
## Drop connections from WireGuard clients to other WireGuard clients
iifname $DEV_WIREGUARD oifname $DEV_WIREGUARD \
counter \
drop \
comment "Prevent connections from WireGuard clients to other WireGuard clients"
## Permit established and related connections from WAN to WireGuard clients
iif $DEV_WAN oifname $DEV_WIREGUARD ct state established,related \
counter \
accept \
comment "Permit established/related connections"
## Permit IPv6 ping/ping responses from internet to WireGuard clients but rate limit to 2000 PPS
iif $DEV_WAN oifname $DEV_WIREGUARD icmpv6 type { echo-reply, echo-request } \
limit rate 2000/second \
counter \
accept \
comment "Permit inbound IPv6 echo (ping) limited to 2000 PPS"
## Permit all other inbound IPv6 ICMP to WireGuard clients
iif $DEV_WAN oifname $DEV_WIREGUARD meta l4proto { icmpv6 } \
counter \
accept \
comment "Permit all other IPv6 ICMP"
## Permit traffic from WireGuard to loopback interface to allow access to this server itself
iifname $DEV_WIREGUARD oif lo \
counter \
accept \
comment "Permit inbound traffic from WireGuard clients to local loopback interface"
## Log any unmatched traffic but rate limit logging to a maximum of 60 messages/minute
## The default policy will be applied to unmatched traffic
limit rate 60/minute burst 100 packets \
log prefix "Forward - Drop: " \
comment "Log any unmatched traffic"
## Count the unmatched traffic
counter \
comment "Count any unmatched traffic"
}
# Rules for input traffic
chain input {
type filter hook input priority 0; policy drop
## Permit WireGuard traffic
iif $DEV_WAN udp dport $WIREGUARD_PORT ct state untracked \
counter \
accept \
comment "Permit inbound untracked WireGuard traffic"
## Permit inbound traffic to loopback interface
iif lo \
accept \
comment "Permit all traffic in from loopback interface"
## Permit established and related connections
ct state established,related \
accept \
comment "Permit established/related connections"
## Log and drop new TCP non-SYN packets
tcp flags != syn ct state new \
limit rate 100/minute burst 150 packets \
log prefix "IN - New !SYN: " \
comment "Rate limit logging for new connections that do not have the SYN TCP flag set"
tcp flags != syn ct state new \
counter \
drop \
comment "Drop new connections that do not have the SYN TCP flag set"
## Log and drop TCP packets with invalid fin/syn flag set
tcp flags & (fin|syn) == (fin|syn) \
limit rate 100/minute burst 150 packets \
log prefix "IN - TCP FIN|SIN: " \
comment "Rate limit logging for TCP packets with invalid fin/syn flag set"
tcp flags & (fin|syn) == (fin|syn) \
counter \
drop \
comment "Drop TCP packets with invalid fin/syn flag set"
## Log and drop TCP packets with invalid syn/rst flag set
tcp flags & (syn|rst) == (syn|rst) \
limit rate 100/minute burst 150 packets \
log prefix "IN - TCP SYN|RST: " \
comment "Rate limit logging for TCP packets with invalid syn/rst flag set"
tcp flags & (syn|rst) == (syn|rst) \
counter \
drop \
comment "Drop TCP packets with invalid syn/rst flag set"
## Log and drop invalid TCP flags
tcp flags & (fin|syn|rst|psh|ack|urg) < (fin) \
limit rate 100/minute burst 150 packets \
log prefix "IN - FIN:" \
comment "Rate limit logging for invalid TCP flags (fin|syn|rst|psh|ack|urg) < (fin)"
tcp flags & (fin|syn|rst|psh|ack|urg) < (fin) \
counter \
drop \
comment "Drop TCP packets with flags (fin|syn|rst|psh|ack|urg) < (fin)"
## Log and drop invalid TCP flags
tcp flags & (fin|syn|rst|psh|ack|urg) == (fin|psh|urg) \
limit rate 100/minute burst 150 packets \
log prefix "IN - FIN|PSH|URG:" \
comment "Rate limit logging for invalid TCP flags (fin|syn|rst|psh|ack|urg) == (fin|psh|urg)"
tcp flags & (fin|syn|rst|psh|ack|urg) == (fin|psh|urg) \
counter \
drop \
comment "Drop TCP packets with flags (fin|syn|rst|psh|ack|urg) == (fin|psh|urg)"
## Drop traffic with invalid connection state
ct state invalid \
limit rate 100/minute burst 150 packets \
log flags all prefix "IN - Invalid: " \
comment "Rate limit logging for traffic with invalid connection state"
ct state invalid \
counter \
drop \
comment "Drop traffic with invalid connection state"
## Permit IPv4 ping/ping responses but rate limit to 2000 PPS
ip protocol icmp icmp type { echo-reply, echo-request } \
limit rate 2000/second \
counter \
accept \
comment "Permit inbound IPv4 echo (ping) limited to 2000 PPS"
## Permit all other inbound IPv4 ICMP
ip protocol icmp \
counter \
accept \
comment "Permit all other IPv4 ICMP"
## Permit IPv6 ping/ping responses but rate limit to 2000 PPS
icmpv6 type { echo-reply, echo-request } \
limit rate 2000/second \
counter \
accept \
comment "Permit inbound IPv6 echo (ping) limited to 2000 PPS"
## Permit all other inbound IPv6 ICMP
meta l4proto { icmpv6 } \
counter \
accept \
comment "Permit all other IPv6 ICMP"
## Permit inbound traceroute UDP ports but limit to 500 PPS
udp dport 33434-33524 \
limit rate 500/second \
counter \
accept \
comment "Permit inbound UDP traceroute limited to 500 PPS"
## Permit inbound SSH
tcp dport ssh ct state new \
accept \
comment "Permit inbound SSH connections via the WAN interface"
## Permit inbound HTTP and HTTPS
tcp dport { http, https } ct state new \
accept \
comment "Permit inbound HTTP and HTTPS connections via the WAN interface"
## Permit inbound DNS requests to the WireGuard interface
iifname $DEV_WIREGUARD meta l4proto { tcp, udp } th dport 53 \
counter \
accept \
comment "Permit inbound DNS requets to the WireGuard interface from the WireGuard clients"
## Log any unmatched traffic but rate limit logging to a maximum of 60 messages/minute
## The default policy will be applied to unmatched traffic
limit rate 60/minute burst 100 packets \
log prefix "IN - Drop: " \
comment "Log any unmatched traffic"
## Count the unmatched traffic
counter \
comment "Count any unmatched traffic"
}
# Rules for output traffic
chain output {
type filter hook output priority 0; policy accept
## Permit WireGuard traffic
oif $DEV_WAN udp sport $WIREGUARD_PORT ct state untracked \
counter \
accept \
comment "Permit outbound untracked WireGuard traffic"
}
}
# Main NAT filtering table
table ip nat {
# Rules for traffic pre-routing
chain prerouting {
type nat hook prerouting priority 0; policy accept
## Redirect RFC1918 DNS traffic to prevent DNS leaks
iifname $DEV_WIREGUARD ip saddr $VPN_PREFIX_V4 meta l4proto { tcp, udp } th dport 53 ip daddr $RFC1918 \
counter \
redirect \
comment "Redirect DNS traffic to RFC1918 networks to local DNS resolver to prevent DNS leaks"
}
# Rules for traffic post-routing
chain postrouting {
type nat hook postrouting priority 100; policy accept
## Source NAT WireGuard IPv4 client traffic to the internet
iifname $DEV_WIREGUARD oif $DEV_WAN ip saddr $VPN_PREFIX_V4 \
counter \
snat to $NAT_V4 \
comment "Source IPv4 NAT traffic to the internet from WireGuard clients"
}
}