Configure a DNS Caching Server with dnsmasq on RHEL 10

Deepak Prasad
Tested on RHEL 10.2 (Coughlan) — vm1.lab.example (192.168.56.116)
Package dnsmasq-2.90-7.el10_2.1
bind-utils-9.18.33-15.el10_2.12
NetworkManager-1.56.0-1.el10
Applies to RHEL 10, Rocky Linux 10, AlmaLinux 10, Oracle Linux 10, CentOS Stream 10
Privilege sudo or root
Scope Run dnsmasq as a local DNS cache on 127.0.0.1 for the same RHEL host (not a LAN-wide caching resolver). Covers standalone dnsmasq.service, upstream files, NetworkManager, and verification. Does not cover dnsmasq DHCP, TFTP, or PXE — keep those as a separate design (see Kea DHCP).
Related guides Configure BIND on Linux
DNS troubleshooting with Wireshark
dhclient removed in RHEL 10
systemctl command
nmcli examples
Short on time?Beta Get the gist of this 10-minute read in seconds, then ask follow-up questions

dnsmasq is a small forwarder that answers DNS on the host, caches responses, and sends cache misses to upstream nameservers you choose. In this guide, “DNS caching server” means a resolver cache for this RHEL machine only. Because dnsmasq listens on 127.0.0.1, other hosts on the network cannot query your cache unless you deliberately widen listen-address (not covered here).


Quick answer

Without a local cache, repeated DNS queries normally go to an upstream resolver. dnsmasq stores successful and negative responses for their TTL. As long as the record is still cached, dnsmasq can answer it locally instead of asking the upstream DNS server again. That does not extend TTL beyond what the upstream zone published unless you change TTL behavior on purpose.

For a local DNS cache on RHEL 10:

  1. Install dnsmasq and bind-utils.
  2. Configure dnsmasq to listen only on 127.0.0.1.
  3. List upstream DNS servers in /etc/resolv.dnsmasq.
  4. Start dnsmasq and verify 127.0.0.1:53 is listening.
  5. Point the host resolver at 127.0.0.1 through NetworkManager.
  6. Verify forwarding and cache hits with dig (and optional tcpdump).
        Application
             ↓
      127.0.0.1:53
             ↓
       dnsmasq cache
             ↓ cache miss
      Upstream DNS

This guide runs standalone dnsmasq.service. RHEL can also let NetworkManager start its own dnsmasq instance (dns=dnsmasq under [main]); that is a different setup. Use one approach or the other — do not run both on the same loopback port.


Test environment

Item Value
Host RHEL 10.2, vm1.lab.example, 192.168.56.116
Upstream DNS Active Directory DNS 192.168.56.128 (lab.example)
Lab interface enp0s8 (connection profile name matches device in this lab)
dnsmasq 2.90-7.el10_2.1
NetworkManager 1.56.0-1.el10

Standalone dnsmasq vs NetworkManager dnsmasq mode

This guide enables dnsmasq.service directly and manages upstreams in /etc/resolv.dnsmasq.

RHEL also supports NetworkManager-controlled dnsmasq: set dns=dnsmasq in NetworkManager configuration, and NetworkManager starts dnsmasq on loopback and can place 127.0.0.1 in /etc/resolv.conf. That mode is useful when you want NetworkManager to own split-DNS forwarding.

Do not enable NetworkManager’s dnsmasq plugin and a separate system dnsmasq service that both bind to 127.0.0.1:53.


Prerequisites

  • Outbound reachability to at least one upstream resolver you trust.
  • Only one listener on 127.0.0.1:53 for the cache role. libvirt, PXE/dhcp drop-ins, or a second dnsmasq process can conflict (see Troubleshooting).
  • An active RHEL subscription or configured repository before dnf install. See Subscription Manager or fix disabled repos if installs fail.

Install dnsmasq and dig

Install the caching forwarder and dig from bind-utils:

bash
sudo dnf install -y dnsmasq bind-utils
output
Installed:
  dnsmasq-2.90-7.el10_2.1.x86_64
Complete!

If dnsmasq is already installed, dnf may report Nothing to do. — that is fine.

Check the package versions:

bash
rpm -q dnsmasq bind-utils
output
dnsmasq-2.90-7.el10_2.1.x86_64
bind-utils-9.18.33-15.el10_2.12.x86_64

Choose upstream DNS servers

Put upstream DNS servers in /etc/resolv.dnsmasq using the same nameserver syntax as /etc/resolv.conf. That keeps upstreams easy to change without editing the rest of the dnsmasq configuration.

For many labs and AD-backed networks, point dnsmasq at your domain DNS server and let that server resolve or forward public names:

bash
sudo tee /etc/resolv.dnsmasq <<'EOF'
nameserver 192.168.56.128
EOF

Replace 192.168.56.128 with your BIND, Active Directory, or corporate resolver.

If the upstream is Active Directory DNS, prefer only that server here. Do not add a public resolver such as 1.1.1.1 as a generic “backup” for an AD namespace unless you understand split-DNS behavior — dnsmasq may send queries to any configured upstream according to its server-selection rules, and public DNS must not answer private AD-only names.

