π Your own VPN server on Azure, deployed in under 5 minutes.
Tired of sketchy VPN providers that log everything, throttle you at peak hours, and vanish overnight? Build your own. This repo gives you a fully automated, infrastructure-as-code VPN server on Azure β WireGuard for raw speed, Xray VLESS+Reality for stealth when deep packet inspection fights back.
You βββΊ WireGuard (UDP 443) βββββββββββββββββββββββΊ Azure VM βββΊ π The Open Internet
You βββΊ VLESS+Reality (TCP 443, disguised as TLS) βββΊ Azure VM βββΊ π The Open Internet
- π Privacy β Your server, your keys, your logs (or lack thereof). Zero trust in third parties.
- π§± Anti-censorship β WireGuard on port 443 punches through most firewalls. When DPI kicks in, VLESS+Reality makes your traffic indistinguishable from regular HTTPS.
- β‘ Speed β Direct path from Azure's global backbone. No shared bandwidth with 10,000 other users.
- π° Cheap β ~$12/mo for a B1s VM. Deallocate when idle, pay ~$5/mo. Less than most commercial VPN subscriptions.
graph TB
subgraph clients ["π± Your Devices"]
mac["π₯οΈ macOS<br/>WireGuard"]
iphone["π± iOS<br/>WireGuard / Xray client"]
win["π» Windows<br/>WireGuard / Xray client"]
end
subgraph azure ["βοΈ Azure (any region)"]
subgraph vnet ["π VNet 10.100.0.0/16"]
subgraph subnet ["Subnet 10.100.1.0/24"]
vm["π₯οΈ Ubuntu 24.04 VM"]
wg["π WireGuard<br/>UDP 443"]
xray["π₯· Xray VLESS+Reality<br/>TCP 443"]
end
end
nsg["π‘οΈ NSG<br/>UDP 443 Β· TCP 443 Β· SSH 22"]
pip["π Static Public IP"]
end
subgraph internet ["π The Internet"]
sites["Anywhere you want to go"]
end
mac -- "WireGuard tunnel" --> pip
iphone -- "WireGuard / VLESS" --> pip
win -- "WireGuard / VLESS" --> pip
pip --> nsg
nsg --> wg
nsg --> xray
vm --> sites
style azure fill:#e8f4fd,stroke:#0078D4,stroke-width:2px
style clients fill:#f0f0f0,stroke:#666,stroke-width:1px
style internet fill:#e8fde8,stroke:#28a745,stroke-width:1px
style vm fill:#fff,stroke:#0078D4
style wg fill:#88171A,color:#fff,stroke:#88171A
style xray fill:#1a6fb5,color:#fff,stroke:#1a6fb5
style nsg fill:#fff3cd,stroke:#ffc107
| Layer | Tech | Why |
|---|---|---|
| ποΈ IaC | Azure Bicep | Declarative, native, no Terraform state file drama |
| βοΈ Provisioning | cloud-init | VM boots with WireGuard already running |
| π VPN | WireGuard | ~3% overhead, kernel-space, auditable codebase |
| π₯· Stealth | Xray VLESS+Reality | Defeats DPI β your packets cosplay as TLS 1.3 |
| π Deploy | Bash + az CLI |
One script. No CI/CD. No YAML pipelines. Just ./deploy.sh |
# 1οΈβ£ Clone it
git clone https://github.com/<your-username>/VPNOnAzure.git && cd VPNOnAzure
# 2οΈβ£ Configure (pick your region, VM size, peer count)
cp .env.example .env && $EDITOR .env
# 3οΈβ£ Generate WireGuard keypairs (Curve25519 β same crypto as Signal)
./scripts/generate-keys.sh
# 4οΈβ£ Ship it to Azure
az login
cd infra && ./deploy.sh
# 5οΈβ£ Generate client configs (+ QR codes if qrencode is installed)
cd .. && ./scripts/generate-client-configs.sh
# 6οΈβ£ Import configs/peer1.conf into WireGuard app. Done. πβ±οΈ Time from git clone to connected VPN: ~5 minutes. Most of that is Azure provisioning the VM.
Everything lives in .env. No YAML. No JSON. Just KEY=value.
RESOURCE_GROUP=rg-vpn # Azure resource group name
LOCATION=eastus # Azure region (see all: az account list-locations -o table)
VM_SIZE=Standard_B2s # B1s ($8/mo) or B2s ($30/mo) β your call
PEER_COUNT=6 # Number of client devices
WG_PORT=443 # Port 443 = looks like HTTPS = harder to block
DNS_SERVERS="1.1.1.1, 8.8.8.8" # Cloudflare + Google DNSSee .env.example for all options including SSH key path, SSH IP restriction, and DNS labels.
./scripts/vm-start.sh # βΆοΈ Wake up the VM (billing resumes)
./scripts/vm-stop.sh # βΉοΈ Deallocate (billing stops, IP retained)
ssh azureuser@<ip> 'sudo wg' # π Check connected peers and transfer stats
./scripts/generate-client-configs.sh # π Regenerate after IP change| B1s (budget) | B2s (comfortable) | |
|---|---|---|
| π₯οΈ Compute | ~$8/mo | ~$30/mo |
| π Static IP | ~$4/mo | ~$4/mo |
| πΎ Disk (30 GB) | ~$1/mo | ~$1/mo |
| β Total (always on) | ~$13/mo | ~$35/mo |
| π΄ Total (deallocate at night) | ~$9/mo | ~$20/mo |
π‘ Pro tip:
vm-stop.shdeallocates the VM. You pay $0 for compute while it's off. Only storage + IP continue billing.
WireGuard is fast but its handshake pattern is fingerprint-able. If your network does deep packet inspection:
- SSH into your VM
- Install Xray-core
- Configure VLESS+Reality on TCP 443
- Connect via any Xray-compatible client
Your traffic looks like a regular TLS 1.3 connection to a legitimate website. DPI sees a real certificate and a normal handshake. Good luck blocking that without breaking half the internet. π
π§ Automating Xray in cloud-init is on the TODO list. PRs welcome.
By default, all traffic goes through the VPN (full tunnel). To route only specific traffic through the tunnel, you can customize AllowedIPs in the generated client configs.
A helper script is included that fetches APNIC delegation data to compute country-level IP exclusions:
python3 scripts/generate-china-routes.py # Fetches APNIC data, computes exclusion routes
./scripts/generate-client-configs.sh # Regenerates configs with split tunnel AllowedIPs
β οΈ iOS caveat: WireGuard on iOS can't handle large route tables (>100 entries). Stick with full tunnel on iOS devices.
| Platform | App | Guide |
|---|---|---|
| π₯οΈ macOS | WireGuard | setup-macos.md |
| π± iOS | WireGuard | setup-ios.md |
| π» Windows | WireGuard | setup-windows.md |
π² brew install qrencode on your Mac β the config generator will spit out terminal QR codes you can scan directly with your phone.
.env.example # cp to .env, fill in your values
infra/
deploy.sh # π΄ The big red button
main.bicep # Bicep orchestrator
cloud-init.yaml # VM bootstrap β WireGuard ready on first boot
modules/{vm,vnet,publicip,nsg}.bicep
scripts/
generate-keys.sh # π Curve25519 keypairs + preshared keys
generate-client-configs.sh # π .conf files + QR codes
generate-china-routes.py # πΊοΈ APNIC data β AllowedIPs exclusion
vm-start.sh / vm-stop.sh # βΆοΈβΉοΈ Billing on / billing off
clients/
setup-{macos,ios,windows}.md # π Per-platform walkthroughs
- Azure CLI β
brew install azure-cli/winget install Microsoft.AzureCLI - WireGuard tools β
brew install wireguard-tools/sudo apt install wireguard-tools - An Azure subscription (free account works, or use Visual Studio Enterprise credits)
- An SSH key β
ssh-keygen -t ed25519if you don't have one
- π« No passwords anywhere. SSH is key-only. WireGuard is public-key + preshared key.
- π― Minimal attack surface. NSG allows exactly 3 ports. Everything else is dropped.
- π No secrets in git. Keys and configs are generated locally, never committed.
- π Optional IP restriction. Set
ALLOW_SSH_FROMin.envto lock SSH to your IP.
| Issue | Workaround |
|---|---|
| π§± DPI may throttle WireGuard | Switch to VLESS+Reality |
| π± iOS can't handle split tunnel routes | Use full tunnel (0.0.0.0/0) on iOS |
| π§ Xray not yet automated | Manual install post-deploy (automation planned) |
Found a bug? Want to automate Xray setup? PRs are welcome. The codebase is intentionally simple β Bash scripts, Bicep modules, no frameworks.
MIT β Do whatever you want with it.
Built with π§ Bicep, π Bash, and a healthy distrust of third-party VPN providers.