OPNsense: OpenVPN automatic gateway creation

Note: The title is actually a little bit clickbait. On pfSense the config option is called “gateway creation”. This option is missing on OPNsense. I had to reimplement the functionality.

The audience for this blogpost are advanced users. Knowledge you must have:

If you don’t have the required knowledge you may misconfigure something. This could lead to “leaks”. If in doubt search for another solution! In the post I’m using ExpressVPN as example. However this works for any public VPN provider with dynamic tunnel network assignemt out there.

TL;DR: Every time the OpenVPN tunnel is established I’ve to edit my firewall config (gateway address + outgoing NAT rule) manually. I’ve automated this process.

Detailed problem description

I’ve a subscription for the public VPN provider ExpressVPN. My OPNsense builds the VPN tunnel to one of their servers using OpenVPN but I’m not routing all my traffic over it. In my network I’ve a LAN called ExpressVPN. Every host in that network should be routed over the VPN tunnel to ExpressVPN. This is called source based or policy based routing. I’ve already written a post how I initially set the tunnel up. How is that accomplished? Here is an example firewall rule:

Traffic matching that rule will be routed towards the specified gateway. For that to work I needed to create a gateway prior. I’ve named it GW_ExpressVPN. Every time the tunnel is established I must exchange the address with the correct new one.
As you know ExpressVPN uses the so called net30 method for inside tunnel addressing. After you successfully connected and authenticated against the VPN server it will dictate a /30 IPv4 tunnel network. According to the specification the first usable address will be assigned on the virtual interface on the VPN server. The second usable address will be assigned to the virtual interface on the client. In this case the OPNsense. Example assuming the dictated network is

Servers tunnel address:
Client tunnel address:

If I want to route traffic over the tunnel I must route it to In the screenshot above you see that the right IP address is currently associated with GW_ExpressVPN. However I must also do an outgoing NAT and exchange the source IP address with the local tunnel address in order for the backtraffic being able to reach the OPNsense. Here is how my outbound NAT rules look like:

As you can see I’ve set my local tunnel address as translation target. With that communication in both directions work. Here are the steps in the order I need to do every time the tunnel is established:

  1. First of all I’ve to recognize that the VPN tunnel network changed. This does not happen often but when it does I’m not recognizing it for a couple of days
  2. I’ve to get the new local tunnel address from the OPNsense status page
  3. I’ve to calculate the new gateway address based on the local address (by simply “substracting” 1 from the local address)
  4. I’ve to edit my gateway (GW_ExpressVPN) and set the calculated address there
  5. I’ve to edit my NAT rule and set the local tunnel address as translation target

This needs to be automated!

Solving problem 4 and 5

OPNsense offers a HTTP REST API. But after checking it out I’ve not seen a possibility to edit NAT rules or edit gateway addresses. So this option was quickly dismissed.

Under the hood OPNsense provides/uses configd (🖇️ 🔐) . With the help of so called actions you can trigger different things. Like restarting sshd, list gateways or states, etc. But the required configd actions were not there - yet! I’ve had a look how the web GUI works and read into the PHP source code. I’ve then extracted the functionality and created configd action scripts and opened a pull request (🖇️ 🔐) . I’m currently waiting for feedback from the maintainers. The maintainers won’t merge it. You can read the discussion in the issue (🖇️ 🔐) .
Apparently the right way to do this is to add the interface. After that a gateway is automatically generated. Apparently you only need to add a new NAT rule and set “Interface address” as translation target and create the firewall rule with a set gateway. However after hours of trying and debugging it did not work for me. I gave up and continue using my (working) approach. I’ve published my code in a new GitHub repository (🖇️ 🔐) .

Let’s review the problem list again:

Problem 4 and 5 are solved. Now I just need to automate calling these actions when the tunnel is established.

Solving problem 1, 2 and 3

From my experience with OpenVPN I know that you can specify scripts which are executed after the tunnel is established. From the docs (🖇️ 🔐) I’ve extracted the following:

For –dev tun execute as:
cmd tun_dev tun_mtu link_mtu ifconfig_local_ip ifconfig_remote_ip [ init | restart ]

As you can see OpenVPN passes some information as parameters: Parameter 4 (ifconfig_local_ip) and 5 (ifconfig_remote_ip) are the two addresses which were dictated by the server. This simple shell script takes them and executes my developed configd actions:


if [ -z "$5" ]
	echo "No gateway address passed. Doing nothing"
	exit 1

if [ -z "$4" ]
	echo "No local address passed. Doing nothing"
	exit 1

configctl interface gateways setaddress GW_ExpressVPN $5
configctl nat set_outbound_translation_target ExpressVPN $4

I’ve placed it on my OPNsense firewall under /usr/local/bin/dostuff.sh. Yeah the name is not fitting. Don’t judge me! You can improve it on your system. Don’t forget to chmod 0755 that script so it can be executed. Then all you have left to do is to tell OpenVPN to exectue the script. Edit the OpenVPN client and add the following in the Advanced configuraton textbox: up /usr/local/bin/dostuff.sh

Here is a screenshot to show the whole picture:

After saving my changes I’ve checked the logs and this showed up:

2020-05-26T18:34:00 openvpn[62919]: Initialization Sequence Completed
2020-05-26T18:33:57 openvpn[62919]: /usr/local/bin/dostuff.sh ovpnc5 1500 1557 init
2020-05-26T18:33:57 openvpn[62919]: /sbin/ifconfig ovpnc5 mtu 1500 netmask up
2020-05-26T18:33:57 openvpn[62919]: TUN/TAP device /dev/tun5 opened

Okay the docs were not lying and the script was executed. I’ve checked the NAT rule and the gateway. All addresses were updated and traffic was able to flow without any problems. OpenSource is awesome. Without the ability to read the backend source code I’d not have been able to do this.

Du hast einen Kommentar, einen Wunsch oder eine Verbeserung? Schreib mir doch eine E-Mail! Die Infos dazu stehen hier.

🖇️ = Link zu anderer Webseite
🔐 = Webseite nutzt HTTPS (verschlüsselter Transportweg)