Setup WireGuard VPN on AWS

Authors

Table of Contents

Introduction

WireGuard is a modern, fast, and secure VPN protocol that offers excellent performance and easy setup. This guide will walk you through setting up a WireGuard VPN server on AWS with both IPv4 and IPv6 support from scratch.

Infrastructure Setup

Configure your local aws cli to run commands.

1. Create VPC and Subnets

First, we'll create a VPC with IPv6 support and necessary subnets:

# Create VPC
VPC_ID=$(aws ec2 create-vpc \
  --region us-west-2 \
  --cidr-block 10.0.0.0/16 \
  --amazon-provided-ipv6-cidr-block \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=wireguard-vpc}]' \
  --output text --query 'Vpc.VpcId')

# Get IPv6 CIDR of VPC
IPV6_CIDR_BLOCK=$(aws ec2 describe-vpcs \
  --vpc-ids $VPC_ID \
  --output text \
  --query 'Vpcs[0].Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock')

# Create IPv6 block of size /60
IPV6_SUBNET_CIDR=$(echo $IPV6_CIDR_BLOCK | cut -d '/' -f1)/60

# Create public subnet
SUBNET_ID=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.1.0/24 \
  --ipv6-cidr-block ${IPV6_SUBNET_CIDR} \
  --availability-zone us-west-2a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=wireguard-subnet}]' \
  --output text --query 'Subnet.SubnetId')

# Create and attach Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=wireguard-igw}]' \
  --output text --query 'InternetGateway.InternetGatewayId')

aws ec2 attach-internet-gateway \
  --vpc-id $VPC_ID \
  --internet-gateway-id $IGW_ID

# Get Route table ID so that we can setup connection to IGW
ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --output text \
  --query 'RouteTables[0].RouteTableId')

# Create routes for IPv4 to internet
aws ec2 create-route \
  --route-table-id $ROUTE_TABLE_ID \
  --destination-cidr-block "0.0.0.0/0" \
  --gateway-id $IGW_ID

# Create routes for IPv6 to internet
aws ec2 create-route \
  --route-table-id $ROUTE_TABLE_ID \
  --destination-ipv6-cidr-block "::/0" \
  --gateway-id $IGW_ID

2. Create Security Group

Create a security group for the WireGuard server:

SG_ID=$(aws ec2 create-security-group \
  --group-name wireguard-sg \
  --description "Security group for WireGuard VPN" \
  --vpc-id $VPC_ID \
  --output text --query 'GroupId')

# Allow WireGuard UDP port
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol udp \
  --port 51820 \
  --cidr 0.0.0.0/0

# Allow SSH (for initial setup)
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

# Allow WireGuard UDP port from IPv6
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --ip-permissions IpProtocol=udp,FromPort=51820,ToPort=51820,Ipv6Ranges="[{CidrIpv6=::/0}]"

# Allow SSH (for initial setup) from IPv6
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --ip-permissions IpProtocol=tcp,FromPort=22,ToPort=22,Ipv6Ranges="[{CidrIpv6=::/0}]"

3. Launch EC2 Instance

Launch an Ubuntu 24.04 EC2 instance:

# Create key pair to SSH into instance
aws ec2 create-key-pair \
  --key-name wireguard-key \
  --query 'KeyMaterial' \
  --output text > wireguard-key.pem

chmod 400 wireguard-key.pem

# Get Ubuntu 24.04 AMI ID
AMI_ID=$(aws ec2 describe-images \
  --owners amazon \
  --filters 'Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-20240423' \
  --output text \
  --query 'Images[0].ImageId')

# Launch instance
INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID \
  --instance-type t3.micro \
  --key-name wireguard-key \
  --subnet-id $SUBNET_ID \
  --security-group-ids $SG_ID \
  --associate-public-ip-address \
  --enable-primary-ipv6 \
  --ipv6-address-count 1 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wireguard-server}]' \
  --output text --query 'Instances[0].InstanceId')

4. SSH into EC2 Instance

IPV4_ADDRESS=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --output text \
  --query 'Reservations[0].Instances[0].PublicIpAddress')

ssh -i wireguard-key.pem ubuntu@$IPV4_ADDRESS

WireGuard Installation and Configuration

