Setting Up Domain-Based Policy Routing & Split Tunneling on VPN Server
Suppose we have two VPN servers: one located in Hong Kong and another in the United States.
- The HK server runs OpenConnect VPN, which is very effective to bypass the Great Firewall of China.
- HK server and US server are connected over WireGuard VPN tunnel.
What we want to achieve is default traffic from VPN clients in China Mainland goes through the HK VPN and specific domains go through the US VPN, so you can have access to Google Gemini and ChatGPT.
Desired behavior
Default traffic: OpenConnect Client --> HK VPS(ocserv) --> HK Internet Specific domains: (chatgpt.com gemini.google.com play.google.com truthsocial.com, spotify.com) OpenConnect Client --> HK VPS --> WireGuard tunnel --> US VPS --> US Internet
This can be done entirely on the HK server, without any special configuration on the clients.
To follow this guide, I assume you have already set up OpenConnect VPN server on the Hong Kong VPS and a WireGuard tunnel between HK and US VPS. My Hong Kong VPS is running Alma Linux 9. The commands below can be easily adapted to Debian/Ubuntu servers.
Domain → IP set → Policy Routing (recommended)
Linux itself cannot route directly by domain name, because routing decisions occur after DNS resolution, using IP addresses.
The usual approach is:
Domain ↓ DNS resolver ↓ IP set (dynamic) ↓ Firewall marking ↓ Policy routing
Step 1: Configure the OpenConnect VPN Client DNS
Domain-based policy routing only works reliably if OpenConnect clients use The Hong Kong VPN DNS server.
Install dnsmasq on the Hong Kong Server.
sudo dnf install dnsmasq
Start it.
sudo systemctl enable --now dnsmasq
Check status:
sudo systemctl status dnsmasq
If BIND is already running, you need to configure BIND to listen on 127.0.0.1 and dnsmasq listen on the VPN LAN address.
sudo nano /etc/dnsmasq.conf
Find the listen-address directive and add the VPN LAN address (such as 10.10.10.1).
listen-address=10.10.10.1
Also change the
interface=lo
to
interface=vpns0
vpns0 is the ocserv interface. If you want dnsmasq to forward DNS requests to BIND, then tell dnsmasq not to listen on localhost.
except-interface=lo
Then tell dnsmasq to forward DNS to BIND listening on 127.0.0.1.
no-resolv server=127.0.0.1
Save and close the file. Restart dnsmasq.
sudo systemctl restart dnsmasq
Next, make sure the VPN clients are configured to use the VPN DNS resolver. In /etc/ocserv/ocserv.conf set
dns = 10.10.10.1
Step 2: Build a domain-based IP set
In /etc/dnsmasq.conf, add
ipset=/chatgpt.com/usvpn ipset=/auth.chatgpt.com/usvpn ipset=/openai.com/usvpn ipset=/auth.openai.com/usvpn ipset=/claude.ai/usvpn ipset=/claude.com/usvpn ipset=/gemini.google.com/usvpn ipset=/play.google.com/usvpn ipset=/truthsocial.com/usvpn ipset=/spotify.com/usvpn
When clients resolve those domains through your DNS server:
chatgpt.com
↓
DNS lookup
↓
Returned IP automatically added to ipset
Restart dnsmasq.
sudo systemctl restart dnsmasq
Step 3: Create the IPSet
Create a persistent ipset:
sudo ipset create usvpn hash:ip timeout 86400
Check:
sudo ipset list
Output:
Name: usvpn Type: hash:ip
Make ipset persistent
sudo dnf install ipset-service
Enable:
sudo systemctl enable ipset
Save:
sudo ipset save | sudo tee /etc/sysconfig/ipset
Verify ipset population by querying a domain from VPN client:
dig chatgpt.com @10.10.10.1
Then:
sudo ipset list usvpn
You should see:
Members: 142.251.151.2 timeout 86314 104.18.32.47 timeout 86397 142.251.153.2 timeout 86314 142.251.150.2 timeout 86314 142.251.152.2 timeout 86314 172.64.155.209 timeout 86397 142.251.156.2 timeout 86314 142.251.157.2 timeout 86314 142.251.154.2 timeout 86314 142.251.155.2 timeout 86314
Step 4: Create a routing table for the US WireGuard tunnel
Suppose:
WireGuard interface: wg0 US WireGuard peer: 10.0.0.2
Create a routing table:
echo "200 usvpn" | sudo tee /etc/iproute2/rt_tables
Add the default route:
sudo ip route add default via 10.0.0.2 dev wg0 table usvpn
Verify:
ip route show table usvpn
Example output:
default via 10.0.0.2 dev wg0
Step 5: Mark packets using Firewalld
This is where many tutorials become outdated. Since Firewalld on AlmaLinux 9 uses nftables internally, but ipset support is still exposed, use a direct rule.
Create rule (10.10.10.0/24 is the VPN LAN).
sudo firewall-cmd --permanent --direct --add-rule ipv4 mangle PREROUTING 0 -s 10.10.10.0/24 -m set --match-set usvpn dst -j MARK --set-mark 100
Reload:
sudo firewall-cmd --reload
Verify:
sudo iptables -t mangle -L -v
You should see:
MARK set 0x64 match-set usvpn dst
Step 6: Policy routing
Add rule:
sudo ip rule add fwmark 100 table usvpn
Check:
ip rule
Example output:
32765: from all fwmark 0x64 lookup usvpn
Step 7: Disable Reverse Path Filtering for VPN Interface
Find the OpenConnect VPN interface on the Hong Kong server.
ip addr
It’s usually named vpns0. But when there are multiple OpenConnect client connections, there will also be vpns1, vpns2, vpns3..
Edit the /etc/sysctl.d/60-custom.conf file.
sudo nano /etc/sysctl.d/60-custom.conf
Add
net.ipv4.conf.vpns0.rp_filter=0 net.ipv4.conf.vpns1.rp_filter=0 net.ipv4.conf.vpns2.rp_filter=0 net.ipv4.conf.vpns3.rp_filter=0 net.ipv4.conf.vpns4.rp_filter=0 net.ipv4.conf.vpns5.rp_filter=0
Save and close the file. Then apply the changes.
sudo sysctl -p /etc/sysctl.d/60-custom.conf
Step 8: WireGuard Configuration
You need to set AllowedIPs=0.0.0.0/0 and Table = off on the Hong Kong WireGuard node.
[Interface] PrivateKey = *************** Address = 10.0.0.2/24 # Prevent wg-quick from adding default route to the main table Table = off [Peer] PublicKey = *************** Endpoint = *******:51820 # Allow forwarding traffic for IP address of the specified domains. AllowedIPs = 0.0.0.0/0
Step 9: Firewall Configuration on the US Server
My U.S. server is running Ubuntu and UFW firewall.
sudo nano /etc/ufw/before.rules
Allow forwarding traffic from VPN clients. (10.0.0.0/24: WireGuard LAN, 10.10.10.0/24: OpenConnect VPN LAN).
-A ufw-before-forward -s 10.0.0.0/24 -j ACCEPT -A ufw-before-forward -d 10.0.0.0/24 -j ACCEPT -A ufw-before-forward -s 10.10.10.0/24 -j ACCEPT -A ufw-before-forward -d 10.10.10.0/24 -j ACCEPT
Also set up IP Masquerading.
# NAT table rules *nat :POSTROUTING ACCEPT [0:0] -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE -A POSTROUTING -s 10.10.10.0/24 -o eth0 -j MASQUERADE # End each table with the 'COMMIT' line or these rules won't be processed COMMIT
Save and close the file. Then restart UFW.
Step 10: Test Your VPN Policy Routing
Visiting:
www.baidu.com
will follow:
Client -> OpenConnect -> HK VPS -> HK Internet
Visiting:
chatgpt.com
will follow:
Client -> OpenConnect -> HK VPS -> WireGuard -> US VPS -> ChatGPT
Modern alternative: nftables
If you’re using Alma Linux 10 or newer distributions, I would recommend nftables instead of iptables, but ipset also works.
The architecture remains identical:
dnsmasq
↓
ipset (or nft set)
↓
packet marking
↓
policy routing
↓
WireGuard US tunnel
Important requirement: DNS control
This only works reliably if OpenConnect clients use your HK DNS server.
If clients use:
8.8.8.8 1.1.1.1 DoH DoT
the server may never see the DNS responses, and the IP sets won’t be populated correctly.
Many administrators therefore also implement DNS redirection:
VPN clients
↓
Any DNS request
↓
Force redirect to local resolver
Yes. OpenConnect clients can absolutely be subject to domain-based policy routing on the HK server, with selected domains transparently forwarded through a WireGuard tunnel to a US VPS. This is a clean and scalable design that works very well in practice.


