Tommi's Scribbles
How to Create a Personal VPN on AWS with WireGuard

- Published on 2022-01-15
- A quick read.
There are multiple guides online for setting up a personal VPN on AWS with WireGuard. Why write another? My experience found out you can't find all the information you need in a single place, but instead need to go here and there to get a working setup. To remedy this, here are the steps to get a WireGuard VPN running on AWS. Modify it to your needs
Set up the cloud
First off, I am not going to tell you how to create an AWS account, IAM account there and so forth. If you can't sort it out on your own, then this guide is definitely not for you. I also won't hold your hand through the basic cloud things, like spinning up the instance.
The reason is simple: I don't know what kind of instance you want. If you want to go stay the free tier (mine expired), you will want a t2.micro. If you plan on having your whole family and friends use the VPN, you will probably want something more performant. Or maybe you want to have multiple locations and use an ALB and automatically spin up and down the instances. That stuff is for you to figure out.
Minimum requirements
That said, the minimum you need is an AWS account with access to VPC and EC2 resources. I highly recommend picking Debian as the OS. This is because in my testing, WireGuard was the easiest to install on an a-series EC2 (ARM) using Debian. The EC2 instance type I used was an a1.large spot instance. This might be overkill as WireGuard is not a massive resource hog. However, I wanted to make sure if I grant family or friends access too, that the performance won't tank.
Region choice
For the region, I picked a location that is central to the area I usually travel to have a good latency from everywhere. Nothing prevents from adding one instance per region and just creating multiple configs, kind of how all the VPN services have a country selection.
EBS
For the EBS, I picked a small 8GB drive since I don't expect to need more than that. You could possibly go even lower, but if you want to do logging or such it might be nice to have a bit of a buffer. I went with gp3 type, as IIRC it has the lowest cost. It's not like the VPN needs tons of IOPS.
VPCs
I created a new VPC, security group, subnets, and all for the VPN. But you could also use a pre-existing one as well. Do note, that if you use the default VPC subnet it has some different options by default compared to when you create a new VPC. But if you match the rules to the ones below, you should be all good even with the default one.
Internet Gateway and Elastic IP
The VPC needs an Internet Gateway and an Elastic IP as additional items. This is again the minimum for a working setup. Assign the elastic IP to the EC2.
Once you have the resources ready, configure the VPC resources in the following way:
Route Table Configuration:
Destination Target x.x.x.x/24 local 0.0.0.0/0 the resource id of your Internet Gateway ::/0 the resource id of your Internet Gateway
Notes:
- The x.x.x.x will be the CIDR range you defined. I used 10.0.0.0, but you might have picked a different one.
- The IPv6 (::/0) came by default and I didn't bother removing it. It is only needed if you use IPv6.
Security Group Configuration
Inbound rules:
IP version Type Protocol Port range Source IPv4 HTTP TCP 80 0.0.0.0/0 IPv4 Custom TCP TCP $$$$$ 0.0.0.0/0 IPv6 HTTPS TCP 443 ::/0 IPv6 HTTP TCP 80 ::/0 IPv4 SSH TCP 22 0.0.0.0/0 IPv4 Custom UDP UDP $$$$$ 0.0.0.0/0 IPv4 HTTPS TCP 443 0.0.0.0/0
Notes:
- For the SSH, you should ideally use your workstation/network ip-address instead of the generic 0.0.0.0/0. The above setting makes the SSH available for the whole internet to try and penetrate. I was okay with this I trust my SSH key won't fall to the wrong hands
- For the $$$$$ you will pick a port WireGuard will use. Example would be 54321.
- I did not review if you need both TCP and UDP open to the WireGuard port. My recollection is that it only uses UDP, so you might not need the TCP.
- You can restrict access to the custom WireGuard with the source IP setting. As I plan on using WireGuard as a VPN when at public cafes and such, I wanted to have it more readily available.
- The IPv6 ones came by default and I didn't bother removing them. If you don't use IPv6, you don't need them.
IP version Type Protocol Port range Destination IPv4 All traffic All All 0.0.0.0/0
Notes:
- If you use IPv6, you probably need an outbound rule for it too (same settings as the IPv4).
Network ACL Configuration
Inbound rules:
Rule number Type Protocol Port range Source Allow/Deny 100 SSH (22) TCP (6) 22 0.0.0.0/0 Allow 101 HTTP (80) TCP (6) 80 0.0.0.0/0 Allow 102 HTTPS (443) TCP (6) 443 0.0.0.0/0 Allow 103 Custom TCP TCP (6) 1024 - 65535 0.0.0.0/0 Allow 104 Custom UDP UDP (17) $$$$$ 0.0.0.0/0 Allow All traffic All All 0.0.0.0/0 Deny
Notes
- For the SSH, you should ideally use your workstation/network ip-address instead of the generic 0.0.0.0/0. The above setting makes the SSH available for the whole internet to try and penetrate. I was okay with this I trust my SSH key won't fall to the wrong hands
- For the $$$$$ you will pick a port WireGuard will use. Example would be 54321.
- For TCP, I had some issues with the package manager on AMI that I first tried (yum) not being able to connect outside. To fix this, the 1024-65535 TCP rule was added. You can test not having it to keep the attack surface smaller.
- You can restrict access to the custom WireGuard with the source IP setting. As I plan on using WireGuard as a VPN when at public cafes and such, I wanted to have it more readily available.
Rule number Type Protocol Port range Source Allow/Deny 100 All traffic All All 0.0.0.0/0 Allow 101 HTTP (80) TCP (6) 80 0.0.0.0/0 Allow 102 HTTPS (443) TCP (6) 443 0.0.0.0/0 Allow All traffic All All 0.0.0.0/0 Deny
Notes:
- These rules aren't exactly the smartest, but it works. You might want to review them or I might update it.
Configure WireGuard
With the infrastructure set up, you can log in to the EC2 instance and start setting up WireGuard. All the command examples below are for Debian 10.
Update the OS
Start by updating and upgrading the OS.
sudo apt-get update sudo apt-get upgrade sudo apt-get dist-upgrade
Once up-to-date, I like to install ufw to control the firewall and make sure basic security is in place. So:
Setup firewall
sudo apt-get install ufw
The following is the set of commands to configure ufw:
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw allow $$$$$/udp sudo ufw allow $$$$$/tcp sudo ufw allow dns sudo ufw allow from x.x.x.x/24
For x.x.x.x you want to come up with an IP address range you will use. I used part of the CIDR range assigned to the VPC in the infrastructure configuration step, so for example, with CIDR 10.0.0.0 something like 10.10.10.0 will work.
The rules we create:
- deny incoming connection that don't match other rules
- allow all outgoing connections
- allow ssh connections (important so you don't get kicked out)
- allow http and https connections (likely not necessary)
- allow udp and tcp connections to the WireGuard port
- allow connections with a DNS
- and allow connections for the WireGuard IP range.
Replace $$$$$ with the port you used above when configuring the VPC. Also, as mentioned in the VPC section, TCP for the port isn't possibly required. And as mentioned there, you could limit ssh to just your workstation/network IP.
Next, we will edit the ufw before rules. For that and also for WireGuard configuration, we need the network interface:
ip -o -4 route show to default | awk '{print $5}'
With the interface value at hand, time to edit the rules:
sudo nano /etc/ufw/before.rules
Add this after the first comment batch before the filter part.
*nat :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -A POSTROUTING -s x.x.x.x/24 -o ZZZ -j MASQUERADE COMMIT
Replace ZZZ with the network interface we got earlier. Replace x.x.x.x with what you get from the following command:
sudo route -n | grep ^0.0.0.0
Finally, enable ufw:
sudo ufw enable
Since the ssh connections were allowed, you should stay signed in with the firewall in a good shape.
Install WireGuard
Installing the WireGuard is easy as it is available as a kernel extension in the default Debian repository:
sudo apt-get install wireguard
After the installation is complete, restart the instance.
sudo reboot
Your connection terminates, but you can reconnect after a brief moment. I like to move around the OS to the working directories instead of issuing them as commands, but feel free to replace commands with direct ones if you don't like that. I also love nano over other editors, but feel free to use your favorite for the edits.
Generate keys
cd /var sudo mkdir keys umask 077 sudo wg genkey > privatekey sudo wg pubkey < privatekey > publickey sudo mv privatekey /var/keys/wg-privatekey sudo mv publickey /var/keys/wg-publickey
These commands create the WireGuard keys that are needed in the configuration. You can take note of the keys now, use echo and cat for some fancy config making, or just view the keys later if needed with
sudo cat /var/keys/wg-privatekey
for the private key and
sudo cat /var/keys/wg-publickey
for the public key.
Create the configuration
Now we can start with the configuration.
sudo nano /etc/wireguard/wg0.conf
This is the full configuration for the server.
[Interface] Address = x.x.x.x/24 SaveConfig = false ListenPort = $$$$$ PrivateKey = THE_SERVER_PRIVATE_KEY_FROM_ABOVE PostUp = iptables -A FORWARD -i <>i -j ACCEPT; iptables -A FORWARD -o <>i -j ACCEPT; iptables -t nat -A POSTROUTING -o ZZZ -j MASQUERADE PostUp = ip route add default via y.y.y.y dev ZZZ table ssh PostUp = ip rule add fwmark 0x2 table ssh PostUp = /sbin/iptables -A OUTPUT -t mangle -o wg0 -p tcp --sport 22 -j MARK --set-mark 2 PreDown = /sbin/iptables -D OUTPUT -t mangle -o wg0 -p tcp --sport 22 -j MARK --set-mark 2 PreDown = ip rule del fwmark 0x2 table ssh PreDown = ip route del default via y.y.y.y dev ZZZ table ssh PostDown = iptables -D FORWARD -i <>i -j ACCEPT; iptables -D FORWARD -o <>i -j ACCEPT; iptables -t nat -D POSTROUTING -o ZZZ -j MASQUERADE
Replace ZZZ with the network interface you identified above. Replace x.x.x.x with a local IP address. I used the same range as the CIDR you configured in the VPC infrastructure, so with CIDR 10.0.0.0 something like 10.10.10.1 works perfectly. Replace $$$$$ with the port you have used in the earlier steps. Replace y.y.y.y with the IP address you get from this command:
sudo route -n | grep ^0.0.0.0
The address is probably close to the x.x.x.x address and in the CIDR range. Finally, adjust the permissions of the file so normal users can't see your keys:
sudo chmod 600 /etc/wireguard/wg0.conf
Additional configuration
We need to do some additional configuration to allow SSH connections to work when WireGuard is up, and to allow the routing to work. Issue the following command and add 2 ssh at the end of the file.
sudo nano /etc/iproute2/rt_tables
Continue with editing the next file:
sudo nano /etc/sysctl.conf
Below are the lines we are interested in. Uncomment them from your configuration.
net.ipv4.conf.all.rp_filter=2 net.ipv4.ip_forward=1
Also uncomment this if you use IPv6:
net.ipv6.conf.all.forwarding=1
Now you are ready for another restart.
Add client configs
Once the device has restarted, it is time to start adding clients. For this, you need to generate keys for the client. Easiest way is to use the official WireGuard app. It is available at least in the macOS, iOS, and iPadOS App Stores for free. As another option, you can use the command you used to generate the server keys to generate the client keys. Just be sure to save them in a different file / place to not confuse them! Also note, you need to give the client private key to the client as well as the public server key.
Once you have the keys, you edit the main configuration again.
sudo nano /etc/wireguard/wg0.conf
With the file open, add the following to the end. You can add as many as you need, one per connecting client. Note that you can use the same peer for connecting from different devices, so think of it a bit more like "user". Though if you connect from multiple devices at the same time it probs won't allow it.
[Peer] #USER PublicKey = USERPUBLICKEY AllowedIPs = x.x.x.x/32
The #USER after the peer is not needed, but it is for bookkeeping purposes: by replacing USER with whomever you assing the config to, you can easily remember which config is which.
As before, you replace the USERPUBLICKEY with the public key generated either by the WireGuard app, or with the public key you generated on the client. For the x.x.x.x, you continue from the server's address. If you gave the server 10.10.10.1, you give the first Peer 10.10.10.2, next one 10.10.10.3 and so on.
Save the file and you are ready to launch!
Launching WireGuard
Launching WireGuard is a fairly easy and rather unexciting. You just apply this command:
sudo wg-quick up wg0
If you need to stop the server, you just replace up with down. If you want WireGuard to start automatically at restart, you do something like:
sudo systemctl enable wg-quick@wg0
Now you can start connecting with your clients.
Future improvements
You now have a simple personal VPN of your own running. If you would like to take the system to the next level, you could look into:
- Create a subdomain on your domain to make connecting easy; no remembering IP-addresses!
- Add scaling groups and load balancers; why have the VPN running if nobody is using or if you need to up the performance after all your friends want in.
- Create an app to select regions, manage accounts and keys, and all that.
- Start selling your VPN service on the App Store with a swaggy name.
- Profit.