1. Install WireGuard

SSH into your instance and install WireGuard:

sudo apt update
sudo apt install -y wireguard qrencode

2. Enable IP Forwarding

# Enable IPv4 forwarding
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf

# Enable IPv6 forwarding
echo "net.ipv6.conf.all.forwarding = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

3. Generate Server and Client Keys

sudo mkdir -p /etc/wireguard

# Generate Server Keys
wg genkey | sudo tee /etc/wireguard/server_private.key
sudo chmod 600 /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key

# Generate Client Keys
wg genkey | sudo tee /etc/wireguard/client_private.key
sudo cat /etc/wireguard/client_private.key | wg pubkey | sudo tee /etc/wireguard/client_public.key

4. Create Server Configuration

Create /etc/wireguard/wg0.conf:


SERVER_PRIVATE_KEY=$(sudo cat /etc/wireguard/server_private.key)
CLIENT_PUBLIC_KEY=$(sudo cat /etc/wireguard/client_public.key)

cat << EOF | sudo tee /etc/wireguard/wg0.conf
[Interface]
PrivateKey = ${SERVER_PRIVATE_KEY}
Address = 10.8.0.1/24, fd00:1234:5678:9abc::1/64
ListenPort = 51820
PostUp = iptables -t nat -I POSTROUTING -o ens5 -j MASQUERADE
PostUp = ip6tables -t nat -I POSTROUTING -o ens5 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o ens5 -j MASQUERADE
PostDown = ip6tables -t nat -D POSTROUTING -o ens5 -j MASQUERADE

[Peer]
PublicKey = ${CLIENT_PUBLIC_KEY}
AllowedIPs = 10.8.0.2/32, fd00:1234:5678:9abc::2/64
EOF

5. Start WireGuard

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

Confirm status by sudo wg show

Client Configuration

1. Create Client Configuration

Create a file named client.conf:

SERVER_PUBLIC_KEY=$(sudo cat /etc/wireguard/server_public.key)
CLIENT_PRIVATE_KEY=$(sudo cat /etc/wireguard/client_private.key)

SERVER_IPV6=$(ip -6 addr show dev ens5 | grep -oP '(?<=inet6 )([0-9a-f:]+)' | head -1)

# Or use IPv4 if your client doesn't have Ipv6 network
# SERVER_IPV4=$(curl checkip.amazonaws.com)

cat << EOF | sudo tee /etc/wireguard/client.conf
[Interface]
PrivateKey = ${CLIENT_PRIVATE_KEY}
Address = 10.8.0.2/32, fd00:1234:5678:9abc::2/64
DNS = 1.1.1.1, 2606:4700:4700::1111

[Peer]
PublicKey = ${SERVER_PUBLIC_KEY}
# Or use IPV4 address if your client doesn't support IPv6
Endpoint = [$SERVER_IPV6]:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

2. Connect Client

Share client.conf with the client or scan QR code on mobile devices:

# Generate QR code
sudo cat /etc/wireguard/client.conf | qrencode -t ansiutf8

Complete Infrastructure Script

Here's the complete set of commands to setup VPC, Subnet, Internet Gateway, Route Table, Security Group, and EC2 instance:

#!/bin/bash

# Create VPC
VPC_ID=$(aws ec2 create-vpc \
  --region us-west-2 \
  --cidr-block 10.0.0.0/16 \
  --amazon-provided-ipv6-cidr-block \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=wireguard-vpc}]' \
  --output text --query 'Vpc.VpcId')

# Get IPv6 CIDR of VPC
IPV6_CIDR_BLOCK=$(aws ec2 describe-vpcs \
  --vpc-ids $VPC_ID \
  --output text \
  --query 'Vpcs[0].Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock')

# Create IPv6 block of size /60
IPV6_SUBNET_CIDR=$(echo $IPV6_CIDR_BLOCK | cut -d '/' -f1)/60

# Create public subnet
SUBNET_ID=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.1.0/24 \
  --ipv6-cidr-block ${IPV6_SUBNET_CIDR} \
  --availability-zone us-west-2a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=wireguard-subnet}]' \
  --output text --query 'Subnet.SubnetId')

# Create and attach Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=wireguard-igw}]' \
  --output text --query 'InternetGateway.InternetGatewayId')

