Local DNS Caching on macOS

Created On:

Running a caching DNS resolver on your local machine can speed up all DNS requests. This is because a local DNS resolver can not only cache repeated resolves from a the same application, but also cache requests across different applications. This post will show how to set up a caching resolver on macOS with minimal configuration.

First lets see what DNS resolution is like without caching. With the dig CLI we can see how long it takes to resolve example.com with the freely available 1.1.1.1 resolver from CloudFlare.

$ dig example.com @1.1.1.1

; <<>> DiG 9.10.6 <<>> example.com @1.1.1.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3272
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.           IN  A

;; ANSWER SECTION:
example.com.        81513   IN  A   93.184.216.34

;; Query time: 8 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Sat Jun 05 21:50:24 EDT 2021
;; MSG SIZE  rcvd: 56

It took 8 milliseconds to resolve this record using a very fast resolver. It could be much slower if you are on a slow WiFi connection, or using your ISPs provided defaults.

With DNS caching it looks like this:

$ dig example.com

; <<>> DiG 9.10.6 <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6904
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;example.com.           IN  A

;; ANSWER SECTION:
example.com.        76992   IN  A   93.184.216.34

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Jun 05 21:56:57 EDT 2021
;; MSG SIZE  rcvd: 56

Look at that sweet 0 milliseconds latency.

To achieve the above on macOS it’s very straight forward with dnsmasq. First install dnsmasq with HomeBrew by running:

brew install dnsmasq

Then ensure the DNS server starts up on login and can bind to port 53 by enabling the service as root by running:

sudo brew services start dnsmasq

With the above done the last remaining step is to instruct macOS to use dnsmasq as the primary resolver. The easiest way to do this via the CLI is to use the provided networksetup tool from Apple.

We can list all of the existing DNS severs used for requests on a network interface by running:

sudo networksetup -getdnsservers Wi-Fi

Replace Wi-Fi above with the network interface that you are using. On most Macbooks Wi-Fi is sensible but if you are not using Wi-Fi to connect to the Internet you can see all of the network interfaces by running:

networksetup listallnetworkservices

We can change the DNS servers used for requests by running:

sudo networksetup -setdnsservers Wi-Fi "127.0.0.1" "1.1.1.1"

This will set our primary resolver to the one provided by dnsmasq and a secondary one from Cloudflare. The IP addresses from the above command will also be reflected in /etc/resolv.conf and by running scutil --dns.

The nice thing about the above is how simple it is. When an application wants to resolve a DNS address, it will typically pick the first IP address which is our computer running dnsmasq. When asked to resolve a domain, dnsmasq will check the list of resolvers on the system, skip itself, and pick the next IP address which is 1.1.1.1. dnsmasq will proxy the request to this resolver and cache the response. Subsequent requests will be served from the cache until the record expires.

Note that not specifying a real DNS resolver in addition to 127.0.0.1 will cause dnsmasq to fail to resolve anything. All DNS requests made from your computer will fail until you revert the changes made with networksetup.

With the above done, you can enjoy faster DNS resolution across all applications on macOS.