I have a Netgate SG-1000 that serves as my firewall and router for my home network. I use it to protect my home network with my private VPN service and connect my work computer to my work VPN. PfSense makes it easy for me to write rules around what machines and services are allowed to go through which VPN. However, sometimes an OpenVPN will be in a state that it can’t recover from and require me to manually restart the client.

I’ve automated this with a script I’ve adapted from the article: Restarting a VPN Client on pfSense through the CLI, and using cron. To do this, I had to setup SSH access to my device. I also created a new user and set it up with my public SSH key. Then I created this file ensure_vpn.sh in my user’s home directory:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env sh

# Ensure OpenVPN connection
#
# pfSense script that restarts an OpenVPN client if the VPN is down.  Intended
# to be run periodically to help ensure that an OpenVPN connections reestablish.
#
# Required environment variables:
#
#   VPN_ID - the numeric ID of the OpenVPN client
#   PING_HOST - the host to ping to test if the VPN is alive
#
# Find the VPN_ID by running `ifconfig` and looking at the interfaces that start
# with "ovpnc".  Then set VPN_ID to the number in the interface name.  For
# example, if the interface is named "ovpnc2" you would set VPN_ID=2.
#
# The source IPv4 address is looked up using `ifconfig` and if one exists, then
# we attempt to ping the host defined at PING_HOST.  If the source address for
# the VPN interface cannot be found, or the host cannot be pinged, we attempt to
# restart the OpenVPN client associated with the VPN_ID.
#
# Example:
#
#   env VPN_ID=1 PING_HOST=mit.edu ./ensure_vpn.sh
#
# Example cron:
#
#   */5 * * * * root /usr/bin/env VPN_ID=2 PING_HOST=work.com /root/ensure_vpn.sh

if [ -z "$VPN_ID" ] || [ -z "$PING_HOST" ]; then
    echo "Must specify VPN_ID and PING_HOST environment variables"
    exit 1
fi

# src is the source IPv4 address of the VPN interface
src=`/sbin/ifconfig ovpnc${VPN_ID} | grep 'inet\b' | awk '{print $2}'`

if [ -n "$src" ] && /sbin/ping -S "$src" -c 3 "$PING_HOST" > /dev/null; then
    # Success, Nothing to do
    exit 0
else
    # Fail, Reconnect VPN
    /usr/local/sbin/pfSsh.php playback svc restart openvpn client "$VPN_ID"
fi
exit 1

I noticed in the original article I based my script on, that the author said to look at the web interface for the VPN ID. However, I noticed that got me the wrong ID. Instead, I had to look at the number of the interface when running ifconfig. For example, if the interface name is ovpnc2 then I would use 2 as the VPN ID.

Since I have two OpenVPN clients that I want to monitor, I made the script so that it uses environment variables to be configured for each VPN. The environment variables are:

  • VPN_ID - the numeric ID of the OpenVPN client.
  • PING_HOST - the host to ping to test if the VPN is alive.

When running the script, it’ll check to see if these environment variables are set. Then it’ll look for the VPN interface that matches the VPN ID that were provided. If it finds the interface, it attempts to ping the host host provided. If everything succeeds, it will exit with code 0 and do nothing more; otherwise, it will restart the OpenVPN client using pfSsh.php and exit with code 1.

You can test this from the command line by running it like so:

1
$ env VPN_ID=1 PING_HOST=mit.edu ./ensure_vpn.sh

To get this to run periodically, I used cron. First I went into the Web UI and installed the cron package under System -> Package Manager. Then, under Services -> Cron, I added two entries.

For both, I configured both entries like so:

  • Minute: */5
  • Hour: *
  • Day of the Week: *
  • Month of the Year: *
  • Day of the Week: *
  • User: root
  • Command: /usr/bin/env VPN_ID=2 PING_HOST=work.com /home/kiba/ensure_vpn.sh

This adds an entry in /etc/crontab like so:

1
*/5 * * * * root /usr/bin/env VPN_ID=2 PING_HOST=work.com /home/kiba/ensure_vpn.sh

This will run the script every 5 minutes to ensure that the OpenVPN client stays alive.