[Tutorial] DNS-over-TLS with dnsmasq and stubby (no need for unbound)


All the guides I see for using DNS-over-TLS on OpenWRT require unbound, what I found out is that in fact you only need stubby, which does the DNS-over-TLS and acts as a proxy for DN resolution.

Stubby is simple to configure and dnsmasq can point to this proxy instead and continue to do all the things it needs to do such as domain name caching.

Because I have this setup running in a old router (4/32) without much space on the overlay to install stubby and its dependencies I have it setup so it installs stubby on startup to the RAM, because of this I'm presenting 2 tutorials, one for a RAM installation and another for a permanent install. All the instructions are for the console, stubby doesn't have a LUCI module so you must be confortable with configuring your router using SSH and Linux.

These configuration are for running DNS-over-TLS in STRICT mode on Cloudfare service.
Strict mode means that only encrypted requests are allowed from a server with a valid certificate.

Install to RAM

Add the following lines to /etc/rc.local (what to do when the router boots)

opkg update
opkg install ca-bundle -d ram
opkg install stubby -d ram
rm /tmp/opkg-lists/*
sleep 5
export PATH="/usr/sbin:/usr/bin:/sbin:/bin:/tmp/usr/bin:/tmp/usr/sbin" && export LD_LIBRARY_PATH="/tmp/lib:/tmp/usr/lib" && /tmp/usr/sbin/stubby -C /etc/myconfig/stubby.yml -g

CA-bundle installs the certificates bundle needed for TLS
The rm command is there to free up some space from the RAM (deletes the OPKG lists as they are no longer needed)
The Export commands are required in order to be able to run applications and libraries installed to the /tmp/ dir (RAM).
Finally it launches stubby, notice the "-C /etc/myconfig/stubby.yml" that is the configuration file (more on that later), you can store permanently as the example in /etc/myconfig (myconfig is an example, just make sure to include the dir in your backup configuration).

1st run:
You should first run those commands by manually via console on your router before having them run automatically at startup.
First, run the opkg commands to install all that we need.
Secondly copy the default config from /tmp/etc/stubby/stubby.yml to your custom config dir in /etc/

Now we need to edit /etc/myconfig/stubby.yml using vi.

#NOTE: See '/etc/stubby/stubby.yml.default' for original config file and descriptions



appdata_dir: "/tmp/stubby"

tls_ca_file: "/tmp/etc/ssl/certs/ca-certificates.crt"



tls_query_padding_blocksize: 128

edns_client_subnet_private : 1

round_robin_upstreams: 0

idle_timeout: 10000

  -  0::1@5453

# IPv6 addresses
# # Cloudflare IPv6
  - address_data: 2606:4700:4700::1111
    tls_port: 853 
    tls_auth_name: "cloudflare-dns.com"

# # Cloudflare IPv6
  - address_data: 2606:4700:4700::1001
    tls_port: 853
    tls_auth_name: "cloudflare-dns.com"

# # Quad 9 IPv6
#  - address_data: 2620:fe::10
#    tls_auth_name: "dns.quad9.net"

# IPv4 addresses
# # Cloudflare servers
  - address_data:
    tls_port: 853
    tls_auth_name: "cloudflare-dns.com"
# # Cloudflare servers     
  - address_data:          
    tls_port: 853                  
    tls_auth_name: "cloudflare-dns.com"

# Quad 9 service
#  - address_data:
#    tls_auth_name: "dns.quad9.net"

dnssec: GETDNS_EXTENSION_TRUE : This line provides DNSSEC validation
appdata_dir: Where the keys for DNSSEC are stored
tls_ca_file is important, this is the name and location of the CA bundle file required for TLS to work.
notice the "listen adresses", 5453 is the port number dnsmasq will have to connect on localhost.

Finally we need to edit dnsmasq.conf in /etc/.
Add the following lines (replace the existing server(s) lines)


If the only "server" line present in the dnsmasq.conf is the one above then all requests from clients will go to
to stubby.

To test the settings:

killall dnsmasq
/etc/init.d/dnsmasq start
export PATH="/usr/sbin:/usr/bin:/sbin:/bin:/tmp/usr/bin:/tmp/usr/sbin" && export LD_LIBRARY_PATH="/tmp/lib:/tmp/usr/lib" && /tmp/usr/sbin/stubby -C /etc/myconfig/stubby.yml -g

It's important to notice, that with this configuration the DNS traffic is only available for clients after stubby is successfully installed and running.
In order for the router to run opkg and set the time after reboot it needs to perform Domain Name resolution even thou Stubby is not running yet, because of that I only have dnsmasq running for the clients, all dn lookups made by the router itself are going to the ISP DN servers that are obtained via DHCP on the WAN side.

If everything is working correctly (you can use http://www.dnsleaktest.com/ to test, note that you will not see the IP but you should see the ISP name as Cloudfare, the IP you will see there is that of the closest cloudflare server and that is a good thing) then you can reboot and have rc.local do the job for you.

Permanent Install

The permanent install is simpler

Run the following commands manually:

opkg update
opkg install ca-certificates
opkg install stubby

Edit /etc/stubby/stubby.yml (see above, but you shouldn't need to set the tls_ca_file), and edit dnsmasq as above.


killall dnsmasq
/etc/init.d/dnsmasq start
/etc/init.d/stubby start
/etc/init.d/stubby enable

Attention: this tutorial is a work in progress and might be incomplete, use at your own risk.

source: https://wiki.archlinux.org/index.php/Stubby

Stubby dns over tls using dnsmasq-full for dnssec & caching
DNS privacy where are we today? 12 dec 2018
Stubby dns over tls using dnsmasq-full for dnssec & caching
DNS resolver options
Stubby dns over tls using dnsmasq-full for dnssec & caching

what's the difference between ca-bundle and ca-certificates package?
Thanks for your tutorial btw.


You're welcome.
Ca-bundle installs all the root certificates in a bundle, one .crt file, ca-certificates installs separate files for each certificate. Ca-bundle ends up occupying less space because of this. cURL (which I have also installed on the same system) works by default with ca-bundle and that's why it's present on the Install to RAM section. Libopenssl which stubby uses works by default with ca-certificates, and as such you don't need to set "tls_ca_file" in the stubby configuration for the permanent install, as it's going to look by default in /etc/ssl/ for the certificates.
I hope that clarifies!


The title just says that unbound isn't needed, not getdns.
When you do 'opkg install stubby' getdns is a dependency and is installed so is libopenssl, this is obvious by just checking the package.

(this post was in response to a post that was deleted)


Dear Specimen,
I deleted my post - sorry for the confusion. Again, I meant no ill will. I explained it over on the "Adding DNS-Over-TLS support to OpenWrt (LEDE) with Unbound" tutorial ( which I authored ) where I responded to you earlier today.
Peace and God Bless,



Thank you for your understanding, :slight_smile:


Dear Specimen,
If you need more storage and swap memory for your router see here: http://ediy.com.my/index.php/blog/item/118-how-to-increase-storage-on-tp-link-tl-mr3020-with-extroot and here: https://samhobbs.co.uk/2013/11/more-space-for-packages-with-extroot-on-your-openwrt-router. Maybe you wish to add this to your tutorial or use this method yourself. Just a suggestion.


Thank you but no need. These should be separate tutorials. Also requires a USB port, of course, not all of these routers have it.


Dear Specimen,
Hello and I hope that you are well. I communicated to you earlier ( if you remember ) that I should be more " open-minded ". So with that in mind, I followed your guide and went ahead and followed your very informative guide and set up DNS OVER TLS using just stubby and dnsmasq.
Things went very well overall. I did not install this to ram - I used my WRT1900ACS V2 with USB drive attached. Also, I use a VPN client so I hit a small hiccup there but was able to get over that small hurdle.
I wish to commend you on a fine guide / tutorial and I wonder if you would mind writing up a similar tutorial which is designed for a more standard setup using Stubby and DNSMASQ in mind. Specifically, I would address with VPN piece as many users implement this option in addition to DNS encryption.
I propose a separate tutorial as I increased cache size for dnsmasq and other features and do not wish to step on your work rather to enhance it for others who may have more standard memory and storage options available on their routers while running OpenWrt.
Call it a collaboration. In any event - thanks for your work. As I said earlier, I am all about advancing DNS OVER TLS ( DNSPRIVACY ) for all. See some of my other work here:
https://forum.opnsense.org/index.php?topic=8579.0 https://forum.opnsense.org/index.php?topic=8759.0
Proper Setup For New Native Unbound DNS-Over-TLS Feature Starting With UNBOUND 1.7.1
Call me a proponent and advocate for internet freedom - again - I thank you for your work.



Thank you Max,

I'm open for collaboration, I saw your new tutorial, personally I would add DNSSEC support (all upstream servers would have to support it, Cloudflare and Quad9 at least do).


Hi thanks for the quick guide. Could you tidy it up a bit tho? Like use code tags pleas. It will be a big help to people like me using screen readers and people who want to copy and paste. Thank you.


What do you mean? I do use code tags (preformatted text) in the tutorial.


Max, can you check the memory usage of the stubby process over time? I think there might be a memory leak, I've seen VSZ inflate by 50% over a period of 24 hours.


Dear Specimen,
Hello and I hope this reply finds you doing well. I went ahead and changed the DNS Privacy Resolvers where all of them support DNSSEC upstream as you advised.
You know that anyone may check out the status of DNS Privacy Resolvers and their working features in real time @ Project dnsprivacy-monitoring available here: https://dnsprivacy.org/jenkins/job/dnsprivacy-monitoring/ By the way - do you have any ideas regarding exactly how to replace dnsmasq with dnsmasq-full ? I do not know if this is difficult to do or set up; however, I read that dnsmasq-full is more well suited for DNSSEC.
Thanks for being agreeable to our working together as it were. I have to setup "DNS-over-TLS with dnsmasq and stubby (no need for unbound)" all over again as I am hoping to see if I am able to come up with with a way to incorporate dnsmasq-full.
I will check memory usage once I get setup up and running and report back to you with my findings at thattime.




You don't need dnsmasq full, the DNSSEC validation is being done by stubby, that's what the setting "proxy-dnssec" in dnsmasq configuration means:

From dnsmasq man page:

--proxy-dnssec Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it. This is an alternative to having dnsmasq validate DNSSEC, but it depends on the security of the network between dnsmasq and the upstream servers, and the trustworthiness of the upstream servers.

You can test DNSSEC validation here (you shared this link in your tutorial): https://cmdns.dev.dns-oarc.net/


Hi, Specimen. Thanks for sharing your tutorial.
Sorry for the slightly irrelevant question. How to upgrade packages on 4/32 devices?


4/32 is very limited, we know that, so there isn't much we can do about it.
You don't really have space for upgrading existing packages to the permanent memory, you have a small overlay. The only thing you can do is reflash with an image that has the packages' latest version.
And upgrading a package to the RAM (-d ram) seems a very icky process, but basically would require uninstalling the package (in the overlay this doesn't actually free space, just marks those files are non-existent in the overlay) and then installing the latest version to the RAM. But I would really not recommend.
What I recommend for 4/32 devices is to build your own image with imagebuilder (and get used to the console, you're going to have to say bye bye to LUCI).


I set this up as described in the original post. I noticed something peculiar. If you enable dnssec, cloudflare fails to detect that you are using their server. The website is . If you dont' enable dnssec, it correctly identifies things. Not sure if there is some config issue, or just an issue with cf's website.


Interesting finding, you can check which dns servers you are using using these links: https://www.dnsleaktest.com/ https://cmdns.dev.dns-oarc.net/ .

And yes, for me, I'm using DNSSEC and the page you posted shows as not connected to, but these other two sites clearly show Cloudflare local servers. I suggest you post in their forums about this.


One limitation of this approach, which is already mentioned, that I noticed is that the router itself doesn't use this dns path. The way it affects me is that sometimes I use my router as a simple socks proxy over ssh. i.e., I do ssh -D 1080 router, and point my browser to the socks proxy. In that scenario dns goes to ISP's dns.