Confident Clean a Hacked Site Workshop Join me July 16th for this 90 Minute Workshop  Claim your spot

VPNs and fun experiments with UPnP


Here lies a tale of a fun adventure with helping a friend remotely set up a VPN server, the joys of UPnP and SystemD. It’s a sort of tutorial but more documenting my own notes for if I have to do it again in future.

I want a VPN server Tim

It all started when I was asked “I want a VPN server because I want to be nice and safe when travelling about” said my friend via Skype that bastion of privacy messaging service. Oh sure I said I can whip you up a VPN server and send it to you.

So the goal of the project, a small VPN server sitting inside his home network, which he could then connect to with a remote client (his laptop).

Building a VPN server

This was fairly painless a spare RasberryPi that was lying around the house a copy of Rasbian a Debian based distro to get an OS on there. Turning off the desktop and associated components and setting up SSH access with a key on a random port (this was less about security and more not wishing to avoid conflict should my friend have something running a SSH server). Building the server, I kept in mind I might have little to no access and my friend while having physical access would at best be able to type careful commands via a console if lucky.

For the VPN server it self I used PiVPN which is a configuration tool for OpenVPN server installation is painless though has one of my biggest pet peeves. The install instructions are:

curl -L | bash

So let’s be clear a security product wants me to install a shell script from a remote source and then execute it without even a cursory look! something-something, grumble, grumble. However, the source is available from Github and once downloaded and piped through Bash like a dutiful sheep, using PiVPN is super easy and configuring the VPN is minimal.

Testing the VPN

Setting up the VPN probably took all of twenty minutes, and testing I went to my router control panel and went to open a port. That’s when the little voice started going… You’re going to have to talk him through port forwarding Tim. Now my friend is technical(ish) taking the Pi and plugging it in would not be an issue. Installing a OpenVPN client and following instructions again not an issue. The problem was I had no idea what router he had and from the discussion that followed it appeared to be a generic box. One he wasn’t even sure of the password. Given he used Steam and Skype I made a wild assumption that it supported UPnP and so decided to simply open the ports that would be needed via UPnP.


Universal Plug and Play is a set of protocols that lets applications talk to switches and routers to enable port forwarding its actually way more then that, its a communication layer for devices to talk to each other. For what I want it should allow me to request that router open and allow traffic to and from the Pi on several ports.

The ease you can basically break out of a network using UPnP is frightening and I think I should say that my advice is:

Good friends don’t let their friends have UPnP enabled

However I’m not a good friend

UPnP clients

I initially looked at writing a small script to open UPnP most programming languages have classes for working with UPnP. However to test everything was working I used miniupnp which is a small client and server for talking over UPnP. In the end I just used the client for this project.

Indeed opening a port is as simple as:

upnpc -a $IP 11948 11948 UDP

where $IP is the machine IP that you wish to pass the port to, you can also specify if you want TCP, UDP or both.

There is something very weird about just downloading a networking tool client, running a single command and it just work. I’m use to spending hours dealing with bridges and network interfaces on multiple devices before anything works!

Realising I could just use upnpc to manage the port opening, it was a case of creating an incredibly simple bash script and a couple of SystemD controllers specifically a SystemD service unit and a Timer (to keep periodically checking and repunching the hole/Letting the remote DNS.

Everyone loves SystemD

Setting up SystemD units and timers is fairly trivial first you need to set up a service file, with some basic description, usefully you can specify what your unit both depends on and the minimum point it should be called. For example, in my case, there is no point trying to use it till after networking is up and running.

Description=Hole Punching for OpenVPN

ExecStart=/usr/local/bin/ >> /var/log/holepunch.log 2>&1

You can then specify its “type”, in this case, a one-off activity because we are going to then use the timer to call it, rather than a service. Finally you tell it what to fire in my case a shell script with it’s output very verbosely going to a log file.

Next up is the timer unit which is a replacement for the cron aspect, in this case the timer is set to wait 5 minutes then run and then rerun every 30 minutes.

Description=Runs and manages Holepunch services

# Time to wait after booting before we run first time
#Run every 30min

Why wait 5 minutes? Yeah I’m lazy and well when I said the networking just worked, working out at what stage it would work took more then 5 seconds, while in theory network-online should have been the correct point and should have been instant, the reality was this didn’t quite behave as expected. the 5 minutes means post reboot the holepunch should have kicked in.

echo $(date -u) Starting Up
IP=$(hostname -I | grep -o '^\S*')
upnpc -a $IP 11948 11948 UDP
upnpc -a $IP 11948 11948 TCP
echo "======= END ======="

The script has been heavily simplified, it does a bunch of other stuff, like sending a CURL request to a VPS to update the DNS on the fly. Originally I was going to put in some basic checks so that upnpc didn’t constantly barrage the router, but in this side project that somehow got missed, going forward if I need to do this again, I will set up some remote testing to make sure the ports are accessible and if not then punch though. By default OpenVPN is set to do UDP and fallback to TCP, originally during testing I was just using UDP but added TCP later after some feedback.

systemctl enable holepunch
systemctl start holepunch

Finally we just enable our service, which also enabled our timer and everything is sorted. One VPN server set up, managing itself.

Loads of improvements I can make, and in the end the core reason for using SystemD timers (to make sure we controlled that everything was ready for our script) actually turned out to not fully work, so a cron might have been easier. I console myself with the belief that in years to come sysadmin Tim will thank me for making it a service so it will be obvious what’s happening. I’m unconvinced, so am writing this blog post to basically remind older me of this.

Helping you and your customers stay safe

WordPress Security Consulting Services

Power Hour Consulting

Want to get expert advice on your site's security? Whether you're dealing with a hacked site or looking to future-proof your security, Tim will provide personalised guidance and answer any questions you may have. A power hour call is an ideal starting place for a project or a way to break deadlocks in complex problems.

Learn more

Site Reviews

Want to feel confident about your site's security and performance? A website review from Tim has got you covered. Using a powerful combination of automated and manual testing to analyse your site for any potential vulnerabilities or performance issues. With a comprehensive report and, importantly, recommendations for each action required.

Learn more

Code Reviews

Is your plugin or theme code secure and performing at its best? Tim provides a comprehensive code review, that combine the power of manual and automated testing, as well as a line-by-line analysis of your code base. With actionable insights, to help you optimise your code's security and performance.

Learn more

Or let's chat about your security?

Book a FREE 20 minute call with me to see how you can improve your WordPress Security.

(No Strings Attached, honest!)