<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: xeptore</title>
    <description>The latest articles on DEV Community by xeptore (@xeptore).</description>
    <link>https://dev.to/xeptore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3303419%2F4289bdba-3af1-4a21-9c2e-3fabc372585b.png</url>
      <title>DEV Community: xeptore</title>
      <link>https://dev.to/xeptore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xeptore"/>
    <language>en</language>
    <item>
      <title>WireGuard DoV (DNS-over-VPN)</title>
      <dc:creator>xeptore</dc:creator>
      <pubDate>Sun, 29 Jun 2025 08:42:20 +0000</pubDate>
      <link>https://dev.to/xeptore/wireguard-dov-dns-over-vpn-ell</link>
      <guid>https://dev.to/xeptore/wireguard-dov-dns-over-vpn-ell</guid>
      <description>&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;To preserve DNS privacy, and prevent sniffing/spoofing, which is a common technique used by ISPs to restrict internet access in countries like mine, we can tunnel DNS traffic through WireGuard and use a remote, private, and trusted DNS server. Keep in my that this will &lt;strong&gt;only tunnel DNS traffic&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;In this guide, I'll use &lt;a href="https://github.com/AdguardTeam/dnsproxy/" rel="noopener noreferrer"&gt;Adguard dnsproxy&lt;/a&gt; as the DNS resolver, which is easy to setup and get it to work. Of course you can use any DNS server of your choice. Some alternatives that I can remember are (in no specific order):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/DNSCrypt/dnscrypt-proxy" rel="noopener noreferrer"&gt;dnscrypt-proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://coredns.io/" rel="noopener noreferrer"&gt;CoreDNS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nlnetlabs.nl/projects/unbound/about/" rel="noopener noreferrer"&gt;Unbound&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WireGuard Server Config
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# wgdns.conf
&lt;/span&gt;[&lt;span class="n"&gt;Interface&lt;/span&gt;]
&lt;span class="n"&gt;PrivateKey&lt;/span&gt; = &lt;span class="n"&gt;server_prv&lt;/span&gt;
&lt;span class="n"&gt;Address&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;/&lt;span class="m"&gt;24&lt;/span&gt;
&lt;span class="n"&gt;Address&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;/&lt;span class="m"&gt;32&lt;/span&gt;
&lt;span class="n"&gt;ListenPort&lt;/span&gt; = &lt;span class="m"&gt;64539&lt;/span&gt;
&lt;span class="n"&gt;MTU&lt;/span&gt; = &lt;span class="m"&gt;1280&lt;/span&gt;

[&lt;span class="n"&gt;Peer&lt;/span&gt;]
&lt;span class="n"&gt;PublicKey&lt;/span&gt; = &lt;span class="n"&gt;client_pub&lt;/span&gt;
&lt;span class="n"&gt;PresharedKey&lt;/span&gt; = &lt;span class="n"&gt;psk&lt;/span&gt;
&lt;span class="n"&gt;AllowedIPs&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;.&lt;span class="m"&gt;2&lt;/span&gt;/&lt;span class="m"&gt;32&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subnet &lt;code&gt;10.3.3.0/24&lt;/code&gt; is used for WireGuard peers, and &lt;code&gt;10.0.0.1/32&lt;/code&gt; will be assigned to the DNS server.&lt;/p&gt;

&lt;h2&gt;
  
  
  WireGuard Client Config
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;Interface&lt;/span&gt;]
&lt;span class="n"&gt;PrivateKey&lt;/span&gt; = &lt;span class="n"&gt;client_prv&lt;/span&gt;
&lt;span class="n"&gt;Address&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;.&lt;span class="m"&gt;2&lt;/span&gt;/&lt;span class="m"&gt;32&lt;/span&gt;
&lt;span class="n"&gt;MTU&lt;/span&gt; = &lt;span class="m"&gt;1280&lt;/span&gt;
&lt;span class="n"&gt;DNS&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;

