WireGuard Point to Site Configuration

This article will cover how to set up two WireGuard peers in a Point to Site topology. This is the configuration you’d use when you want to connect a single endpoint running WireGuard to another host running WireGuard that can route to packets from the first endpoint on to other endpoints.

In many cases this is done to allow a remote endpoint access to a LAN (Local Area Network); but you can also use the same approach to configure an endpoint to access a WAN (Wide Area Network), like the Internet.

In the specific scenario I’ll cover for this article, we’ll have an end-user workstation running WireGuard, which I’ll call “Endpoint A”, on one LAN; and another host running WireGuard in a different LAN, configured as a router for that LAN. I’ll call that other host “Host β”, and its LAN “Site B”. Within Site B, we’ll have another endpoint, which I’ll call “Endpoint B”, running an HTTP server listening on port 80.

The Internet is between Endpoint A and Site B. Endpoint A is behind NAT (Network Address Translation) and a firewall that allows only established connections through. Site B is also behind NAT and a firewall, but its NAT + firewall allows port 51822 from the Internet to be forwarded on to Host β. We’ll use this port, 51822, for WireGuard on Host β. The firewall within Site B allows Host β to initiate new connections to Endpoint B on port 80.

The diagram below illustrates this scenario:

WireGuard point-to-site scenario

By connecting Endpoint A and Host β in a WireGuard VPN (Virtual Private Network), Endpoint A will be able to access the HTTP server running on Endpoint B as if Endpoint A were in the same LAN and Endpoint B. Endpoint B has an IP address of 192.168.200.22 within Site B, so a user using Endpoint A will be able to access the HTTP server on Endpoint B simply by navigating to http://192.168.200.22 in her web browser.

This article will walk through how to install and configure WireGuard on Endpoint A and Host β, as well as how to configure Host β to allow it to route packets from Endpoint A to other endpoints within Site B.

Before we start, take note of the IP addresses shown in the above diagram: In this scenario, Endpoint A’s IP address, from the perspective of the Internet, is 198.51.100.1; but from the perspective of its own LAN (Site A), it’s 192.168.1.11; and from the perspective of the WireGuard VPN that we’ll build, it’s 10.0.0.1. Endpoint B is not accessible from the Internet; but on its own LAN (Site B), its IP address is 192.168.200.22.

Host β’s IP address, from the perspective of the Internet, is 203.0.113.2; but from the perspective of its own LAN (Site B), it’s 192.168.200.2; and from the perspective of the WireGuard VPN we’ll build, it’s 10.0.0.2. And from the perspective of Endpoint B (or any other endpoints in Site B), Endpoint A’s packets will appear to come from Host β — so from the perspective of Endpoint B, Endpoint’s A IP address will be the same as the IP address for Host β in the Site B LAN: 192.168.200.2.

To make this scenario work, these are the steps we’ll follow:

  1. Install WireGuard
  2. Generate WireGuard Keys
  3. Configure WireGuard on Endpoint A
  4. Configure WireGuard on Host B
  5. Configure Routing on Host B
  6. Start Up WireGuard
  7. Test Out the Tunnel
  8. Basic Troubleshooting
  9. Extra: Configure Firewall on Host B

Install WireGuard

Install WireGuard on both Endpoint A and Host β by following the installation instructions for the appropriate platform on the WireGuard Installation page. You can verify that you’ve installed WireGuard successfully by running wg help on both hosts.

Generate WireGuard Keys

Next, generate two WireGuard keys, one for Endpoint A, and one for Host β. A WireGuard key (also known as a “key pair”) is composed of two parts, the “private key” (also known as the “secret key”), and the “public key”. The magic of this kind of crypto (known as “public-key cryptography”) is that it’s trivial to compute the public key if you know the private key, but practically impossible to compute the private key if all you know is the public key.

