How to Build a WireGuard VPN

Yet another “build a WireGuard VPN” guide! This setup here is mostly automated with scripts and Infrastructure as Code. It is meant to be quick temporary VPN setup; you spin it up in a few minutes, do whatever and destroy after use. There’s only about 50 lines of bash to put everything together.

This guide is a little technical; guardrails not included.

Infrastructure

My setup is simply an AWS t2.micro EC2 instance created using CloudFormation. You can get the stack YML here.

The stack pulls the latest Ubuntu image, sets a static public IP address, sets up session manager, whitelists UDP port 51820, then runs the following setup script:

#!/bin/bash
apt update
apt install -y wireguard qrencode
cd /etc/wireguard/
# Generate server keys
wg genkey | tee private.key | wg pubkey > public.key
# Write config
NET=`lshw -C network | grep -oP '(?<=logical name: )\w+'`
cat <<EOF > wg0.conf
[Interface]
Address = 10.10.0.1/24
Address = fd86:ea04:1111::1/64
ListenPort = 51820
PostUp = sysctl -q -w net.ipv4.ip_forward=1
PostUp = sysctl -q -w net.ipv6.conf.all.forwarding=1
PostDown = sysctl -q -w net.ipv4.ip_forward=0
PostDown = sysctl -q -w net.ipv6.conf.all.forwarding=0
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o $NET -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o $NET -j MASQUERADE
PrivateKey = `cat private.key`
EOF
# Start Wireguard service on boot
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
view raw wg-setup.sh hosted with ❤ by GitHub

Lastly, it adds a wg-addclient executable which we will get to later.

To connect to the instance, use session manager. The URL is under “SSMURL” under the “Outputs” tab:

Just click on the link and you get remote access to the instance via your browser window.

If you don’t use AWS, you just need to run wg-setup.sh as root user in a publicly accessible Ubuntu server. Make sure to white list UDP port 51820 from any firewall.

This setup will cost you 0.01 USD per hour (us-east-1 region), or 0.24 USD a day; you can explore lighter instances for more cost savings by changing the “InstanceType” parameter of the stack.

I’ve also gotten this running with an Amazon ARM server (since they are having a free trial now for t4g.small until Dec 2023) with the following parameters to the CloudFormation stack:

  • InstanceType: t4g.small
  • LatestAmiId: /aws/service/canonical/ubuntu/server/jammy/stable/current/arm64/hvm/ebs-gp2/ami-id

Adding Clients

In the VPN server, execute wg-addclient as root user:

sudo -iu root
wg-addclient

Here is wg-addclient for your perusal:

#!/bin/bash
# Note: Must run as root!
cd /etc/wireguard/
# Get next available internal IP address
IP=$((`tac wg0.conf | grep -m1 -oP "[0-9]+(?=/32)" || echo 1` + 1))
# Generate client key
CLIENT_PRIVATE=`wg genkey`
CLIENT_PUBLIC=`wg pubkey <<< $CLIENT_PRIVATE`
# Append to server config
cat <<EOF >> wg0.conf
[Peer]
PublicKey = $CLIENT_PUBLIC
AllowedIPs = 10.10.0.$IP/32, fd86:ea04:1111::$IP/128
EOF
# Output client config and render QR
CONF=`mktemp`
trap "rm $CONF" EXIT
cat <<EOF | tee $CONF
[Interface]
PrivateKey = $CLIENT_PRIVATE
Address = 10.10.0.$IP/32
DNS = 8.8.8.8
[Peer]
PublicKey = `cat public.key`
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = `curl -s ifconfig.me`:51820
EOF
cat $CONF | qrencode -t ansiutf8
# Reload the Wireguard service
systemctl reload wg-quick@wg0
view raw wg-addclient.sh hosted with ❤ by GitHub

You will get an output that looks like this:

To add more clients, simply rerun wg-addclient.

Adding Client in iOS

Download the official WireGuard iOS app. Tap the “+” icon and select “Create from QR code” then scan the QR code generated from wg-addclient.

Follow the instructions that follow.

Adding Client in Windows

Download the official WireGuard Windows app. Go to “Add Tunnel” > “Add empty tunnel…”:

Give the tunnel a name and copy-paste the text contents from wg-addclient and click “Save”

Monitor Clients

Use the wg command:

# wg
interface: wg0
  public key: XSYfQReJui9ZYaLykAifNCaA5dn6MIHJjckl/nRkV0Y=
  private key: (hidden)
  listening port: 51820

peer: YEMGteQuySmdXCvwBlqIBIvGX/p4D32LbfE21nYLkm8=
  endpoint: 93.184.216.34:54114
  allowed ips: 10.10.0.3/32, fd86:ea04:1111::3/128
  latest handshake: 14 seconds ago
  transfer: 106.50 KiB received, 660.57 KiB sent

peer: DvLpEHPP5cFoX4nlFg0Jehq2RLZ0dBua/tEtRpXQvTI=
  endpoint: 93.184.216.34:53107
  allowed ips: 10.10.0.2/32, fd86:ea04:1111::2/128
  latest handshake: 1 minute, 4 seconds ago
  transfer: 721.85 KiB received, 3.46 MiB sent

In the example above, I have 2 clients (my phone and my laptop) connected. Your devices can only be referenced by their public key – there’s no built-in way to put a name to these devices.

Removing Clients

Internally, all wg-addclient does is append to /etc/wireguard/wg0.conf something like:

[Peer]
PublicKey = DvLpEHPP5cFoX4nlFg0Jehq2RLZ0dBua/tEtRpXQvTI=
AllowedIPs = 10.10.0.2/32, fd86:ea04:1111::2/128

And so to remove a client you remove the corresponding entry from wg0.conf and reload the WireGuard service:

systemctl reload wg-quick@wg0

Destroying the VPN

As mentioned earlier, this is meant to be a temporary VPN setup. Leaving this running long-term will probably cost you more and you should consider just paying for a VPN service.

Destroying the VPN is as easy as deleting the CloudFormation stack. All the corresponding resources (EC2, security group, EIP, IAM etc) will be cleanly removed and no further charges will be incurred.

Closing Remarks

  • Though I haven’t tried it, you could get WireGuard running with a nice admin UI similar to OpenVPN. See “Web UIs for WireGuard That Make Configuration Easier” for some good picks, though they will likely conflict with the setup here.
  • There’s a fully featured Wireguard install script by Nyr that works for multiple Linux distributions; has like 10x the amount of bash code.
  • This is not meant for production use.
  • You can probably only add 200 clients (why would you need so many??)
  • I don’t think any of the IPv6 stuff here is really needed; the EC2 instance I spun up does not even have an IPv6 address.
  • None of the keys you see here are going to be usable once this post is up.
  • You should not share your generated keys or VPN QR codes on the internet.
  • You should thoroughly understand any script you execute from some random blogger on the internet 😉.

References: