Split tunnel OpenVPN client config made easy on macOS

Alan Batruk
AppLearn Engineering
4 min readJun 26, 2023

Recently, I had a painful experience with a VPN. I spent some time resolving it, and I thought I would share my solution with others in case it’s helpful. This will be a short blog post focusing on a specific problem I encountered. I’m aware that there are different ways to solve it, but I’ll only discuss one approach in this post.

Feel free to jump to the conclusions at the bottom if you do not want to read the whole story. You will find there example config and VPN client that work with it.

At AppLearn, we use AWS VPN to connect to various AWS services. This includes internal tools, dashboards, test sites, Cloud IDE, or simply SSHing into a server. We utilise the AWS VPN Client and OpenVPN configuration to establish the VPN connection.

Here is an example of OpenVPN configuration we normally use:

client
dev tun
proto udp
remote example.vpn.com
remote-random-hostname
resolv-retry infinite
nobind
remote-cert-tls server
cipher AES-256-GCM
verb 3

# ca, cert key would be here
# ...
# Other options
# ...

I faced multiple issues with this setup. Firstly, when connected to the VPN, all traffic would pass through it, causing certain applications like MS Teams to disconnect or crash. Additionally, connection speed and latency were affected, impacting activities such as video calls. Initially, I would frequently toggle the VPN connection on and off, but that became inconvenient and if I stayed connected to the VPN for longer period, Teams would lose connection anyway. I also found the AWS VPN Client is slow to connect compared to the OpenVPN Client I had used in the past. Furthermore, the menu bar icon provided no indication of the client’s connection status. Lastly, not all OpenVPN configuration file options were compatible with the AWS VPN Client.

Consequently, I began searching for a solution to these problems. I realised that it would be ideal if only traffic requiring the VPN could be routed through it, while the rest could utilise my regular ISP connection. I turned to the OpenVPN documentation page at https://openvpn.net/community-resources/reference-manual-for-openvpn-2-5/ to explore the possibility of splitting traffic based on the destination IP address. Simultaneously, I aimed to replace the AWS VPN Client due to the reasons mentioned earlier.

My initial choice was the OpenVPN client since I had prior experience with it. However, when I attempted to use it before (after transitioning to the AWS VPN service) I encountered connectivity issues and I left it there.

Upon further investigation, I discovered that the problem was related to DNS. Although the OpenVPN config included the remote-random-hostname option, OpenVPN Client (version 3, at least) did not recognise this option. Fortunately, I found a workaround by prefixing the DNS in the config file with a random subdomain, resolving the problem.

Next, I needed to configure routes to ensure that specific IP addresses would be routed through the VPN. This could be achieved using the route option, which adds a route to the routing table after establishing a connection.

route network/IP
route network/IP netmask
route network/IP netmask gateway
route network/IP netmask gateway metric

For configuring a route to a single IP address, it’s as simple as:

route 123.222.111.213

since the netmask is set to 255.255.255.255 by default.

If you wish to route an IP range, you would need to provide the netmask, for example:

route 10.99.0.0 255.255.0.0

Multiple route options can be used in a config file.

However, this configuration still routes all traffic through the VPN connection.

To split the traffic between VPN and non-VPN, you need to add the route-nopull option. When using this option, ensure that it is placed after all the configured routes in case some routes are already configured on the server side. More information can be found here.

At this point, I was satisfied with the solution as it addressed all the problems I mentioned initially. However, I wanted to further refine it since encountering a new DNS that I wanted to add to the config meant I would have to check its IP address. Moreover, there are often two IP addresses associated with a single DNS.

Continuing my research, I looked into using DNS instead of IP addresses in the OpenVPN config. After a quick read, it seemed that DNS should work the same way as IP addresses. However, it did not function correctly with the OpenVPN client. I attempted to debug the issue and discovered the. allow-pull-fqdn option, which should enable pulling DNS names from the server instead of being limited to IP addresses for — ifconfig, — route, and — route-gateway. Despite enabling this option, it still did not work. I also experimented with the pull-filter ignore “block-outside-dns” option after further debugging and reading, but I had no luck. In the end, I was unable to determine if this was due to the OpenVPN client’s incompatibility with DNS or if it failed to recognize these additional options, or perhaps it was a bug.

As a last resort, I decided to change the VPN client.

I chose Tunnelblick, a highly capable OpenVPN client for macOS.

With this client, remote-random-hostname functions as expected. Additionally, using DNS in the route option instead of IP addresses requires no additional configuration. The client’s GUI interface offers numerous additional options, including the ability to enable/disable traffic through the VPN. However, I could not find a way to add routes through the GUI.

In conclusion, to simplify your experience, opt for the Tunnelblick VPN client. Add the necessary route and route-nopull options to your existing OpenVPN client configuration file, as on the example below, and you should be good to go.

# Your existing config
# ...

# Routing options
route domain.com
route other-domain.com

# Add IP range
route 10.99.0.0 255.255.0.0

route-nopull

--

--