Home
Admin | Edit

Cheap custom VPN on AWS EC2 and virtualization

Contents

Introduction


I was wondering lately if hosting my OpenVPN server as an AWS EC2 instance would be cheaper than the cheap ~6$ permanent hosting solution i was using since then.

Turns out that yes it seems way cheaper to host it on a small AWS ARM t4g.nano instance (varying from as low as ~0.46$ a month to ~2$ a month) with some small work and being careful on the options. (gp3 storage with very scaled down storage space, no static IP, no snapshots)

05/08/2023 : From Feb 2024, AWS users with public IPv4 addresses will be charged $0.005 per IP per hour. So the monthly expense above may have changed, the solution is to perhaps go for IPV6.

I am generally not a proponent of AWS services for personal projects but it is well adapted for my use case of a shared multi regions VPN server with very infrequent access, the instance is small, cheap and still have more bandwidth / power than the solution i was using so far... it's also way more flexible with huge amount of options if scaling is ever needed. It is also easy to setup as most of the process can be done through the web UI.

VPN setup

There is nothing much to say on the VPN installation as i use an automated setup script, it is trivial to use and provide everything needed to setup a simple VPN server on Linux with good defaults, the script also allow to add VPN users. (it will generate a .ovpn client file)

An AWS EC2 security group must be created for the VPN server to allow inbound access, my security group allow inbound UDP on port 1194 and any outbound connections. I also allow ICMP requests so i am able to check when the instance is up.

The last step was to add some sort of automated script to provide an updated .ovpn file in case of an IP change. (i don't use a static IP to reduce cost) A good and easy automated alternative would be to use a free dynamic DNS service likes no-ip or just use spare domains with some API capabilities.

To solve DNS leak i basically use the same DNS on my client / server. I don't use IPV6.

Some streaming website detect Amazon instances and block them, they can do so through reverse DNS lookup (with a default setup a ping to your instance gives you something like ec2-35-178-79-47-eu-west-2.compute.amazonaws.com), i am not sure there is solutions for the default dynamic IP given by AWS but it could be done (with some fees) with a static Elastic IP.

Some website detect Amazon instances through CIDR IP range, there is no solutions to bypass this as far as i know ?

To reduce the VPN cost further i also added a CloudWatch alarm which terminate the instance automatically when inbound activity drop under a threshold for some hours. (instances are started through the API in my case so i can select which region i want to be on at any times)

Virtualizing


I first tried to virtualize my existing VPN server so i could import it as an image (AMI) and spawn EC2 instances from different regions with the AWS API.

The process of virtualizing (aka cloning) a machine is quite simple and can be useful for backup / archival reasons, you may want to turn any machines and all their content into a "small" portable file which can be deployed again later on.

The steps below are done on Ubuntu but most of it should works on any Linux distributions.

Image creation over SSH


Warning: This step clone the entire machine disk so probably needs plenty storage space; just make sure you have enough.

The command below produce a RAW server.img file which can be converted later on to smaller formats.

ssh -v -i /your/ssh/private/key -p 20000 root@your.server.ip.address "dd if=/dev/sda" | dd of=server.img

There is arguments to select the SSH private key (-i ...) and port (-p ...) but this is not mandatory if the default SSH key / port is ok for you.

Replace /dev/sda by the disk you want to clone; it can be identified by running fdisk -l

The .img is generally not usable directly (and may also takes too much disk space as-is) so a conversion step is needed.

Image conversion


This step uses qemu-img to convert the RAW image file to any of the tool supported formats eg. qcow2, qcow, cow, vdi, vmdk, vpc, cloop.

sudo apt-get install qemu-utils
qemu-img convert -pO vpc server.img server.vhd

VHD format will generally be compatible with Amazon EC2 import utility, the utility also supports OVA, VMDK and RAW format but some of them may fail due to different options.

Alternative

There is also the VBoxManage command (need Virtual Box) which can convert between several formats, below is an example of VMDK to VHD conversion:

VBoxManage clonemedium disk server.vmdk server.vhd --format VHD

AMI import


This step will import the image as an AMI image, it require AWS CLI utilities. (install and quick-start guide)

Some policy setup may be required on your AWS account to allow the commands below to work. (can be done on the web UI)

A S3 bucket must be created first but you can also use an existing one, once created the image file must be uploaded to the bucket. (can be done easily through the S3 web. UI)

Then a file containers.json must be created with this content (makes sure to replace bucket name and eventually description / key to match yours):

[
  {
    "Description": "your description",
    "Format": "vhd",
    "UserBucket": {
        "S3Bucket": "your-s3-bucket",
        "S3Key": "server.vhd"
    }
  }
]

An AMI (Amazon Image) can then be created with AWS CLI (makes sure to change the containers.json path and the region to your preference):

aws ec2 import-image --description "VPN Server" --disk-containers "file:///local/path/to/containers.json" --region eu-west-3

The creation is not immediate, the process can be monitored with a second command (replace import-ami-xx by the id given by the previous command output) :

aws ec2 describe-import-image-tasks --import-task-ids import-ami-0bc0651071016d2 --region eu-west-3

And that is it, the image should appear in the AMI list on the web UI once the import is done, EC2 instances can be created with this image.

The image can also be copied to other regions through the web UI for a multi-regions VPN.

Conclusion


In the end i didn't use the virtualized image at all :-) because the cheapest instances are ARM instances... and my original server was x86 so i reinstalled the VPN on an ARM Amazon Linux instance instead and created an image from this instance once done, the process was faster than virtualizing the original server.

So now i have a cheap on-demand multi-regions VPN and i am quite satisfied of AWS for the performance / cost / flexibility over the hosting solution i was using before.

No leaks ! (DNS are all UK / OpenDNS)

UPDATE: I also did a small web interface (see below) to switch VPN region at will from anywhere, this use Pug to render the page, Node.js with Express.js and the AWS API, it probe regions instances to detect which one is up and  spawn an instance from a launch template when a region is selected, it also terminate all VPN instances when it switch and setup a CloudWatch alarm to terminate the instance automatically when inactive. It is also able to tell if the client IP is connected to the VPN by using NGINX module ngx_http_sub_module to replace a specific string by the client IP and client side JavaScript logic check if it match with the VPN IP.


back to topLicence Creative Commons