Check the file before continuing:

bash
cat /etc/resolv.dnsmasq
output
nameserver 192.168.56.128

Optional: different DNS servers for different domains

If different domains must use different DNS servers (for example lab.example to AD DNS and everything else to a public resolver), use explicit server= lines and no-resolv instead of /etc/resolv.dnsmasq:

text
no-resolv
server=/lab.example/192.168.56.128
server=1.1.1.1

Names under lab.example go to 192.168.56.128; other queries go to 1.1.1.1.

This is an alternative to the /etc/resolv.dnsmasq setup above. With no-resolv, do not also use resolv-file=/etc/resolv.dnsmasq in the same drop-in. For this article’s main walkthrough, keep the single-upstream file; use the server= form only when you specifically need split DNS.


Configure dnsmasq as a local cache

RHEL ships defaults in /etc/dnsmasq.conf. Add a drop-in under /etc/dnsmasq.d/ so package updates do not overwrite your settings.

bash
sudo tee /etc/dnsmasq.d/local-cache.conf <<'EOF'
# Listen only on this host
listen-address=127.0.0.1
bind-interfaces

# Upstream DNS servers
resolv-file=/etc/resolv.dnsmasq
no-poll

# Local cache
cache-size=1000
EOF

Here is what each setting changes:

  • listen-address=127.0.0.1 — accept DNS only on loopback so LAN clients do not use this cache by accident.
  • bind-interfaces — bind specifically to that address instead of dnsmasq’s default wildcard socket on Linux. Without it, ss may show 0.0.0.0:53 even though listen-address filters which clients are served; with bind-interfaces, the listener in ss matches the intended loopback-only scope.
  • resolv-file — read upstream servers from /etc/resolv.dnsmasq, not from /etc/resolv.conf.
  • no-poll — do not monitor the resolver file for changes. If you edit /etc/resolv.dnsmasq, reload or restart dnsmasq yourself.
  • cache-size=1000 — cache up to 1000 entries (default is 150). For one workstation or small server, 1000 is a reasonable example; a huge cache is not required.

Optional hardening (not required for basic caching):

  • domain-needed — do not forward single-label names such as printer upstream when dnsmasq has no local answer.
  • bogus-priv — do not forward reverse lookups for private ranges when there is no local data. Skip this if you rely on AD for RFC1918 PTR records.

If other drop-ins under /etc/dnsmasq.d/ enable DHCP, PXE, or extra listen-address values, dnsmasq may also bind on LAN interfaces. Keep PXE/DHCP configuration separate from this loopback-only cache example when you can.

Check the configuration for syntax errors

Validate the drop-in before you start the service:

bash
sudo dnsmasq --test
output
dnsmasq: syntax check OK.

Start dnsmasq

Start the resolver before you point NetworkManager at 127.0.0.1, so the host is not left with no working DNS on port 53.

Enable and start the service with systemctl:

bash
sudo systemctl enable --now dnsmasq

systemctl enable --now is quiet on success, so check that the daemon is running:

bash
systemctl is-active dnsmasq
output
active

Verify UDP and TCP on loopback

DNS uses UDP for most queries and TCP when needed. With listen-address=127.0.0.1 and bind-interfaces, dnsmasq should listen on both protocols on loopback only:

bash
sudo ss -luntp | grep ':53'
output
udp   UNCONN 0 0 127.0.0.1:53 0.0.0.0:* users:(("dnsmasq",pid=98556,fd=9))
tcp   LISTEN 0 32 127.0.0.1:53 0.0.0.0:* users:(("dnsmasq",pid=98556,fd=10))

If you see 0.0.0.0:53 without bind-interfaces, dnsmasq may still be filtering by listen-address — add bind-interfaces so ss matches what you intend. If you see a LAN address such as 192.168.56.116:53, another drop-in (DHCP/PXE) is binding extra interfaces; scope or disable that snippet for a loopback-only cache.


Point RHEL at the local cache with NetworkManager

Hand-editing /etc/resolv.conf is fragile on RHEL 10 because NetworkManager regenerates it from connection profiles.

Find the NetworkManager connection profile

List active connections. Use the value from the NAME column, not necessarily the DEVICE value:

bash
nmcli -t -f NAME,DEVICE connection show --active
output
enp0s8:enp0s8

On another host you might see Wired connection 1:enp0s8 — then modify 'Wired connection 1', not enp0s8.

Set 127.0.0.1 as the only resolver

Point applications at the cache. dnsmasq itself uses /etc/resolv.dnsmasq for upstreams:

bash
sudo nmcli connection modify enp0s8 ipv4.dns "127.0.0.1" \
  ipv4.dns-search "lab.example" ipv4.ignore-auto-dns yes

ipv4.ignore-auto-dns yes stops DHCP from injecting nameservers that bypass dnsmasq.

Adding the upstream DNS server as a second nameserver in NetworkManager gives the host a fallback if dnsmasq is down, but DNS can then bypass the local cache. If you want every DNS query from this host to go through dnsmasq, keep only 127.0.0.1 in /etc/resolv.conf.

