Wg-quick Default Firewall Rules

The wg-quick program is the standard command-line tool for starting up and shutting down a WireGuard interface on Linux. By default it doesn’t set up any firewall rules — unless you configure one of the interface’s peers with an AllowedIPs setting that includes the default route: 0.0.0.0/0 for IPv4 or ::/0 for IPv6.

When you do that, wg-quick will automatically set up the following nftables firewall rules (if you system has nftables available):

table ip wg-quick-wg0 {
    chain preraw {
        type filter hook prerouting priority raw; policy accept;
        iifname != "wg0" ip daddr 10.0.0.1 fib saddr type != local drop
    }

    chain premangle {
        type filter hook prerouting priority mangle; policy accept;
        meta l4proto udp meta mark set ct mark
    }

    chain postmangle {
        type filter hook postrouting priority mangle; policy accept;
        meta l4proto udp meta mark 0x0000ca6c ct mark set meta mark
    }
}

Or the following iptables firewall rules if your system doesn’t have nftables:

*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -p udp -m comment --comment "wg-quick(8) rule for wg0" -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff
-A POSTROUTING -p udp -m mark --mark 0xca6c -m comment --comment "wg-quick(8) rule for wg0" -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff
COMMIT
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A PREROUTING -d 10.0.0.3/32 ! -i wg0 -m addrtype ! --src-type LOCAL -m comment --comment "wg-quick(8) rule for wg0" -j DROP
COMMIT

(The above is for IPv4; for IPv6, wg-quick does the same thing, just with the corresponding IPv6 flags and addresses.)

Routes and Policies

These firewall rules are added to supplement the routes and policy rules that wg-quick adds to use the WireGuard interface for the default route (below shown for IPv4; for IPv6, wg-quick does the exact same thing but with IPv6 flags and addresses):

ip -4 route add 0.0.0.0/0 dev wg0 table 51820
ip -4 rule add not fwmark 51820 table 51820
ip -4 rule add table main suppress_prefixlength 0

The first line above sets up a custom routing table, number 51820, and sets the default route for the table to use the wg0 interface. The second line adds a routing policy rule to use this new custom table for all packets that don’t have a mark (aka fwmark) of 51820.

In this “default route” mode, wg-quck also automatically configures WireGuard to add a mark of 51820 (0xca6c in hex) to all the encrypted packets it sends out, so this policy rule will apply to all packets that have not yet been processed by the WireGuard interface. (You can customize this mark to use a value other than 51820 via the FwMark setting on the interface’s config.)

The third line from above adds a routing policy rule to use the existing main routing table for everything except the default route. Because neither of the above policy rules has a specified priority, the rule from the third line is actually executed before the one from the second — so all routes in the main table, except for the default route, will continue to be used.

So the combination of these three lines switch the system from using the its primary physical network interface as the default route, to using the WireGuard interface instead.

Firewall Rule 1

So what do these firewall rules do? The first nftables rule prevents routing loops (and other hijinks) with packets sent directly to the WireGuard interface’s address from an external source other than through the WireGuard interface itself. In nftables, it’s applied via the prerouting hook of a custom filter chain, at the raw (-300) step (before connection tracking is applied):

iifname != "wg0" ip daddr 10.0.0.1 fib saddr type != local drop

This is the iptables equivalent (listed third in iptables rules from above):

-A PREROUTING -d 10.0.0.1/32 ! -i wg0 -m addrtype ! --src-type LOCAL -j DROP

In the above example, our WireGuard interface is wg0, and its address is 10.0.0.1. The destination address of a packet usually should be the WireGuard interface’s own address only if the packet itself just came through the WireGuard tunnel. If it came from some other source (not including the system itself), it can be safely dropped, since it’s either an accident or an attack.

If it’s not dropped, and the packet’s source is not included in one of the routes from the system’s main table, then any reply to the original packet (like an ICMP port-unreachable reply) would be sent through the WireGuard tunnel instead of back out the same interface from which the original packet came (thus potentially creating loops or other mischief).

Firewall Rule 3

The second and third nftables rules work in concert to copy the mark that WireGuard sets on outbound packets over to the inbound replies to those packets. The third rule listed actually is executed first in this sequence, so we’ll look at it first. In nftables, it’s applied via the postrouting hook of a custom filter chain, at the mangle (-150) step (after connection tracking, but before address translation):

meta l4proto udp meta mark 0x0000ca6c ct mark set meta mark

This is the iptables equivalent (listed second in the iptables rules from above):

-A POSTROUTING -p udp -m mark --mark 0xca6c -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff

0xca6c is hex for 51820, so this rule copies the packet mark (aka “fwmark”) to the connection-tracking mark (aka “connmark”) for outgoing UDP packets that already have a packet mark of 51820. These marks are just arbitrary 32-bit values that have no predefined meaning to the system — they’re intended for use by sysadmins to write firewall and routing rules that track individual packets, or two-way connections, through different points in the system. The packet mark applies to just a single packet; whereas the connection-tracking mark applies to all packets tracked as part of a particular two-way connection.

Firewall Rule 2

The second nftables rule copies the connection-tracking mark on incoming UDP packets back to the packet mark. In nftables, it’s applied via the prerouting hook of a custom filter chain, at the mangle (-150) step (after connection tracking, before address translation):

meta l4proto udp meta mark set ct mark

This is the iptables equivalent (listed first in the iptables rules from above):

-A PREROUTING -p udp -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff

Copying the original mark from outgoing WireGuard packets to their inbound replies (and turning on the net.ipv4.conf.all.src_valid_mark sysctl parameter) prevents the ip -4 rule add not fwmark 51820 table 51820 policy rule that we discussed above from breaking reverse-path routing lookups on those inbound packets. This is particularly important if you have strict reverse-path filtering turned on for your system (as it would otherwise drop all inbound packets to your system).

You can check whether you have reverse-path filtering turned on with the following command:

$ sysctl net.ipv4.conf.all.rp_filter

A value of 1 is strict filtering (drop packets if the reverse path doesn’t match the forward path); a value of 2 is loose filtering (drop packets only if the reverse path isn’t reachable); and a value of 0 is no filtering.

Disabling the Defaults

To prevent wg-quick from adding any firewall rules — as well as any routes and policy rules — configure the interface’s Table setting to off:

[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.0.1/32
Table = off

[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
AllowedIPs = 0.0.0.0/0

You could alternatively configure wg-quick to set up the default route in a custom routing table without setting up any firewall rules or policy rules; in that case, configure Table to the number or name of the table (like Table = 123 or Table = foo). Note that if you use a name for the table, you must first manually add the number-to-name mapping for it to the /etc/iproute2/rt_tables config file:

$ echo '123 foo' | sudo tee -a /etc/iproute2/rt_tables

If you disable these defaults, you will need to implement your own routes or policy rules. See the Routing All Your Traffic section of Routing guide on the WireGuard site for a few simple alternatives. Also see the How to Use WireGuard With Nftables or WireGuard Access Control With Iptables articles on this site for guides on how to set up firewall rules for WireGuard.