Best Linux Firewall for WireGuard
When setting up a new WireGuard server on Linux, what’s the best firewall to use? We recommend using firewalld on WireGuard Endpoints, and nftables on WireGuard Gateways.
Note
|
Technically, behind the scenes, all Linux firewalls use the netfilter kernel subsystem — firewalld, UFW, nftables, iptables, etc are all just “front ends” to netfilter. However, each of these front ends maintain their own firewall rules separately — so if you try to use more than one on the same machine, you will often end up with conflicting rules. |
Endpoints
Firewalld has a simple command-line interface, which makes it ideal for use on WireGuard endpoints — like an end-user workstation, or an internal service (like an internal webapp or fileshare). A GUI (Graphical User Interface) tool for firewalld, firewall-config, is also available, which can make firewalld’s options more discoverable:
User Endpoint
On an end-user workstation, you’d typically install the firewalld
package from its OS (Operating System) package manager, and then use NetworkManager to assign a firewalld zone to each connection; either with the graphical NetworkManager control panel or applet, or the nmcli command-line tool.
Firewalld’s built-in public
zone makes for a good default to use with a workstation’s physical network connections (like eth0
or wlan0
); and its internal
zone provides a good default for a workstation’s WireGuard interfaces (like wg0
). These zones will prevent inbound access to each interface (except for a few commonly-used services like SSH and DHCP). As long as the endpoint always initiates WireGuard connections (as you would typically expect for an end-user device), you don’t need to adjust these rules to allow inbound WireGuard access.
You can assign NetworkManager connections to firewalld zones like the following:
$ nmcli connection show
NAME UUID TYPE DEVICE
System eth0 1dd9a779-d327-56e1-8454-c65e2556c12c ethernet eth0
wg0 1c047315-acdb-4f52-9432-277ebf58c1b0 wireguard wg0
$ sudo nmcli connection modify 'System eth0' connection.zone public
$ sudo nmcli connection modify wg0 connection.zone internal
Once you’ve assigned the zones, you can review which zones are active via the firewall-cmd --get-active-zones
command, and view the settings for each zone via the firewall-cmd --info-zone=[name]
command:
$ sudo firewall-cmd --get-active-zones
internal
interfaces: wg0
public
interfaces: eth0
$ sudo firewall-cmd --info-zone=public
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
$ sudo firewall-cmd --info-zone=internal
internal (active)
target: default
icmp-block-inversion: no
interfaces: wg0
sources:
services: dhcpv6-client mdns samba-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Service Endpoint
With a service endpoint, unlike an end-user endpoint, the endpoint is typically the recipient of connections, rather than the initiator. So while, like with an end-user endpoint, firewalld’s built-in public
zone is a good fit for the server’s physical network connections and the internal
zone provides a good fit for the server’s WireGuard interfaces, you’d typically also need to open up some additional service ports in those zones to allow inbound WireGuard access.
On a server with a WireGuard interface listening for external connections on port 51820
, and a webserver listening for connections tunneled through WireGuard on port 80
, you might run the following series of commands to configure firewalld:
$ sudo firewall-cmd --zone=public --add-port=51820/udp
success
$ sudo firewall-cmd --zone=public --add-interface=eth0
success
$ sudo firewall-cmd --zone=internal --add-service=http
success
$ sudo firewall-cmd --zone=internal --add-interface=wg0
success
This will prevent access to almost all of the server’s network services except through WireGuard, and prevent access even through WireGuard to all but a small set of the server’s network services.
Firewalld’s public
and internal
zones do allow access to a few additional network services by default (like SSH and DHCP) — you can view and remove them with the following series of commands:
$ sudo firewall-cmd --get-active-zones
internal
interfaces: wg0
public
interfaces: eth0
$ sudo firewall-cmd --info-zone=public
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: dhcpv6-client ssh
ports: 51820/udp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
$ sudo firewall-cmd --zone=public --remove-service=dhcpv6-client --remove-service=ssh
success
$ sudo firewall-cmd --info-zone=public
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services:
ports: 51820/udp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
$ sudo firewall-cmd --info-zone=internal
internal (active)
target: default
icmp-block-inversion: no
interfaces: wg0
sources:
services: dhcpv6-client http mdns samba-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
$ sudo firewall-cmd --zone=internal --remove-service=dhcpv6-client \
--remove-service=mdns --remove-service=samba-client --remove-service=ssh
success
$ sudo firewall-cmd --info-zone=internal
internal (active)
target: default
icmp-block-inversion: no
interfaces: wg0
sources:
services: http
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Tip
|
Once you have firewalld working the way you like, save your current firewalld configuration with the following command, so that the server will automatically apply it when it boots up:
|
See the How to Use WireGuard with Firewalld article for some more advanced examples of using firewalld with WireGuard.
Gateways
On WireGuard servers that forward connections to other servers, like the hub of a WireGuard hub-and-spoke network, or the site gateway in a WireGuard point-to-site network, the server’s firewalld configuration can become complicated fast. At that point, it’s easier just to use nftables to manage the server’s firewall than firewalld.
Nftables is kind of like the “2.0” version of the venerable iptables firewall tool. It’s a little more complicated than iptables to do one single thing (like to set up masquerading on a network interface, or forward a particular port), but it’s a generally easier to build and manage a server’s full set of firewall rules with nftables than with iptables.
WireGuard Hub
On a WireGuard server, you’d typically install the nftables
package from its OS package manager, configure its firewall ruleset via the /etc/nftables.conf
file (or /etc/sysconfig/nftables.conf
on Fedora-based distros), and apply the ruleset with the following command:
$ sudo systemctl restart nftables
The following nftables config file provides a good starter ruleset for a WireGuard hub:
#!/usr/sbin/nft -f flush ruleset define pub_iface = "eth0" define wg_iface = "wg0" define wg_port = 51820 table inet filter { chain input { type filter hook input priority 0; policy drop; # accept all loopback packets iif "lo" accept # accept all icmp/icmpv6 packets meta l4proto { icmp, ipv6-icmp } accept # accept all packets that are part of an already-established connection ct state vmap { invalid : drop, established : accept, related : accept } # drop new connections over rate limit ct state new limit rate over 1/second burst 10 packets drop # accept all DHCPv6 packets received at a link-local address ip6 daddr fe80::/64 udp dport dhcpv6-client accept # accept all SSH packets received on a public interface iifname $pub_iface tcp dport ssh accept # accept all WireGuard packets received on a public interface iifname $pub_iface udp dport $wg_port accept # reject with polite "port unreachable" icmp response reject } chain forward { type filter hook forward priority 0; policy drop; # forward all packets transiting the WireGuard network iifname $wg_iface oifname $wg_iface accept # reject with polite "host unreachable" icmp response reject with icmpx type host-unreachable } }
It blocks all connections to the server itself except for ICMP packets (Internet Control Message Protocol, used for network diagnostics and signaling), DHCPv6 client messages (needed if the server has an IPv6 address assigned via Dynamic Host Configuration Protocol), SSH connections, and WireGuard connections; and it allows unrestricted forwarding of connections through the WireGuard tunnel to and from the other WireGuard hosts connected to it.
Tip
|
Once you have your nftables config working the way you like, you can load it automatically on boot with the following command:
|
See the Hub and Spoke section of the How to Use WireGuard with Nftables article for an explanation of these rules, as well as examples of how to restrict which WireGuard connections are forwarded through the hub.
WireGuard Site Gateway
For a WireGuard server that provides access to a private site (or to the public Internet) from connected WireGuard clients, the following nftables config file makes for a good starter ruleset:
#!/usr/sbin/nft -f flush ruleset define pub_iface = "eth0" define wg_iface = "wg0" define wg_port = 51820 table inet filter { chain input { type filter hook input priority 0; policy drop; # accept all loopback packets iif "lo" accept # accept all icmp/icmpv6 packets meta l4proto { icmp, ipv6-icmp } accept # accept all packets that are part of an already-established connection ct state vmap { invalid : drop, established : accept, related : accept } # drop new connections over rate limit ct state new limit rate over 1/second burst 10 packets drop # accept all DHCPv6 packets received at a link-local address ip6 daddr fe80::/64 udp dport dhcpv6-client accept # accept all SSH packets received on a public interface iifname $pub_iface tcp dport ssh accept # accept all WireGuard packets received on a public interface iifname $pub_iface udp dport $wg_port accept # reject with polite "port unreachable" icmp response reject } chain forward { type filter hook forward priority 0; policy drop; # forward all packets that are part of an already-established connection ct state vmap { invalid : drop, established : accept, related : accept } # forward all packets from the WireGuard network to the site network iifname $wg_iface oifname $pub_iface accept # reject with polite "host unreachable" icmp response reject with icmpx type host-unreachable } } table inet nat { chain postrouting { type nat hook postrouting priority 100; policy accept; # masquerade all packets from the WireGuard network to the site network iifname $wg_iface oifname $pub_iface masquerade } }
Like the WireGuard Hub example in the previous section, this ruleset blocks all connections to the server itself except for ICMP packets, DHCPv6 client messages, SSH connections, and WireGuard connections; and it allows unrestricted forwarding of connections through the WireGuard tunnel from its WireGuard clients to the other hosts at the site. Additionally, it masquerades connections from its WireGuard clients to the site (ie substitutes its own site IP address in place of client IP addresses), ensuring that the other hosts at the site can return traffic back to the WireGuard clients without having to adjust any routing configuration at the site.
See the Point to Site section of the How to Use WireGuard with Nftables article for an explanation of these rules, as well as examples of how to restrict which WireGuard connections are forwarded to the site.