While you don’t have to generate the keys on the hosts, generating a host’s key on the host itself often is the best way to keep its private key secure — that way the private key never leaves the host. If you generate your keys outside of the host, be very careful with the private keys, as WireGuard’s security depends entirely on keeping the private keys a secret.

Run the following commands to generate a new key pair for Endpoint A:

$ wg genkey > endpoint-a.key
$ wg pubkey < endpoint-a.key > endpoint-a.pub

And similar commands to generate a new key pair for Host β:

$ wg genkey > host-b.key
$ wg pubkey < host-b.key > host-b.pub

This will generate four files: endpoint-a.key, endpoint-a.pub, host-b.key, and host-b.pub. The *.key files contain the private keys, and the *.pub files contain the public keys. The content of each will be 44 characters of Base64-encoded text:

$ cat endpoint-a.key
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
$ cat endpoint-a.pub
/TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=

$ cat host-b.key
ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
$ cat host-b.pub
fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=

You don’t actually need to keep these files around — the content of each will be used in the WireGuard configuration files we build for Endpoint A and Host β in the next sections. The private key for a host goes in the host’s own configuration file; and its public key goes in the configuration file of every other host you want to connect it to. Each end of a connection must be pre-configured with the other end’s public key in order for WireGuard to establish the connection.

Configure WireGuard on Endpoint A

Now let’s configure Endpoint A (the “client” side of the connection). On Endpoint A, create a new file at /etc/wireguard/wg0.conf with the following content:

# /etc/wireguard/wg0.conf

# local settings for Endpoint A
[Interface]
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
Address = 10.0.0.1/32
ListenPort = 51821

# remote settings for Host β
[Peer]
PublicKey = fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
Endpoint = 203.0.113.2:51822
AllowedIps = 192.168.200.0/24

Set the file’s owner to root, and its permissions to 600 (owner can read+write; everyone else is denied access — the file contains the endpoint’s private key, which must be kept secret).

Let’s go through the configuration, setting-by-setting:

Interface.PrivateKey
This is Endpoint A’s private key — replace this value with the actual private key you generated for Endpoint A. This value is secret, and this is the only place where it should live.
Interface.Address

This is Endpoint A’s IP address inside the WireGuard VPN we’re building — replace this value with a suitable value for your network. You can use any address you want for this, but the address should be within the “Private-Use” IPv4 or “Unique-Local” IPv6 address space (like 10.0.0.0/8 — see RFC 6890 for all available address blocks), and should not collide with any other private subnets to which the endpoint itself or any of its peers can connect.

With a Point to Site topology, this setting in Endpoint A’s configuration file should match exactly the Peer.AllowedIps setting in Host β’s configuration file (discussed in the next section).

This setting isn’t used by WireGuard directly — it’s only used by the wg-quick helper service when it sets up a virtual network interface for WireGuard. In order for network packets to be routed correctly to and from this endpoint when they’re outside of the WireGuard tunnel, they need to use the IP address you set here.

While you can configure multiple IP addresses for this setting, unless you have a special use-case, you should just use a single IP address (in the form of a /32 block with an IPv4 address, or a /64 block with an IPv6 address).

Interface.ListenPort

This is Endpoint A’s WireGuard port. Endpoint A must be able to receive UDP traffic for established connections on this port from the Internet (or wherever the traffic of the other WireGuard peers with which it will communicate comes from).

If you omit this setting, WireGuard will select a new random, unused port in the in the operating system’s ephemeral port range (which may range from 1024 to 65535, depending on operating system) every time it starts up.

Peer.PublicKey
This is Host β’s public key — replace this value with the actual public key you generated for Host β. This value is not secret; however, it is a globally-unique identifier for Host β.
Peer.Endpoint

This is Host β’s publicly-facing IP address (and port) — the IP address and port Endpoint A will use to connect over the Internet to Host β to set up the WireGuard tunnel. Replace this value with the actual IP address that you would normally use to connect to Host β from Endpoint A (and suffix it with the actual WireGuard port you’ll use for Host β, if it’s not WireGuard’s default port of 51820).

