Writing
Technical

Building a Dual-Hop VPN with Terraform and V2Ray

Ahead of a trip where I knew everyday tools would be blocked, I built a multi-hop VPN from scratch with Terraform, Docker, V2Ray, and Nginx, and recently open-sourced it.

Jul 14, 20254 min read

In July 2025 I was preparing for an extended trip to a country where I knew I'd run into network restrictions: no GitHub, no Google, limited access to half the tools I use daily. Rather than trust a commercial VPN, I decided to build my own. Over a couple of weekends I put together a dual-hop setup using Terraform, Docker, V2Ray, and Nginx. It worked well enough that I kept using it for months, and I recently cleaned it up into an open-source project.

How it works

Dual-hop VPN architecture: traffic flows from you through a relay node to an exit node and out to the internet

Traffic flows through two hops. A relay node in one AWS region picks up the encrypted connection and passes it along to an exit node in a different region, and that exit node is the one that actually reaches the open internet. From the outside the relay looks like a normal HTTPS site serving static content, so there's nothing obvious to fingerprint or block.

Why I built it this way

Partly practical, partly curiosity. I work with AWS and infrastructure-as-code day to day, so this was a chance to wire together a bunch of services I already know (EC2 provisioning, security groups, Elastic IPs, TLS certificate automation, Docker orchestration) into something end-to-end.

Things I'm happy with

One-command deploy. A single terraform apply provisions both EC2 instances and handles TLS certificates via a Cloudflare DNS challenge. It also drops a ready-to-import client config onto your machine, and tearing everything down is just as quick.

QR code generator. I wrote a small Python CLI that generates a VMess URI and QR code so you can set up a phone client in about ten seconds. Scan the code with v2Box or v2rayNG and you're connected.

Sensible security defaults. SSH is locked to a single IP, inter-node traffic is restricted by security group, and TLS auto-renews via cron. Nothing is left open that doesn't need to be.

Stack

Terraform • AWS (EC2, Elastic IP, Security Groups) • V2Ray • Nginx • Docker • Certbot • Cloudflare DNS • Python

Source

The project is on GitHub: v2ray-dual-region.

Terraform
AWS
Networking
Docker
DevOps

Related