How to Tunnel Traffic With WireGuard Forwarding

Edward Wibowo,

Imagine the following scenario: you want to host a service from a local server of yours to the internet. Connecting to https://service.example.com from any device should access the service. However, there is one slight problem: you don’t have the means to port forward from the local server’s network. For example, this scenario may arise if you don’t have access to the local server’s router (e.g. in a hotel or dormitory).

This problem can be solved by establishing a tunnel between the local server and an external (public-facing) server. Through forwarding traffic with WireGuard, a tunnel can be established easily and securely.

Architecture

WireGuard Forwarding Architecture

The diagram above illustrates a client accessing the service on the local server by interfacing with the external server. This way, the local service is accessible without exposing the local server’s network to the internet. The external server acts as a proxy sitting between the client and the local server.

Why WireGuard?

First and foremost, WireGuard is a communication protocol for VPNs. So why use WireGuard for tunneling traffic?

One benefit of using WireGuard is scalability. A single service can be tunneled to multiple servers as long as the server is a WireGuard peer. So, if one external server goes down, the service can still be accessible from other peer servers.

Furthermore, using WireGuard also brings about its traditional benefits: speed, security, and simplicity.

Requirements

The architecture requires access to an external server, which will serve as the proxy server. The server should have a public IP with port forwarding capabilities, so using a cheap VPS is a good option.

You will also need to have a working WireGuard tunnel connecting the two servers. Consider following the official WireGuard quick start instructions to learn how to do so.

Routing Traffic

The following instructions will assume a few properties of the WireGuard network. Feel free to modify the values to your liking (just ensure that you reflect these modifications in the configuration files):

PropertyValue
Domain pointing to external server public IPexample.com
WireGuard subnet10.1.1.0/24
External server WireGuard IP10.1.1.1/32
Local server WireGuard IP10.1.1.2/32
Service IP and port127.0.0.1:3000

The goal is to get any traffic reaching service.example.com (pointing to the external server) to get forwarded to 127.0.0.1:3000 on the local server.

Local Server Peer Settings

Firstly, the local server’s peer settings should be adjusted:

# Local server's WireGuard configuration
[Peer]
PublicKey = {EXTERNAL_PUBLIC_KEY}
Endpoint = example.com:{EXTERNAL_WIREGUARD_PORT}
PersistentKeepalive = 25
AllowedIPs = 10.1.1.0/24
  • PublicKey: External server’s public WireGuard key.
  • Endpoint: Domain (or IP) of the external server.
  • PersistentKeepalive: How often (in seconds) to send an authenticated empty packet to the external server.
  • AllowedIPs: Make WireGuard facilitate traffic across the WireGuard subnet.

Without PersistentKeepalive, the WireGuard tunnel will only be active while the service is being accessed; however, the tunnel should always be ready to accept any incoming (or outgoing) traffic. Thus, there needs to be constant (and periodic) communication between the two servers.

Destination Network Address Translation

Next, a packet rule should be added to redirect incoming traffic from the WireGuard tunnel to the service. This can be done using DNAT (Direct Network Address Translation) and the iptables command:

iptables -t nat \
    -A PREROUTING \
    -d 10.1.1.2 \
    -p tcp \
    --dport 3000 \
    -j DNAT \
    --to-destination 127.0.0.1:3000
  • -t nat: Specify nat table.
  • -A PREROUTING: Append to the PREROUTING chain.
  • -d 10.1.1.2: Select all packets that are heading towards 10.1.1.2 (the local server’s WireGuard IP).
  • -p tcp: Use TCP protocol.
  • --dport 3000: Select packets that are headed towards port 3000.
  • -j DNAT: Specify that the selected packets should undergo DNAT.
  • --to-destination 127.0.0.1:3000: Reroute all selected packets to 127.0.0.1:3000 (the local service).

To automatically append (and delete) the rules when the WireGuard interface is toggled, set PreUp and PreDown in the local server’s WireGuard configuration:

[Interface]
Address = 10.1.1.2/32
PrivateKey = {LOCAL_PRIVATE_KEY}
ListenPort = {EXTERNAL_WIREGUARD_PORT}
# ...

PreUp = iptables -t nat -A PREROUTING -d 10.1.1.2 -p tcp --dport 3000 -j DNAT --to-destination 127.0.0.1:3000

#                         👇 Remove rules with -D flag
PostDown = iptables -t nat -D PREROUTING -d 10.1.1.2 -p tcp --dport 3000 -j DNAT --to-destination 127.0.0.1:3000

With this, the rules will be added upon executing wg-quick up and removed upon executing wg-quick down.

Conclusion

If everything was set up correctly, the local service should be accessible from the external server through 10.1.1.2:3000. To make this accessible to anyone on the internet, a reverse proxy can be used to point service.example.com to 10.1.1.2:3000.