Host β must be able to accept new UDP connections from the Internet at this address and port; and Endpoint A must be able to send UDP traffic over the Internet to it at this address and port.

Peer.AllowedIps
This is Site B’s subnet block — replace this value with the actual block of IP addresses Site B uses. If Host β can route to multiple subnets within Site B, you can specify each block of IP addresses separated by commas (like 192.168.200.0/24,192.168.202.64/26,192.0.2.48/28) for this setting, or you can just specify this setting multiple times, one for each CIDR (Classless Inter-Domain Routing) block.

Configure WireGuard on Host B

Now let’s configure Host β (the “server” side of the connection). On Host β, create a new file at /etc/wireguard/wg0.conf with the following content:

# /etc/wireguard/wg0.conf

# local settings for Host β
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIps = 10.0.0.1/32

Set the file’s owner to root, and its permissions to 600 (owner can read+write; everyone else is denied access — the file contains the host’s private key, which must be kept secret).

Let’s go through the configuration, setting-by-setting:

Interface.PrivateKey
This is Host β’s private key — replace this value with the actual private key you generated for Host β. This value is secret, and this is the only place where it should live.
Interface.Address

This is Host β’s IP address inside the WireGuard VPN we’re building — replace this value with a suitable value for your network. You can use any address you want for this, but the address should be within the “Private-Use” IPv4 or “Unique-Local” IPv6 address space (like 10.0.0.0/8 — see RFC 6890 for all available address blocks), and should not collide with any other private subnets to which the endpoint itself or any of its peers can connect.

This setting isn’t used by WireGuard directly — it’s only used by the wg-quick helper service when it sets up a virtual network interface for WireGuard. In order for network packets to be routed correctly to and from this host when they’re outside of the WireGuard tunnel, they need to use the IP address you set here.

In a Point to Site topology, this setting on the site’s WireGuard host isn’t very important — typically you want route packets through the site’s WireGuard host (and on to other endpoints in the site), not to the host itself. So you just need to make sure you pick an IP address that isn’t going to collide with any other IP addresses the host can route to.

While you can configure multiple IP addresses for this setting, unless you have a special use-case, you should just use a single IP address (in the form of a /32 block with an IPv4 address, or a /64 block with an IPv6 address).

Interface.ListenPort

This is Host β’s WireGuard port. Host β must be able to receive UDP traffic for new connections on this port from the Internet (or wherever the traffic of the other WireGuard peers with which it will communicate comes from).

With a Point to Site topology, this setting in Host β’s configuration file should be the same as the port in the Peer.Endpoint setting of Endpoint A’s configuration file (discussed in the section above).

Peer.PublicKey
This is Endpoint A’s public key — replace this value with the actual public key you generated for Endpoint A. This value is not secret; however, it is a globally-unique identifier for Endpoint A.
Peer.AllowedIps

This is Endpoint A’s IP address inside the WireGuard VPN we’re building — replace this value with a suitable value for your network. You can use any address you want for this, but the address should be within the “Private-Use” IPv4 or “Unique-Local” IPv6 address space (like 10.0.0.0/8 — see RFC 6890 for all available address blocks), and should not collide with any other private subnets to which the endpoint itself or any of its peers can connect.

With a Point to Site topology, this setting in Host β’s configuration file should match exactly the Interface.Address setting in Endpoint A’s configuration file (discussed in the section above).

While you can configure multiple IP addresses for this setting, unless you have a special use-case, with a Point to Site topology you should just use a single IP address (in the form of a /32 block with an IPv4 address, or a /64 block with an IPv6 address).

You will notice that we omitted the Peer.Endpoint setting from Host β’s configuration file that we used in Endpoint A’s configuration file. In this scenario, because Endpoint A initiates all connections to Site B (like when the end user makes HTTP requests from Endpoint A to Endpoint B), Host β doesn’t need to know how to initiate a connection to Endpoint A — and so it doesn’t need a pre-configured value for Peer.Endpoint.

