Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

Personal WireGuard VPN on AWS EC2

A self-hosted, full-tunnel WireGuard VPN running on a free-tier AWS EC2 instance in us-east-1. Every device (phone, laptop, tablet) routes its traffic through one encrypted tunnel to a t3.micro Ubuntu 24.04 server, which forwards it to the internet on their behalf. Websites therefore see the server's US-East-1 Elastic IP instead of the device's real location.

Layer Choice Why
VPN protocol WireGuard Modern, audited crypto, ~100 lines of config, minimal overhead on low-end instances
Compute EC2 t3.micro (Ubuntu 24.04) Free tier eligible (750 hrs/month); 1 GiB RAM is plenty for personal use
Region us-east-1 Free tier availability, low latency to the US
Firewall EC2 security group SSH only from my IP; UDP 51820 open to the world
Public address Elastic IP Stable endpoint so device configs never break across restarts

Result: a private, encrypted network (10.0.0.0/24) that any device can join in under a minute, at zero monthly cost.


Table of contents


Architecture

Topology

flowchart TD
    subgraph AWS["AWS — us-east-1"]
        EIP["Elastic IP<br/>(stable public address)"]
        subgraph EC2INSTANCE["EC2 t3.micro — Ubuntu 24.04"]
            W["eth0: physical interface<br/>connects to the internet"]
            WG["wg0: virtual interface<br/>10.0.0.1/24 — the VPN side"]
        end
    end

    Phone["Phone — 10.0.0.2"]
    Laptop["Laptop — 10.0.0.3"]
    Internet["Public internet"]
    Web["Websites see the server's<br/>Elastic IP (us-east-1)"]

    Phone <-->|"WireGuard tunnel<br/>encrypted UDP :51820"| EIP
    Laptop <-->|"WireGuard tunnel<br/>encrypted UDP :51820"| EIP
    EIP <--> W
    W <-->|"NAT masquerade<br/>(source address rewrite)"| Internet
    Internet --> Web
Loading

Two completely separate networks meet inside the server:

  1. VPN subnet 10.0.0.0/24 — private, only reachable through the tunnel. Server is 10.0.0.1, each device gets its own address (10.0.0.2, 10.0.0.3, ...).
  2. Public internet — the server's physical eth0 interface, reached via the Elastic IP.

Traffic flows between the two through iptables NAT (see the packet flow below).

Packet flow

sequenceDiagram
    participant P as "Phone (10.0.0.2)"
    participant S as "EC2 wg0 (10.0.0.1)"
    participant E as "EC2 eth0 (public)"
    participant W as "Website"

    P->>P: Browser sends packet (dest: website IP)
    P->>P: WireGuard encrypts it and wraps it in a UDP packet to :51820
    P->>S: Encrypted UDP travels through the tunnel
    S->>S: Decrypts the packet back to its original form
    S->>E: iptables MASQUERADE rewrites source to the Elastic IP
    E->>W: Ordinary request goes out to the internet
    W-->>E: Response comes back to the Elastic IP
    E-->>S: NAT reverses the rewrite (back to 10.0.0.2)
    S-->>P: Encrypted response returns through the tunnel
    P->>P: Decrypts and the browser renders the page
Loading

Security group (the firewall)

Type Protocol Port Source Purpose
SSH TCP 22 My IP only Admin access to the server
Custom UDP UDP 51820 0.0.0.0/0 The WireGuard door — open to everyone, but only devices with a valid key get in

How it works in 30 seconds

  1. Each device has a cryptographic keypair — a secret private key and a public key it shares with the server.
  2. The server keeps a member list (/etc/wireguard/wg0.conf): one [Peer] block per device containing that device's public key and its VPN IP.
  3. When a device connects, the phone and server perform an authenticated handshake — no one without the right private key can impersonate a member.
  4. Every packet the phone sends is encrypted and wrapped (encapsulated) inside a UDP packet addressed to Elastic-IP:51820. To the outside world it just looks like encrypted noise.
  5. The server decrypts, rewrites the source address (NAT), and forwards the packet to the internet. Return traffic is rewritten back and sent through the tunnel. This is why websites see the server's US location.
  6. The phone's config says AllowedIPs = 0.0.0.0/0all traffic goes through the tunnel (full-tunnel mode).

Key concepts glossary

Concept Plain meaning Deep dive
Interface A network connection point on a machine — physical (eth0) or virtual (wg0) docs/networking-guide.md
Subnet A range of IP addresses, e.g. 10.0.0.0/24 = 256 addresses for the VPN docs/networking-guide.md
Tunnel / encapsulation Wrapping an encrypted packet inside another packet docs/networking-guide.md
NAT / MASQUERADE Rewriting the source address so return traffic is routable docs/networking-guide.md
AllowedIPs Both a routing rule and a firewall filter in WireGuard docs/networking-guide.md
Keypair Private key (secret) + public key (shared) that prove a device's identity docs/networking-guide.md
Elastic IP A permanent public IP attached to the instance docs/networking-guide.md

Setup recap

Exact steps used to build this project (details in the docs):

1. AWS console

  • Launched Ubuntu 24.04, t3.micro, in us-east-1, with a new key pair (vpn-key.pem).
  • Security group: SSH (22) from my IP, UDP 51820 from 0.0.0.0/0.
  • Allocated an Elastic IP and associated it with the instance.

2. Server setup (via SSH)

ssh -i vpn-key.pem ubuntu@<ELASTIC-IP>

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

# Enable IP forwarding (allow the server to route client traffic)
sudo sh -c 'echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf'
sudo sysctl -p

# Generate the server keypair
wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

3. Server config — /etc/wireguard/wg0.conf

[Interface]
PrivateKey = <server-private-key>
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens5 -j MASQUERADE

Note: ens5 is the physical interface name on t3 instances. Verify with ip route | grep default — if yours says eth0, use eth0.

# Start and enable at boot
sudo systemctl enable --now wg-quick@wg0

4. Device setup (phone)

# On your computer: generate the phone's keypair
wg genkey > phone_private.key
wg pubkey < phone_private.key > phone_public.key

phone.conf (scanned as a QR code by the official WireGuard app):

[Interface]
PrivateKey = <phone-private-key>
Address = 10.0.0.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = <server-public-key>
Endpoint = <ELASTIC-IP>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

Add the phone to the server's member list, then hot-reload:

# Append to /etc/wireguard/wg0.conf:
[Peer]
PublicKey = <phone-public-key>
AllowedIPs = 10.0.0.2/32

# Apply without disconnecting anyone
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'

Display the QR and scan it from the app:

sudo apt install -y qrencode
qrencode -t ansiutf8 < phone.conf

Docs


Troubleshooting

Symptom Likely cause Fix
Tunnel connects but no sites load Wrong interface in the NAT rule, or IP forwarding off Check ip route | grep default; use the correct name (ens5/eth0) in PostUp; confirm sysctl net.ipv4.ip_forward = 1
No handshake at all (wg show shows stale peer) Security group missing/incorrect rule Inbound must be UDP (not TCP) 51820 from 0.0.0.0/0
Dies after a few minutes on cellular Missing keepalive PersistentKeepalive = 25 in the device config
wg syncconf fails: "Permission denied" sudo didn't cover the process substitution Use sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'
QR code won't scan Terminal rendering Enlarge font / fullscreen; or try qrencode -t UTF8 < phone.conf
VPN was working, stopped after a server reboot Elastic IP not attached Associate the Elastic IP (addresses change across stop/start otherwise)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors