How to Allow OpenVPN Clients to Override Server-Pushed DNS Settings


2 views

When configuring OpenVPN, the server-side DNS push (push "dhcp-option DNS") creates a common pain point: technically-inclined users lose the ability to use their preferred DNS servers. The current behavior forces all clients to use the server-specified DNS, which persists even if users attempt local DNS modifications.

The standard approach creates two problems:

  1. Privacy-conscious users can't use DNS-over-HTTPS or other secure DNS solutions
  2. Network administrators need to support non-technical users who would otherwise have broken DNS resolution

For Windows clients, create a custom connection script:

# client.ovpn addition
script-security 2
up "C:\\Program Files\\OpenVPN\\config\\set_dns.cmd"
down "C:\\Program Files\\OpenVPN\\config\\reset_dns.cmd"

Example set_dns.cmd:

@echo off
for /f "tokens=2 delims=:" %%A in ('ipconfig /all ^| find "DNS Servers"') do (
  set LOCAL_DNS=%%A
)
netsh interface ip set dns name="TAP-Windows Adapter" static %LOCAL_DNS% primary
#!/bin/bash
# Add to client config
up "/etc/openvpn/update-resolv-conf"
down "/etc/openvpn/update-resolv-conf"

# Sample update-resolv-conf
#!/bin/sh
for optionname in ${!foreign_option_*} ; do
  option="${!optionname}"
  if echo "$option" | grep "dhcp-option DNS" ; then
    DNS=($(echo "$option" | awk '{print $3}'))
  fi
done
networksetup -setdnsservers "Thunderbolt Ethernet" $LOCAL_DNS_SERVER

For administrators who control both ends:

# Conditional DNS pushing based on client config
client-config-dir /etc/openvpn/ccd
auth-user-pass-verify /etc/openvpn/check-dns-preference via-file

Example check-dns-preference script:

#!/bin/bash
if grep -q "CUSTOM_DNS=1" $1; then
  echo "push \"dhcp-option DNS 1.1.1.1\"" > $client_config
else
  echo "push \"dhcp-option DNS 8.8.8.8\"" > $client_config
fi

On Linux servers, consider DNS interception:

iptables -t nat -A PREROUTING -i tun+ -p udp --dport 53 -j DNAT --to-destination 8.8.8.8
iptables -t nat -A PREROUTING -i tun+ -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8

Always verify with:

# Windows
nslookup example.com

# Linux/Mac
dig +short example.com

When managing an OpenVPN server, we often face a paradox: we need to push DNS servers for non-technical users who wouldn't configure their own, while still allowing power users to override these settings. The standard push "dhcp-option DNS x.x.x.x" directive makes this impossible by design - it forcibly sets DNS servers on clients.

The most elegant solution involves client-side scripts that run after the VPN connection establishes. Here's how to implement it:


# Client configuration file (client.ovpn)
script-security 2
up "/etc/openvpn/update-resolv-conf.sh"
down "/etc/openvpn/update-resolv-conf.sh"

For Windows clients, you'll need a similar approach with a PowerShell script:


# update-dns.ps1
$newDNS = "1.1.1.1" # Client's preferred DNS
$interface = Get-NetAdapter | Where-Object {$_.InterfaceDescription -like "*TAP-Windows*"}
Set-DnsClientServerAddress -InterfaceIndex $interface.ifIndex -ServerAddresses $newDNS

We can make the server smarter by implementing conditional DNS pushing based on client certificates or usernames:


# /etc/openvpn/scripts/clientconnect.sh
#!/bin/bash
if grep -q "DNS_OVERRIDE=1" <<< "$common_name"; then
    echo "No DNS push for override-enabled clients" >> /var/log/openvpn.log
else
    echo "push \"dhcp-option DNS 8.8.8.8\"" > /tmp/dns_$common_name
    echo "push \"dhcp-option DNS 8.8.4.4\"" >> /tmp/dns_$common_name
fi

For Mac users, the resolvconf approach works well, but we need to handle System Integrity Protection (SIP):


# macOS client configuration
setenv PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
up "/usr/local/bin/openvpn-dns.sh"
down "/usr/local/bin/openvpn-dns.sh"

After implementing any solution, verify with:


# Linux/macOS
dig +short myip.opendns.com @resolver1.opendns.com

# Windows
nslookup myip.opendns.com resolver1.opendns.com

Remember that DNS changes might require flushing the cache:


# Windows
ipconfig /flushdns

# macOS
sudo killall -HUP mDNSResponder

# Linux (systemd-resolved)
sudo systemd-resolve --flush-caches