Configure Routing on Host B

If Host β is already set up as the Internet gateway for Site B (or the gateway for Site B to a subnet that includes the WireGuard VPN we’re setting up — in this example, just 10.0.0.1/32), you don’t need to do anything extra for WireGuard; just skip on to the next section.

Otherwise, we need to configure Host β to route packets from Endpoint A to Endpoint B (and back). When a packet sent from Endpoint A destined for Endpoint B comes through the WireGuard tunnel we’ve configured between Endpoint A and Host β, and into the wg0 interface on the Host β end, its source address will be 10.0.0.1, and its source port will be an ephemeral port chosen by Endpoint A, like 49999; and its destination address will be 192.168.200.22, and destination port 80.

The destination address and port are good the way they are — Host β just needs to be configured to allow it to forward packets like this on to other hosts. But the source address is a problem — Endpoint B won’t know how to send packets back to Endpoint A at 10.0.0.1. So we need to use SNAT (Source Network Address Translation) on Host β to re-write the packet’s source address to use Host β’s own address on the Site B LAN (192.168.200.2). Endpoint B will know how to send packets back to this address (and Host β will know to forward those packets back to Endpoint A — with SNAT it will re-write the source port of the original packets, 49999 in this example, to some other ephemeral port that it will remember is associated with Endpoint A, like 50123).

So, assuming Host β is a Linux host, we need to configure 2 things on it:

  1. Turn on IP forwarding
  2. Set up iptables to apply IP masquerading to packets emerging from the wg0 interface

IP masquerading is just a simplified form of SNAT, where iptables automatically chooses the source address to re-write packets to (which in our scenario will always be 192.168.200.2). If you don’t already have iptables installed on Host β (try running sudo iptables -L to check), install it now.

While you can also use other services to do this, the most expedient way for us to turn on IP forwarding and IP masquerading is to have the wg-quick service do it when we bring the WireGuard interface up. To do it this way, update the /etc/wireguard/wg0.conf file on Host β to add several Interface.PreUp and Interface.PostDown settings like the following:

# /etc/wireguard/wg0.conf

# local settings for Host β
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# IP forwarding
PreUp = sysctl -w net.ipv4.ip_forward=1
# IP masquerading
PreUp = iptables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x30
PreUp = iptables -t nat -A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
PostDown = iptables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x30
PostDown = iptables -t nat -D POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIps = 10.0.0.1/32

If you’re using IPv6 addresses for your WireGuard VPN, use these settings instead:

PreUp = sysctl -w net.ipv6.conf.all.forwarding=1
PreUp = ip6tables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x30
PreUp = ip6tables -t nat -A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
PostDown = ip6tables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x30
PostDown = ip6tables -t nat -D POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE

The first PreUp setting turns on IP forwarding. Note that IP forwarding is a potentially dangerous setting to turn on if you don’t have appropriate firewall rules in place in front of the host (or as part of the host’s own firewall) — with it on, the host will blindly try to forward on any packets it receives that are destined for other hosts (allowing a malicious actor with network access to the host to access any other hosts that the host itself can access).

The second PreUp setting adds an iptables rule to mark all packets emerging from the wg0 interface with a value of 0x30. The third PreUp setting adds an iptables rule to apply IP masquerading to all packets with this 0x30 mark before they’re forwarded off to their destination (as long as that destination is not back out through the wg0 interface again). Note that 0x30 is an arbitrarily-chosen value, meaning nothing to anyone other than these two iptables rules — any other value would work just as well.

The two PostDown settings delete the iptables rules added by the PreUp settings. The PostDown iptables settings should match the corresponding PreUp settings exactly, except the PostDown ones use the -D (delete) flag instead of the -A (append) flag.

