A cost-effective WireGuard VPN setup on AWS EC2 with auto-shutdown when idle.
- Docker-based: Uses wg-easy for easy management
- Web UI: Manage clients via browser
- Auto-shutdown: Instance stops after 30 minutes of no VPN traffic
- Cost-effective: ~$1-2/month with minimal usage (t4g.nano ARM instance)
- Elastic IP: Fixed IP address persists across stop/start
- AWS CLI configured (
aws configure) - Terraform installed
- Docker (for generating password hash)
- SSH key pair registered in AWS Tokyo region (ap-northeast-1)
cd wireguard-aws
cp terraform.tfvars.example terraform.tfvars# Check existing keys
aws ec2 describe-key-pairs --region ap-northeast-1 --query 'KeyPairs[*].KeyName'
# Import your key if needed
ssh-keygen -y -f ~/.ssh/your-key.pem > ~/.ssh/your-key.pub
aws ec2 import-key-pair \
--region ap-northeast-1 \
--key-name "your-key-name" \
--public-key-material fileb://~/.ssh/your-key.pub# Generate and base64 encode in one command
docker run --rm ghcr.io/wg-easy/wg-easy wgpw 'YourSecurePassword' | cut -d"'" -f2 | base64 -w0
echo # newlinecurl ifconfig.mekey_name = "your-ssh-key-name"
wg_password_hash_base64 = "your-base64-encoded-hash"
my_ip = "YOUR.PUBLIC.IP/32"terraform init
terraform plan
terraform applyWait 2-3 minutes for the instance to initialize, then:
terraform outputOpen the Web UI URL in your browser and login with your password.
- Access:
http://<vpn_ip>:51821 - Create clients, download configs, scan QR codes
# Install WireGuard
sudo pacman -S wireguard-tools # Arch
sudo apt install wireguard # Debian/Ubuntu
# Save config from Web UI to /etc/wireguard/wg0.conf
# Connect
sudo wg-quick up wg0
# Check status
sudo wg
# Disconnect
sudo wg-quick down wg0- Install WireGuard app (iOS/Android)
- Scan QR code from Web UI
- Toggle connection on/off
aws ec2 describe-instances \
--instance-ids $(terraform output -raw instance_id) \
--region ap-northeast-1 \
--query 'Reservations[0].Instances[0].State.Name' \
--output textaws ec2 start-instances \
--instance-ids $(terraform output -raw instance_id) \
--region ap-northeast-1
# Wait for it
aws ec2 wait instance-running \
--instance-ids $(terraform output -raw instance_id) \
--region ap-northeast-1aws ec2 stop-instances \
--instance-ids $(terraform output -raw instance_id) \
--region ap-northeast-1$(terraform output -raw ssh_command)Create these in ~/.local/bin/ for convenience:
#!/bin/bash
INSTANCE_ID=$(cd ~/wireguard-aws && terraform output -raw instance_id)
REGION="ap-northeast-1"
echo "Starting WireGuard VPN..."
aws ec2 start-instances --instance-ids "$INSTANCE_ID" --region "$REGION" > /dev/null
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" --region "$REGION"
IP=$(terraform -chdir=~/wireguard-aws output -raw vpn_ip)
echo "VPN ready: http://$IP:51821"#!/bin/bash
INSTANCE_ID=$(cd ~/wireguard-aws && terraform output -raw instance_id)
aws ec2 stop-instances --instance-ids "$INSTANCE_ID" --region ap-northeast-1 > /dev/null
echo "VPN stopping..."#!/bin/bash
INSTANCE_ID=$(cd ~/wireguard-aws && terraform output -raw instance_id)
STATE=$(aws ec2 describe-instances \
--instance-ids "$INSTANCE_ID" \
--region ap-northeast-1 \
--query 'Reservations[0].Instances[0].State.Name' \
--output text)
echo "Status: $STATE"Make executable:
chmod +x ~/.local/bin/wg-vpn-{start,stop,status}| Resource | Monthly Cost |
|---|---|
| t4g.nano (stopped) | $0 |
| t4g.nano (running) | ~$3.80/month full-time |
| Elastic IP (attached to running instance) | $0 |
| Elastic IP (attached to stopped instance) | ~$3.60/month |
| EBS 30GB gp3 | ~$2.40/month |
Typical usage (2 hrs/day): ~$6-8/month
Note: AWS charges for Elastic IPs attached to stopped instances since February 2024.
The instance automatically shuts down after 30 minutes of no VPN activity. This is handled by a cron job that checks WireGuard handshake timestamps.
To modify the idle threshold, SSH into the instance and edit:
sudo nano /opt/wireguard/auto-shutdown.sh
# Change IDLE_THRESHOLD=1800 (seconds)- Check instance is running
- Verify your IP matches
my_ipin terraform.tfvars - Check security group allows your IP
ssh -i ~/.ssh/your-key.pem ec2-user@<IP>
sudo docker ps -a
sudo docker logs wg-easy
cat /var/log/user-data.logThe password hash may have been corrupted. SSH in and regenerate:
cd /opt/wireguard
sudo docker-compose down
HASH=$(sudo docker run --rm ghcr.io/wg-easy/wg-easy wgpw 'YourPassword' | cut -d"'" -f2)
export PASSWORD_HASH="$HASH"
export PUBLIC_IP=$(curl -s ifconfig.me)
sudo -E docker-compose up -dterraform destroyThis removes the EC2 instance, security group, and Elastic IP.
MIT