Automate the Deployment of a WireGuard VPN on Linode
Disclosure. This page contains links to products that may earn us a small commission at no extra cost to you, should you click on them and make a purchase. Read full disclosure.
This tutorial will show you how to automate the deployment of a WireGuard VPN so you can use it to gain access to Geo-Blocked services or secure your internet connection when using a public network.
Why Self-Host Your VPN?
There are lots of VPN providers, as I’m sure you’re aware, from all the ads on YouTube, podcasts and blogs etc. These services are easy to use, but they are more expensive than hosting your own.
Also, it’s debatable they improve your privacy because you never know if they were are being created to act as a honey pot.
If your hosting provider has an API, you can make it easy to script the deployment and configuration of the VPN. You can even delete it after each use to make your monthly bill cheaper.
In this post, I will show you how to use a shell script to make it easy to deploy a WireGuard VPN to Linode.
Before you begin
The script we are going to create requires two Unix utilities (wireguard-tools
and jq
) that might need to be installed on your machine. You can install them on macOS with Homebrew using the commands below.
brew install wireguard-tools
brew install jq
If you’re not using a Mac, check the documentation on how to install these utilities on your operating system.
Step 1: Create Linode Personal Access Token
Log in to your Linode account and create a Personal Access Token using the steps in this tutorial. Save the token, we will use it when configuring the script in the next step.
Step 2: Create the Bash Shell Script
Create a file called wgl and add the following code.
#!/bin/bash
#
# A Script to Deploy a Linode Intance with WireGuard Installed
#
# Dependencies:
# - wireguard-tools
# - jq
CONFIG_DIR=~/.wgl
# Create config directory and files if they don't exist
mkdir -p $CONFIG_DIR
if test -f "${CONFIG_DIR}/config"; then
source "${CONFIG_DIR}/config"
else
# Generate WireGuard Server Key Pairs
wg genkey | tee "${CONFIG_DIR}/server.key" | wg pubkey > "${CONFIG_DIR}/server.pub"
wg genkey | tee "${CONFIG_DIR}/client.key" | wg pubkey > "${CONFIG_DIR}/client.pub"
cat <<EOT > "${CONFIG_DIR}/config"
TOKEN=""
WG_SERVER_PRIVATE_KEY="$(cat ${CONFIG_DIR}/server.key)"
WG_SERVER_PUBLIC_KEY="$(cat ${CONFIG_DIR}/server.pub)"
WG_CLIENT_PRIVATE_KEY="$(cat ${CONFIG_DIR}/client.key)"
WG_CLIENT_PUBLIC_KEY="$(cat ${CONFIG_DIR}/client.pub)"
REGION="ap-southeast"
NAME="wireguard"
STACKSCRIPT_ID="963850"
EOT
echo "config file created at ${CONFIG_DIR}/config"
echo " assign your Linode API Token to the TOKEN variable in the config file then run the script again"
exit 0
fi
if [ "$1" == "deploy" ]; then
# assign a secure random password to the root user
password="$(dd if=/dev/urandom bs=1 count=20 2>/dev/null | base64)"
# use curl to request the Linode API to create an instance
result=$(curl -s "https://api.linode.com/v4/linode/instances" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-X POST -d '{
"image": "linode/ubuntu20.04",
"root_pass": "'${password}'",
"stackscript_id": '${STACKSCRIPT_ID}',
"stackscript_data": {
"wg_server_private_key": "'${WG_SERVER_PRIVATE_KEY}'",
"wg_client_public_key": "'${WG_CLIENT_PUBLIC_KEY}'"
},
"booted": true,
"label": "'${NAME}'",
"type": "g6-nanode-1",
"region": "'${REGION}'"
}')
# search for errors in response and print reason
if [[ "$result" == *"errors"* ]]; then
err=$(echo -e "$result" | jq -r ".errors[].reason")
echo "error: $err"
exit 1
fi
# print the public IP address of the Linode
ip=$(echo -e "$result" | jq -r ".ipv4[]")
cat <<EOT > "${CONFIG_DIR}/client.conf"
[Interface]
PrivateKey = ${WG_CLIENT_PRIVATE_KEY}
Address = 10.1.1.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = ${WG_SERVER_PUBLIC_KEY}
AllowedIPs = 0.0.0.0/0
Endpoint = ${ip}:51820
PersistentKeepalive = 15
EOT
echo "VPN deployed. Use the following client.conf to connect"
echo "$(cat ${CONFIG_DIR}/client.conf)"
exit 0
fi
if [ "$1" == "destroy" ]; then
# get the linode id from the name
result=$(curl -s "https://api.linode.com/v4/linode/instances" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H 'X-Filter: { "label": "'${NAME}'"}')
# search for errors in response and print reason
if [[ "$result" == *"errors"* ]]; then
err=$(echo -e "$result" | jq -r ".errors[].reason")
echo "error: $err"
exit 1
fi
id=$(echo -e "$result" | jq -r ".data[].id")
curl "https://api.linode.com/v4/linode/instances/${id}" \
-H "Authorization: Bearer $TOKEN" -X DELETE
exit 0
fi
echo "invalid argument try one of the following:"
echo " deploy Deploy a new WireGuard VPN on Linode"
echo " destroy Destroy existing WireGuard VPN"
exit 1
Make the file executable with.
chmod +x wgl
Step 3: Run the Script
Run the script for the first time.
./wgl

As you can see from the output above, you are asked to update the config file located at ~/.wgl/config
so that the TOKEN
variable contains the value generated in Step 1.

The remaining variables can be left alone. However, you might want to change them if you have your own WireGuard Key-Pairs, or if you want the Linode deployed to a different region.
Step 4: Deploy the WireGuard Linode Instance
Let’s deploy the Linode by running the following command.
./wgl deploy

If your access token is valid, your VPN will deploy, and the output will show the WireGuard client configuration. You can use this to set up your WireGuard client on whatever device you are using.
Step 5: Configure WireGuard Client
WireGuard clients are available for Windows, Linux, iOS, Android and most operating systems. In this tutorial, I will demonstrate using the macOS WireGuard client using the config displayed in the output from the previous step.
Open the App Store, search for WireGuard, install the client then click Open.

Click + then Add Empty Tunnel…

Enter a name for the VPN, for example LinodeVPN, then add the config from the output from the ./wgl deploy
command.
Note: the config is also stored in ~/.wgl/client.conf

Click Activate to connect to the VPN and switch your IP address from your ISPs to the VPNs.

When data is being sent and received, it should be working. We will test it in the next step.

Step 6: Test WireGuard VPN
If everything is configured correctly you should see the Data sent and Data received both have values greater than 0.
You should also see your public IP address has changed when you browse the internet. You can test this by going to https://dnsleaktest.com

As you can see in the image above, the DNS leak test website shows my public IP address has changed to one assigned to a Linode in Australia.
Note: you can use the DNS leak test website to check that your ISP does not see any of your DNS lookups (If they do, your connection is not private).
Let’s test this now by pressing the Standard test button.

The test above shows Cloudflare is the DNS resolver used for my DNS lookups, not my ISP. You should be good to go on public networks or if you want to bypass geo-blocks.
Deleting the VPN
When you are finished using the VPN, you can delete it by running the following command.
./wgl destroy
Conclusion
Self-hosting your VPN is cheaper than signing up for a VPN provider. It’s also not that complicated to set up when you automate the deployment as shown in this tutorial.