aws ec2 attach-internet-gateway \
  --vpc-id $VPC_ID \
  --internet-gateway-id $IGW_ID

# Get Route table ID so that we can setup connection to IGW
ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --output text \
  --query 'RouteTables[0].RouteTableId')

# Create routes for IPv4 to internet
aws ec2 create-route \
  --route-table-id $ROUTE_TABLE_ID \
  --destination-cidr-block "0.0.0.0/0" \
  --gateway-id $IGW_ID

# Create routes for IPv6 to internet
aws ec2 create-route \
  --route-table-id $ROUTE_TABLE_ID \
  --destination-ipv6-cidr-block "::/0" \
  --gateway-id $IGW_ID

SG_ID=$(aws ec2 create-security-group \
  --group-name wireguard-sg \
  --description "Security group for WireGuard VPN" \
  --vpc-id $VPC_ID \
  --output text --query 'GroupId')

# Allow WireGuard UDP port
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol udp \
  --port 51820 \
  --cidr 0.0.0.0/0

# Allow SSH (for initial setup)
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

# Allow WireGuard UDP port from IPv6
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --ip-permissions IpProtocol=udp,FromPort=51820,ToPort=51820,Ipv6Ranges="[{CidrIpv6=::/0}]"

# Allow SSH (for initial setup) from IPv6
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --ip-permissions IpProtocol=tcp,FromPort=22,ToPort=22,Ipv6Ranges="[{CidrIpv6=::/0}]"

# Create key pair to SSH into instance
aws ec2 create-key-pair \
  --key-name wireguard-key \
  --query 'KeyMaterial' \
  --output text > wireguard-key.pem

chmod 400 wireguard-key.pem

# Get Ubuntu 24.04 AMI ID
AMI_ID=$(aws ec2 describe-images \
  --owners amazon \
  --filters 'Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-20240423' \
  --output text \
  --query 'Images[0].ImageId')

# Launch instance
INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID \
  --instance-type t3.micro \
  --key-name wireguard-key \
  --subnet-id $SUBNET_ID \
  --security-group-ids $SG_ID \
  --associate-public-ip-address \
  --enable-primary-ipv6 \
  --ipv6-address-count 1 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wireguard-server}]' \
  --output text --query 'Instances[0].InstanceId')

IPV4_ADDRESS=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --output text \
  --query 'Reservations[0].Instances[0].PublicIpAddress')

ssh -i wireguard-key.pem ubuntu@$IPV4_ADDRESS

Complete WireGuard Script

Here's complete set of commands to setup WireGuard server:

#!/bin/bash

# Install WireGuard
sudo apt update
sudo apt install -y wireguard qrencode

# Enable IPv4 forwarding
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf

# Enable IPv6 forwarding
echo "net.ipv6.conf.all.forwarding = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

 # Generate Client And Server Keys
 sudo mkdir -p /etc/wireguard

# Generate Server Keys
wg genkey | sudo tee /etc/wireguard/server_private.key
sudo chmod 600 /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key

# Generate Client Keys
wg genkey | sudo tee /etc/wireguard/client_private.key
sudo cat /etc/wireguard/client_private.key | wg pubkey | sudo tee /etc/wireguard/client_public.key


# Create Server Configuration
SERVER_PRIVATE_KEY=$(sudo cat /etc/wireguard/server_private.key)
CLIENT_PUBLIC_KEY=$(sudo cat /etc/wireguard/client_public.key)

cat << EOF | sudo tee /etc/wireguard/wg0.conf
[Interface]
PrivateKey = ${SERVER_PRIVATE_KEY}
Address = 10.8.0.1/24, fd00:1234:5678:9abc::1/64
ListenPort = 51820
PostUp = iptables -t nat -I POSTROUTING -o ens5 -j MASQUERADE
PostUp = ip6tables -t nat -I POSTROUTING -o ens5 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o ens5 -j MASQUERADE
PostDown = ip6tables -t nat -D POSTROUTING -o ens5 -j MASQUERADE

[Peer]
PublicKey = ${CLIENT_PUBLIC_KEY}
AllowedIPs = 10.8.0.2/32, fd00:1234:5678:9abc::2/64
EOF