[&lt;span class="n"&gt;Peer&lt;/span&gt;]
&lt;span class="n"&gt;PublicKey&lt;/span&gt; = &lt;span class="n"&gt;server_pub&lt;/span&gt;
&lt;span class="n"&gt;PresharedKey&lt;/span&gt; = &lt;span class="n"&gt;psk&lt;/span&gt;
&lt;span class="n"&gt;AllowedIPs&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;/&lt;span class="m"&gt;32&lt;/span&gt;
&lt;span class="n"&gt;PersistentKeepalive&lt;/span&gt; = &lt;span class="m"&gt;15&lt;/span&gt;
&lt;span class="n"&gt;Endpoint&lt;/span&gt; = &lt;span class="n"&gt;server_ip&lt;/span&gt;:&lt;span class="m"&gt;64539&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  dnsproxy
&lt;/h2&gt;

&lt;p&gt;Download and extract the &lt;a href="https://github.com/AdguardTeam/dnsproxy/releases/latest/" rel="noopener noreferrer"&gt;latest release&lt;/a&gt; of &lt;code&gt;dnsproxy&lt;/code&gt;, and spin it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./dnsproxy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cache&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ipv6-disabled&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pending-requests-enabled&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--refuse-any&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--udp-buf-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-l&lt;/span&gt; 10.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 53 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; udp://1.1.1.1:53 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; udp://1.0.0.1:53
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with equivalent config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.yaml&lt;/span&gt;
&lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;ipv6-disabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;pending-requests-enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;refuse-any&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;udp-buf-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
&lt;span class="na"&gt;listen-addrs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.1&lt;/span&gt;
&lt;span class="na"&gt;listen-ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt;
&lt;span class="na"&gt;upstream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;udp://1.1.1.1:53&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;udp://1.0.0.1:53&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Firewall Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Allow WireGuard inbound connections:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw rule allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 proto udp to SERVER_PUBLIC_IP/32 port 64539 comment &lt;span class="s1"&gt;'WireGuard Inbound'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Where &lt;code&gt;eth0&lt;/code&gt; is the public-facing network interface of the server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow WireGuard peers to query DNS server:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw rule allow &lt;span class="k"&gt;in &lt;/span&gt;on wgdns proto udp from 10.3.3.0/24 to 10.0.0.0/24 port 53 comment &lt;span class="s1"&gt;'WireGuard DNS'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Where &lt;code&gt;wgdns&lt;/code&gt; is the name of WireGuard interface.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Spinning up the &lt;code&gt;dnsproxy&lt;/code&gt; server(s) should happen after WireGuard interface is up as IP(s) are not available otherwise. One solution for having a separate DNS interface is to use the &lt;code&gt;dummy&lt;/code&gt; network kernel module like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;add dummy0 &lt;span class="nb"&gt;type &lt;/span&gt;dummy
ip addr add 10.0.0.1/32 dev dummy0
ip &lt;span class="nb"&gt;link set &lt;/span&gt;dummy0 mtu 1280 up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;dummy&lt;/code&gt; module isn't already enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;modprobe dummy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: This is not persistent against reboots. Ask ChatGPT for ways to make it so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Forward
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You can spin up multiple &lt;code&gt;dnsproxy&lt;/code&gt; servers on different IPs. In order for peers to access it, you'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assign a new IP to the DNS interface (&lt;code&gt;wgdns&lt;/code&gt; or &lt;code&gt;dummy0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Apply the respective firewall rule&lt;/li&gt;
&lt;li&gt;Update both &lt;code&gt;DNS&lt;/code&gt;, and &lt;code&gt;AllowedIPs&lt;/code&gt; of peers to match the new DNS server IP&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;You can use port-forwarded UDP, or TCP (DoH, or DoT for example) ports instead of local process to escape network restrictions (e.g., by using tools like &lt;a href="https://github.com/rathole-org/rathole/" rel="noopener noreferrer"&gt;rathole&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;dnsproxy Systemd service template: &lt;a href="https://github.com/xeptore/gist/blob/main/systemd/dnsproxy@.service" rel="noopener noreferrer"&gt;https://github.com/xeptore/gist/blob/main/systemd/dnsproxy@.service&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wireguard</category>
      <category>dns</category>
      <category>privacy</category>
      <category>vpn</category>
    </item>
  </channel>
</rss>