Apply the profile:

bash
sudo nmcli connection up enp0s8

Reactivating a connection can briefly interrupt that interface. If you change DNS on a remote system over SSH, keep another access path available or schedule the change during a maintenance window.

Verify /etc/resolv.conf

Check the resulting resolver configuration:

bash
cat /etc/resolv.conf
output
search lab.example
nameserver 127.0.0.1

More nmcli patterns: nmcli cheat sheet.


Verify DNS forwarding

Query through the cache explicitly:

bash
dig +noall +answer +stats www.example.org @127.0.0.1

After a cache flush or restart, the first lookup often shows a higher Query time while dnsmasq contacts upstream:

output
www.example.org.	274	IN	A	172.66.157.237
;; Query time: 58 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)

If you use internal DNS, test an internal hostname as well:

bash
dig +short vm1.lab.example @127.0.0.1
output
192.168.56.116

Verify the DNS cache

Run the same query again before the TTL expires:

bash
dig +noall +answer +stats www.example.org @127.0.0.1

A much lower Query time is a good indication that dnsmasq used its cache, but timing alone is not proof — a fast upstream can also answer quickly on a second miss.

output
;; Query time: 0 msec

Confirm the second query was not forwarded upstream

Forwarded queries leave through a real interface, not only lo. Capture on all interfaces or on the NIC that reaches your upstream DNS:

bash
sudo tcpdump -ni any port 53

In another session:

  1. Run the first dig — you should see the client query to 127.0.0.1 and dnsmasq’s query to the upstream resolver.
  2. Run the same dig again before TTL expiry — you should see the client query, but no new upstream query for a genuine cache hit.

Alternatively, capture only the external interface (example enp0s8):

bash
sudo tcpdump -ni enp0s8 port 53

Install tcpdump only when you need this check:

bash
sudo dnf install -y tcpdump

Clear the dnsmasq cache

Restarting dnsmasq clears the cache:

bash
sudo systemctl restart dnsmasq

The dnsmasq man page also documents clearing cache with SIGHUP. On RHEL, use systemctl restart dnsmasq unless you have verified your unit’s reload behavior sends that signal.

After a restart, the next lookup must contact an upstream resolver again.


Troubleshooting

Symptom Likely cause What to check
dnsmasq fails to start Port 53 in use or bad config sudo ss -luntp | grep ':53'; sudo dnsmasq --test
dnsmasq runs but DNS fails Upstream unreachable or wrong /etc/resolv.dnsmasq dig @192.168.56.128 (or your upstream); fix upstream file
Applications skip the cache Second nameserver in /etc/resolv.conf or DHCP DNS cat /etc/resolv.conf; set only 127.0.0.1 and ipv4.ignore-auto-dns yes
Internal AD names fail Public DNS used for AD zones Use AD DNS only in /etc/resolv.dnsmasq, or server=/domain/upstream
Repeated queries still hit upstream Low TTL, different QNAME each time, or cache cleared Repeat the exact name; check cache-size; use tcpdump
Extra listeners on LAN IP DHCP/PXE drop-ins in /etc/dnsmasq.d/ Separate PXE design from loopback cache

For NXDOMAIN and timeout behavior on the wire, see DNS troubleshooting with Wireshark.


Summary

dnsmasq gives RHEL 10 a local DNS cache without running a full authoritative server. Install the package, list upstreams in /etc/resolv.dnsmasq, add a small loopback-only drop-in, and set NetworkManager to nameserver 127.0.0.1 so applications always hit the cache first.

The problems you are most likely to hit are another process on port 53 and a second nameserver in /etc/resolv.conf that bypasses dnsmasq. After systemctl enable --now dnsmasq, use dig twice and optional tcpdump on any or your external NIC to confirm caching — not only query time.

For DHCP or PXE from dnsmasq, plan interfaces and listen-address separately; on RHEL 10, Kea is the long-term DHCP direction.


References


Frequently Asked Questions

Does RHEL cache DNS by default?

No. You enable caching by running dnsmasq (standalone or through NetworkManager’s dnsmasq mode) or another resolver that caches.

Does dnsmasq replace BIND?

No. dnsmasq forwards and caches. Use BIND or directory DNS when you need authoritative zones or enterprise DNS features.

Should dnsmasq listen on 127.0.0.1?

For a host-local cache, yes — it keeps the cache off the LAN. Widen listen-address only when you intentionally offer DNS to other machines.

Can dnsmasq use Active Directory DNS?

Yes. Point /etc/resolv.dnsmasq at your AD DNS server, or use server=/your.domain/AD-DNS-IP for split forwarding.

How do I clear the dnsmasq DNS cache?

Restart the service: sudo systemctl restart dnsmasq.

Can NetworkManager run dnsmasq automatically?

Yes. RHEL documents dns=dnsmasq under NetworkManager. Do not combine that with a separate dnsmasq.service on the same loopback port.

Can other machines use this DNS cache?

Not with listen-address=127.0.0.1. Other hosts would need dnsmasq to listen on a LAN address, which is a different design.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration