Creating a VPN tunnel via Wireguard #1 (Terraform)

I wanted to create VPN network for my devices. To do this I used Wireguard for tunneling and Vultr for the remote service that I can forward my traffic to. Why Vultr? well because it's cheap :)

I want something very simple like depicted in figure.1 where I bypass my NAT to connect to the instance in Vultr. Wireguard does exactly that! So how do we go about implementing this?


Figure. 1

Well lets start with creating the server instance on Vultr, I would like to create the instance through code so that it's reproducible. We can do that through Terraform.  Vultr provides a Terraform module we can use.


Things to know about Terraform

Resource

Terraform provides a mechanism to describe the instance we would like to create. It's called "resource",
the "Resource" can be anything: the instance type, CPU type, Memory size, or Volume/Volume size or even backup etc. You can see the resource types in Terraform documentation here. All of the cloud providers will have this "resource" type.

Data

What happens when you want to select certain OS, or certain CPU in resource, you would like to provide a unique ID to the Resource. Data type will do exactly that. But instead of you providing a number of sorts, you can use words this way you won't get confused by what is the OS ID 4213?

Variable

Variables can be anything that User provides, for instance your public key that you would like to deploy on the server or the API key you want to provide to Cloud provider.


We now know about the basic concepts we are going to use to build our Terraform code.

We will create a file called init.tf.  This is where we will tell terraform about the provider, and the instance. 

The code below shows that we will use Vultr provider, and we will use instance called "my_cheap_instance", where it's located in Frankfurt Germany and I disabled backup because it costs 1 euro more which I don't want to pay :P

terraform {
required_providers {
vultr = {
source = "vultr/vultr"
version = "2.13.0"
}
}
}

provider "vultr" {
api_key = var.vultr_api_key
}

resource "vultr_ssh_key" "example" {
name = "my-ssh-key"
ssh_key = var.ssh_public_key
}

resource "vultr_instance" "my_cheap_instance" {
region = "fra"
plan = "vc2-1c-1gb"
os_id = data.vultr_os.ubuntu.id
enable_ipv6 = true
backups = "disabled"
user_data = <<-EOF
#cloud-config
runcmd:
- apt install wireguard
- apt install wireguard-tools
- echo net.ipv4.ip_forward=1 > /etc/sysctl.conf
- echo net.ipv6.conf.all.forwarding=1 >> /etc/sysctl.conf
- sudo sysctl -p
- sudo ufw allow 51820/udp
- sudo ufw disable
- sudo ufw enable
EOF
ssh_key_ids = [vultr_ssh_key.example.id]
}

output "vpn_ip" {
value = vultr_instance.my_cheap_instance.main_ip
}


"User-data" property allows us to provide a cloud-init config. Where I would like to install wireguard, and enable the IPv4 and IPv6 forwarding. And I also would like to enable 51820 port in the firewall. 

Now As you can see we used "data" and "variable" types. Namely "data" ubuntu gives back the OS ID of the ubuntu 25.04.
data "vultr_os" "ubuntu" {
filter {
name = "name"
values = ["Ubuntu 25.04 x64"]
}
}

variable "ssh_public_key" {
type = string
default = "SSH_PUBLIC_KEY"
}
variable "vultr_api_key"{
type = string
default = "<VULTR_API_KEY>"
}

Where the ssh_public_key provides SSH key of my client machine. Here in this post I removed the Public key because I don't want to expose it on the internet :-)
Vultr_api_key is a token I generated from Vultr web interface which is a top secret token you should never expose to anywhere!!!

So with that, you can create your instance on Vultr. 

To initiate the terraform we call: terraform init. This will download all of the dependency and create directories we may require 

Then we run terraform plan: This will show what kind of resources, the terraform will create on Vultr. Go through the console logs, make sure it only creates resources you need
Then we run: "terraform apply" to create the resources.

Now we have the server up and running which you can execute remote shell to.


Comments