# Start WireGuard
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0


# Create Client Configuration
SERVER_PUBLIC_KEY=$(sudo cat /etc/wireguard/server_public.key)
CLIENT_PRIVATE_KEY=$(sudo cat /etc/wireguard/client_private.key)

SERVER_IPV6=$(ip -6 addr show dev ens5 | grep -oP '(?<=inet6 )([0-9a-f:]+)' | head -1)

# Or use IPv4 if your client doesn't have Ipv6 network
# SERVER_IPV4=$(curl checkip.amazonaws.com)

cat << EOF | sudo tee /etc/wireguard/client.conf
[Interface]
PrivateKey = ${CLIENT_PRIVATE_KEY}
Address = 10.8.0.2/32, fd00:1234:5678:9abc::2/64
DNS = 1.1.1.1, 2606:4700:4700::1111

[Peer]
PublicKey = ${SERVER_PUBLIC_KEY}
# Or use IPV4 address if your client doesn't support IPv6
Endpoint = [$SERVER_IPV6]:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

# Generate QR code
echo "Generating QR code..."
sudo cat /etc/wireguard/client.conf | qrencode -t ansiutf8

echo "Current WireGuard status:"
sudo wg show

Using the VPN

For Desktop Clients

  1. Install WireGuard client for your operating system:

  2. Copy the contents of /etc/wireguard/client.conf to your client machine

  3. Import the configuration into your WireGuard client

  4. Enable the VPN connection

For Mobile Clients

  1. Install WireGuard app:

  2. Scan the QR code displayed in sudo cat /etc/wireguard/client.conf | qrencode -t ansiutf8

  3. Enable the VPN connection

Adding Additional Clients

To add more clients, run these commands on the server:

#!/bin/bash

# Generate keys for the new client
CLIENT_NUM=2  # Change this number for each new client
sudo wg genkey | sudo tee "/etc/wireguard/client${CLIENT_NUM}_private.key"
sudo cat "/etc/wireguard/client${CLIENT_NUM}_private.key" | wg pubkey | sudo tee "/etc/wireguard/client${CLIENT_NUM}_public.key"

# Get the keys and server info
NEW_CLIENT_PRIVATE_KEY=$(sudo cat "/etc/wireguard/client${CLIENT_NUM}_private.key")
NEW_CLIENT_PUBLIC_KEY=$(sudo cat "/etc/wireguard/client${CLIENT_NUM}_public.key")
SERVER_PUBLIC_KEY=$(sudo cat /etc/wireguard/server_public.key)
SERVER_IPV6=$(ip -6 addr show dev ens5 | grep -oP '(?<=inet6 )([0-9a-f:]+)' | head -1)

# Add peer to server config
sudo tee -a /etc/wireguard/wg0.conf << EOF

[Peer]
PublicKey = ${NEW_CLIENT_PUBLIC_KEY}
AllowedIPs = 10.8.0.$((CLIENT_NUM + 1))/32, fd00:1234:5678:9abc::$((CLIENT_NUM + 1))/64
EOF

# Create client config
cat << EOF | sudo tee "/etc/wireguard/client${CLIENT_NUM}.conf"
[Interface]
PrivateKey = ${NEW_CLIENT_PRIVATE_KEY}
Address = 10.8.0.$((CLIENT_NUM + 1))/32, fd00:1234:5678:9abc::$((CLIENT_NUM + 1))/64
DNS = 1.1.1.1, 1.0.0.1

[Peer]
PublicKey = ${SERVER_PUBLIC_KEY}
Endpoint = [$SERVER_IPV6]:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

# Generate QR code for the new client
sudo cat "/etc/wireguard/client${CLIENT_NUM}.conf" | qrencode -t ansiutf8

# Restart WireGuard to apply changes
sudo systemctl restart wg-quick@wg0

echo "New client configuration created:"
echo "Config file: /etc/wireguard/client${CLIENT_NUM}.conf"

Troubleshooting

If you encounter issues, check:

  1. WireGuard service status:
sudo systemctl status wg-quick@wg0
  1. Logs:
sudo journalctl -xeu wg-quick@wg0
  1. Interface status:
sudo wg show
ip addr show wg0
  1. IP forwarding status:
sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding