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:
- Privacy-conscious users can't use DNS-over-HTTPS or other secure DNS solutions
- 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