Start Up WireGuard

On both Endpoint A and Host β, start the wg-quick service. On Linux with systemd, run the following commands:

$ sudo systemctl enable wg-quick@wg0.service
$ sudo systemctl start wg-quick@wg0.service

On systems without systemd, run this command instead:

$ sudo wg-quick up /etc/wireguard/wg0.conf

Either way, starting up the wg-quick service will set up a WireGuard network interface named wg0 on the host, and configure some routing rules to route packets destined for any IP address listed in the Peer.AllowedIPs setting(s) of the /etc/wireguard/wg0.conf file to go out the wg0 interface.

Note that the name wg0 is just the standard convention for your first WireGuard interface. You can create as many WireGuard interfaces as you like, and name them however you like. For example, you could create another configuration file named /etc/wireguard/mytunnel.conf, and start it up with the command wg-quick up /etc/wireguard/mytunnel.conf; this would create a new WireGuard interface named mytunnel.

But for now, if you ran wg-quick up directly, you’ll see output like the following:

$ sudo wg-quick up /etc/wireguard/wg0.conf
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.0.0.1/32 dev wg0
[#] ip link set mtu 8921 up dev wg0
[#] ip -4 route add 10.0.0.2/32 dev wg0

This output shows the routing rules that wg-quick set up for you automatically.

If you ran systemctl start instead, you can see the same output by running the journalctl tool, like so:

$ journalctl -u wg-quick@wg0.service
systemd[1]: Starting WireGuard via wg-quick(8) for wg0...
wg-quick[271288]: [#] ip link add wg0 type wireguard
wg-quick[271288]: [#] wg setconf wg0 /dev/fd/63
wg-quick[271288]: [#] ip -4 route add 10.0.0.1/32 dev wg0
wg-quick[271288]: [#] ip link set mtu 8921 up dev wg0
wg-quick[271288]: [#] ip -4 address add 10.0.0.2/32 dev wg0
systemd[1]: Started WireGuard via wg-quick(8) for wg0.

Test Out the Tunnel

On Endpoint B, start up a webserver on port 80 (or any other port, like 8080). If you don’t have a webserver handy, a dead-simple substitute is to use the http.server module of Python 3, like the following, running as root to listen on port 80:

$ sudo python3 -m http.server 80

Or run it as a regular user to listen on any port above 1023 (like port 8080):

$ python3 -m http.server 8080

The http.server module will serve the directory listing of and files in your current directory. A somewhat safer version of this is to create an empty directory in your current directory, and serve that instead:

$ mkdir dummydir && cd dummydir
$ python3 -m http.server 8080

On Endpoint A, use curl (or a regular webrowser) to request a page from the webserver running on Endpoint B port 80:

$ curl 192.168.200.22

Or if the webserver is running on port 8080 (or some other port), specify that port explicitly:

$ curl 192.168.200.22:8080

If you see any HTML output from this, your WireGuard tunnel works!

Basic Troubleshooting

If curl hangs, or you see an error like like Connection refused or No route to host, you may have neglected to turn on IP forwarding for Host β. Run this command on Host β to double check:

$ sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

Or, if you’re using IPv6 addresses, run the IPv6 variant:

$ sudo sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 0

If the result is 0, you need to turn on IP forwarding (see the Configure Routing on Host B section above). If the result is 1, IP forwarding is on.

The next thing to check is to make sure Host β is performing IP masquerading for Endpoint A. Run this command on Host β to list out all your iptables tables rules:

$ sudo iptables-save -c

If you don’t have any iptables rules in place, the result will be blank. What you should see instead is something like this:

$ sudo iptables-save -c
# Generated ...
*mangle
:PREROUTING ACCEPT [123:12345]
:INPUT ACCEPT [123:12345]
:FORWARD ACCEPT [12:123]
:OUTPUT ACCEPT [123:12345]
:POSTROUTING ACCEPT [123:12345]
[12:123] -A PREROUTING -i wg0 -j MARK --set-xmark 0x30/0xffffffff
COMMIT
# Completed ...
# Generated ...
*nat
:PREROUTING ACCEPT [12:123]
:INPUT ACCEPT [12:123]
:OUTPUT ACCEPT [12:123]
:POSTROUTING ACCEPT [12:123]
[12:123] -A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
COMMIT
# Completed ...
# Generated ...
*filter
:INPUT ACCEPT [123:12345]
:FORWARD ACCEPT [12:123]
:OUTPUT ACCEPT [123:12345]
COMMIT
# Completed ...

If you set up IP masquerading like the Configure Routing on Host B section above suggested, you should have this rule under the *mangle table:

-A PREROUTING -i wg0 -j MARK --set-xmark 0x30/0xffffffff

And this rule under the *nat table:

-A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE

If you don’t have a rule with a MASQUERADE or SNAT target under your *nat table, Endpoint B isn’t going to be able to send packets back to Endpoint A. Try following the Configure Routing on Host B section above to set up IP masquerading.

The numbers in brackets (like [123:12345]) are the packet and byte count processed by each rule. If you see [0:0] for the -j MAQUERADE rule, it means no packets have matched it (ie no packets have been marked with 0x30 and forwarded directly out of one of the host’s physical network interfaces). And if you see [0:0] for the -j MARK rule, it means no packets have come in the host’s wg0 interface.

If no packets have come in the host’s wg0 interface, you likely have some other firewall rules or network configuration blocking the WireGuard tunnel itself from being set up. Try running sudo wg on both Endpoint A and Host β, to see if the WireGuard interface on both is up and configured as you expect.

On Host β, you’ll probably see output like this:

$ sudo wg
interface: wg0
  public key: fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
  private key: (hidden)
  listening port: 51822

peer: /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
  allowed ips: 10.0.0.1/32

If Endpoint A had successfully connected, this display would include a “latest handshake” field for the peer, as well as a “transfer” field indicating some data had been received. The above output indicates that Endpoint A has not successfully connected yet.

On Endpoint A, you’ll probably see output like this:

$ sudo wg
interface: wg0
  public key: /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
  private key: (hidden)
  listening port: 51821

peer: fE/wdxzl0klVp/IR8UcaoGUMjqaWi3jAd7KzHKFS6Ds=
  endpoint: 203.0.113.2:51822
  allowed ips: 192.168.200.0/24
  transfer: 0 B received, 123 B sent

If the “transfer” field indicates some data has been sent to Host β, but none has been received (as it does above), it likely means packets are being blocked or misdirected somewhere between Endpoint A and Host β. If this is the case for you, you need to fiddle with your firewalls or other network configuration until they allow Endpoint A to send UDP packets to Host β via the IP address and port configured in Endpoint A’s Peer.Endpoint setting (in this example, it’s 203.0.113.2:51822).

Extra: Configure Firewall on Host B

If you already have some basic firewall rules set up on Host β itself, you don’t need to do anything else to your WireGuard configuration. You may want to add additional rules to your firewall specifically for restricting the forwarding of WireGuard traffic, but you don’t have to.

If you don’t already have some basic rules in place, here are a few simple iptables rules that you can use to reject unexpected WireGuard traffic from accessing local sockets on Host β itself, and to reject any WireGuard traffic from being forwarded to an unexpected destination.

First, bring down the WireGuard interface on Host β with wg-quick or systemctl:

$ sudo wg-quick down /etc/wireguard/wg0.conf
$ # or alternately:
$ sudo systemctl stop wg-quick@wg0.service

Then edit the /etc/wireguard/wg0.conf file to add several new Interface.PreUp and Interface.PostDown settings, like the following:

# /etc/wireguard/wg0.conf

# local settings for Host β
[Interface]
PrivateKey = ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBFA=
Address = 10.0.0.2/32
ListenPort = 51822

# IP forwarding
PreUp = sysctl -w net.ipv4.ip_forward=1
# IP masquerading
PreUp = iptables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x30
PreUp = iptables -t nat -A POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE
PostDown = iptables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x30
PostDown = iptables -t nat -D POSTROUTING ! -o wg0 -m mark --mark 0x30 -j MASQUERADE

# firewall local host from wg peers
PreUp = iptables -A INPUT -i wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PreUp = iptables -A INPUT -i wg0 -j REJECT
PostDown = iptables -D INPUT -i wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -D INPUT -i wg0 -j REJECT
# firewall other hosts from wg peers
PreUp = iptables -A FORWARD -i wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PreUp = iptables -A FORWARD -i wg0 -m state --state NEW -d 192.168.200.22 -p tcp --dport 80 -j ACCEPT
PreUp = iptables -A FORWARD -i wg0 -j REJECT
PostDown = iptables -D FORWARD -i wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -m state --state NEW -d 192.168.200.22 -p tcp --dport 80 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j REJECT

# remote settings for Endpoint A
[Peer]
PublicKey = /TOE4TKtAqVsePRVR+5AA43HkAK5DSntkOCO7nYq5xU=
AllowedIps = 10.0.0.1/32

Then bring the interface back up again with wg-quick or systemctl:

$ sudo wg-quick up /etc/wireguard/wg0.conf
$ # or alternately:
$ sudo systemctl start wg-quick@wg0.service

This updated configuration will cause wg-quick on Host β to add five new iptables rules before it brings up the WireGuard interface, and remove the same rules after it brings the interface down.

The first two rules (added to the INPUT chain) apply to traffic destined for Host β itself:

iptables -A INPUT -i wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i wg0 -j REJECT

The first rule will accept all packets for already-established connections (ie connections that Host β initiated) coming through the WireGuard tunnel; and the second rule will reject anything else coming through the tunnel destined for Host β.

The last three rules (added to the FORWARD chain) apply to traffic forwarded from Endpoint A to Site B:

iptables -A FORWARD -i wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i wg0 -m state --state NEW -d 192.168.200.22 -p tcp --dport 80 -j ACCEPT
iptables -A FORWARD -i wg0 -j REJECT

Similar to the rules for the INPUT chain, the first and third of the rules for the FORWARD chain allow already-established connections, but reject new connections.

The second rule, however, will allow new TCP connections to be forwarded to port 80 of Endpoint B (192.168.200.22 in Site B). If you want to allow access to a different port of Endpoint B other than port 80 (like 8080), specify that port instead of 80. If you want to allow access to multiple ports of Endpoint B, adjust the rule slightly to use the multiport match directive with the --dports flag (note the trailing s in --dports) — like, for example, to allow both port 80 and 443:

iptables -A FORWARD -i wg0 -m state --state NEW -d 192.168.200.22 -p tcp -m multiport --dports 80,443 -j ACCEPT

Whenever you make changes to these rules, make sure you make the exact same change to both the PreUp setting and its corresponding PostDown setting (the PreUp and PostDown rules should be the same, just with a -A flag for PreUp and -D flag for PostDown). And if you use IPv6 addresses in your WireGuard VPN, substitute the ip6tables command for iptables in everything above.

Also note that when you’re fiddling with PreUp and PostDown settings, it’s best to bring the interface down first, make your configuration changes, and then bring the interface back up again (rather than making the configuration changes while the interface is still up, and then trying to restart afterward). The reason is that most of the time, your “Up” and “Down” settings are meant to be symmetrical (like start up a service with PreUp and shut it down with PostDown, or add a firewall rule with PreUp and remove it with PostDown) — so if you change a PostDown rule while your WireGuard interface is still running, you might inadvertently leave a stray service running or firewall rule in place from the previous configuration version (which you’ll have to track down and clean up manually).