SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Flush DNS cache on Ubuntu, and what else caches

Flush the DNS cache on Ubuntu with resolvectl, then find the copies a flush misses: nscd, nginx, Docker, the JVM and stale or negative answers held upstream.

How to flush the DNS cache on Ubuntu

To flush the DNS cache on Ubuntu 24.04 or 26.04, run sudo resolvectl flush-caches. This empties the cache that systemd-resolved keeps on the machine. That is the whole answer only if systemd-resolved is the only cache in the path. On a server, it often is not.

A name can stay wrong after that flush for four reasons. An entry in /etc/hosts may answer before DNS is asked. A second local cache, such as nscd, dnsmasq or unbound, may answer first. An application may keep its own copy of the address in memory. Or the stale answer is at an upstream resolver, and no local command can reach it there. This guide checks each place in order, so you know which cache is wrong before you flush anything.

If these terms are new to you, how DNS resolution works from stub resolver to authoritative server explains them. The short version: a stub resolver on your machine asks a recursive resolver. The recursive resolver asks the authoritative nameservers for the domain. Each of them can keep the answer for the record's TTL (time to live), which is a number of seconds.

Find out what is caching on this machine

Start with /etc/resolv.conf. Programs that read this file send their DNS queries to the servers it lists.

readlink -f /etc/resolv.conf
grep '^nameserver' /etc/resolv.conf

The systemd-resolved man page for Ubuntu 24.04 documents four ways to set up this file. The path that readlink prints tells you which one you have.

  • /run/systemd/resolve/stub-resolv.conf: lists the stub at 127.0.0.53 as the only DNS server. The man page calls this the recommended mode. Queries pass through systemd-resolved and its cache.
  • /usr/lib/systemd/resolv.conf: a static file that also lists only 127.0.0.53, with no search domains. Queries still pass through systemd-resolved.
  • /run/systemd/resolve/resolv.conf: lists the real upstream DNS servers. Some programs read /etc/resolv.conf themselves instead of using a local DNS API. In this mode, those programs talk to the upstream servers directly and skip the systemd-resolved cache.
  • A regular file that another package manages. In this mode, systemd-resolved reads the file as configuration and does not write to it.

Next, check whether a second caching service is installed or running.

dpkg -l nscd dnsmasq unbound 2>/dev/null | grep '^ii'
systemctl is-active systemd-resolved nscd dnsmasq unbound
sudo ss -lunp 'sport = :53'

The dpkg line prints one row for each of the three packages that is installed. It prints nothing for packages that are not installed. systemctl is-active prints one state per unit, in the order you named them. The ss line shows which process listens on UDP port 53. Say /etc/resolv.conf names 127.0.0.1 and ss shows dnsmasq or unbound on that address. Then that service is your first cache, not systemd-resolved.

Last, check what the rest of the system sees before DNS is asked at all.

grep '^hosts:' /etc/nsswitch.conf
getent hosts www.example.com

getent hosts resolves the name the same way most programs on the box do. It follows the order on the hosts: line, which usually puts /etc/hosts first. dig, which you will use later in this guide, sends DNS queries directly and ignores /etc/hosts. So if getent and dig give different answers, the cause is on your machine: an entry in /etc/hosts, or an NSS (name service switch) cache such as nscd. In that case, DNS is not the problem.

Flush systemd-resolved with resolvectl

sudo resolvectl flush-caches
resolvectl statistics
resolvectl query www.example.com

resolvectl flush-caches flushes all the DNS resource record caches that systemd-resolved keeps locally. The 24.04 man page says that sending the SIGUSR2 signal to the service does the same thing. It recommends the command, because the command is synchronous: it returns only after the flush is done. The same page notes that systemd-resolved flushes its caches by itself whenever the host's network configuration changes. So a manual flush is mostly a debugging step.

resolvectl statistics shows general resolver statistics. These include whether DNSSEC (DNS security extensions) is enabled and available, plus counters for resolution and validation. If you are chasing validation failures, the cache is the wrong place to look. DNSSEC validation failures after the root KSK rollover covers that problem. resolvectl query resolves the name through systemd-resolved, so you can see what it returns after the flush.

You can also see what the cache held before you empty it. Send the service SIGUSR1, and it writes the contents of all its caches to the system log.

sudo systemctl kill -s USR1 systemd-resolved
sudo journalctl -u systemd-resolved -n 200 --no-pager

Does the old systemd-resolve command still exist?

Older guides tell you to run systemd-resolve --flush-caches. That command was renamed to resolvectl. Upstream systemd still defines a systemd-resolve symlink for backwards compatibility. But Ubuntu's own package file lists for 24.04 and 26.04 show only /usr/bin/resolvectl in the systemd-resolved package. They show no systemd-resolve binary. Check your own box instead of trusting any guide, including this one:

command -v systemd-resolve resolvectl

The command prints the path of each name it finds. If it prints only the resolvectl path, use resolvectl flush-caches.

Why a flush can change nothing on 24.04

The Cache= setting in resolved.conf has a different default on the two LTS releases. Ubuntu 24.04 ships systemd 255, and its man page gives the default as no-negative: only positive answers are cached. Ubuntu 26.04 ships systemd 259, and its man page gives the default as yes, so negative answers are cached too.

This matters for the most common report. You create a record, query it, and get NXDOMAIN (the name does not exist). On 24.04 the local stub never stored that NXDOMAIN, so resolvectl flush-caches cannot fix it. The negative answer is held at an upstream resolver. On 26.04 the stub can hold it, and the flush clears that local copy. An upstream copy can still remain. Check which setting your box uses:

systemd-analyze cat-config systemd/resolved.conf | grep -i '^cache'

No output means that no file sets it, so the release default applies. One more default matters here. CacheFromLocalhost= is no, so systemd-resolved does not cache answers that come from a DNS server on the same host. If it forwards queries to a local dnsmasq or unbound, flush that local server instead.

Flush nscd, dnsmasq and unbound

nscd

nscd (name service cache daemon) caches lookups for programs that use the system's name service functions. Its man page documents nscd -i <database> for invalidating one cache table. hosts is one of the services that nscd.conf lists.

sudo nscd -i hosts

nscd keeps positive and negative entries with separate TTLs, which you set in /etc/nscd.conf. The nscd.conf man page says that for DNS, nscd uses the TTL returned by the name service and ignores positive-time-to-live. Failed lookups follow negative-time-to-live. A short value there limits how long a "not found" answer stays.

dnsmasq

dnsmasq clears its cache when it receives SIGHUP, and then reads /etc/hosts again.

sudo pkill -HUP dnsmasq

pkill sends the signal to every dnsmasq process on the machine. That is what you want when you do not know which instance answers. The dnsmasq man page gives a default cache size of 150 names, and --cache-size=0 turns caching off. Two more options are useful here. --neg-ttl sets a TTL for negative replies that have no SOA record to take a TTL from. --clear-on-reload clears the cache whenever dnsmasq reads /etc/resolv.conf again.

unbound

sudo unbound-control flush www.example.com
sudo unbound-control flush_negative
sudo unbound-control flush_zone example.com

flush removes the common record types for one name: A, AAAA, NS, SOA, CNAME, MX, PTR, SRV and others. flush_negative removes all negative data: NXDOMAIN, NODATA and SERVFAIL answers. flush_zone removes everything at or below a name. It is slow, because it walks through the whole cache. If you want to look at the cache first, unbound-control dump_cache prints it. unbound-control talks to the running server over its remote control interface. You turn that interface on with control-enable: yes in the remote-control: section of unbound.conf. If unbound-control cannot connect, sudo systemctl restart unbound also empties the cache that unbound holds in memory.

Why an application still has the old address after a flush

The OS caches are now empty, and getent hosts returns the new address. But your service still connects to the old one. This happens because many programs resolve a name once and keep the address in their own memory. No system flush can reach that memory. You have to make the program look up the name again.

nginx resolves a literal proxy_pass hostname once

When proxy_pass names a host and contains no variables, nginx resolves the name while it loads its configuration. It then uses that address until the next reload. The nginx documentation does not say this directly, but the source code shows it. A literal proxy_pass builds an implicit upstream. The round-robin upstream code in the nginx source then resolves its host with ngx_inet_resolve_host, which calls getaddrinfo.

location / {
    proxy_pass http://app.example.com:8080;
}

The quick fix is a reload. nginx reads the configuration again, so it resolves the name again.

sudo nginx -t && sudo nginx -s reload

On reload, the nginx master process checks the new configuration, starts new worker processes, and asks the old workers to shut down. The old workers first finish the requests they are serving. If the new configuration fails, nginx rolls back and keeps running the old one, so a typo does not take the site down.

To make nginx look up the name again by itself, put the address in a variable and give nginx a resolver. The nginx docs say that when proxy_pass uses a variable, a name that is not one of your upstream groups is resolved with that resolver.

resolver 127.0.0.53 valid=30s;

location / {
    set $backend http://app.example.com:8080;
    proxy_pass $backend;
}

By default, nginx caches resolver answers for the TTL in the DNS response. valid=30s overrides that TTL. The resolver also looks up IPv6 addresses unless you add ipv6=off. Add it on a host that has no IPv6 route. With the resolver directive, nginx sends DNS queries itself, so entries in /etc/hosts do not apply to these lookups.

The third option keeps a normal upstream block. The resolve parameter on a server line watches the name for address changes and updates the group without a restart. It needs a shared memory zone in the group, plus a resolver in the http or upstream block. Before nginx 1.27.3, this parameter was only part of the commercial subscription, so run nginx -v before you rely on it.

resolver 127.0.0.53 valid=30s;

upstream backend {
    zone backend 64k;
    server app.example.com:8080 resolve;
}

The rest of a proxy setup, including headers and timeouts, is covered in how an nginx reverse proxy config works, line by line.

Docker's embedded DNS on user-defined networks

On a user-defined network, containers find each other by name or alias through Docker's embedded DNS server at 127.0.0.11. That server forwards external lookups to the DNS servers configured on the host. On the default bridge network, containers cannot find each other by name. Instead, each container gets a copy of the host's /etc/resolv.conf.

The Docker documentation does not say whether the embedded server caches answers. The stale copy is usually in the program inside the container. That conclusion comes from reading the nginx and Docker docs together. Docker does not state it. Here is a typical case. An nginx container proxies to http://app:8080. You recreate the app container, and it may get a different address on the network. nginx resolved app when it started, so it keeps sending traffic to the old address.

Restarting the proxy container makes it resolve the name again (docker compose restart nginx). For a lasting fix, use the variable pattern from the nginx section with Docker's resolver:

resolver 127.0.0.11 valid=10s;

location / {
    set $app http://app:8080;
    proxy_pass $app;
}

How Docker Compose networks and service names work explains how service names map to networks. If you do not know whether a restart is enough or the container needs to be recreated, read when to restart, recreate or rebuild a Compose service.

The JVM keeps its own address cache

Java's InetAddress class caches both successful and failed lookups. The JDK 21 documentation says that when a security manager is installed, successful lookups are cached forever by default. Without a security manager, they are cached for a limited time that depends on the implementation. OpenJDK's java.security source file says its default is 30 seconds. Failed lookups are cached for 10 seconds by default.

Three Java security properties control this: networkaddress.cache.ttl, networkaddress.cache.stale.ttl and networkaddress.cache.negative.ttl. For cache.ttl, -1 means cache forever. For cache.negative.ttl, 0 means never cache. Restarting the JVM always empties the cache. You can also set the TTL without editing the JDK's own file, because OpenJDK lets you add a properties file from the command line:

sudo install -d /etc/myapp
echo 'networkaddress.cache.ttl=30' | sudo tee /etc/myapp/java-dns.properties
java -Djava.security.properties=file:/etc/myapp/java-dns.properties -jar app.jar

If a key appears in both files, the value in this file wins over the main java.security file. OpenJDK's file also warns that changing the default can have serious security effects if you are exposed to DNS spoofing. So choose a short positive value, not 0.

When the stale answer lives upstream

This is the cause behind most reports of "I flushed and it still resolves wrong". Your box asks a recursive resolver, such as your VPS provider's resolver or a public one like 1.1.1.1. That resolver caches each answer for the record's TTL. Say the record had a TTL of 3600 when you changed it. Resolvers that fetched it just before the change can return the old address for up to an hour.

Negative answers are cached too. RFC 2308 says resolvers should cache an NXDOMAIN answer. Its TTL is the smaller of two values: the MINIMUM field of the zone's SOA (start of authority) record, and the TTL of the SOA record itself. So if anyone queried a name before you created it, resolvers can keep the answer "this name does not exist" for that long. How long a specific public resolver keeps it depends on that resolver. No local flush can change any of this.

Ask the authoritative nameserver directly with dig

The fix is to find out which server is wrong. Query the authoritative nameserver, then compare its answer with the resolvers. The sequence below is our own recipe built from documented dig options. It is not a procedure from the dig manual.

sudo apt install -y bind9-dnsutils
dig NS example.com +short
dig @ns1.example.net www.example.com A +norecurse
dig @1.1.1.1 www.example.com A
dig @8.8.8.8 www.example.com A
dig www.example.com A

The first dig lists the domain's nameservers. Put one of them after @ in the second line. @server sends the query to that server. +norecurse clears the RD (recursion desired) bit, so the authoritative server answers from its own zone data. The next two lines ask two public resolvers. The last line has no @, so dig uses the servers in /etc/resolv.conf. With the stub setup, that means systemd-resolved at 127.0.0.53. Read the results in this order:

  1. The authoritative answer is wrong. No cache is involved. Fix the record at your DNS host.
  2. The authoritative answer is right and a public resolver is wrong. That resolver's cache is stale. Wait until the TTL it shows runs out, or purge it as shown below.
  3. Every dig is right but getent hosts is wrong. The problem is on your box: /etc/hosts, nscd, or a local forwarder.
  4. Everything above is right but your service is wrong. The application keeps its own copy, so go back to the nginx, Docker or JVM section.

The nameservers at your registrar may not match your DNS host. In that case, dig www.example.com +trace follows the delegation path down from the root servers. It shows which nameservers the rest of the internet is actually sent to.

To see how long a negative answer can last, read the zone's SOA record:

dig example.com SOA +noall +answer

The second field is the SOA record's TTL. The last number in the record is MINIMUM. The smaller of the two is the negative-cache TTL from RFC 2308. If DNS works everywhere except through a VPN, the cause is usually the tunnel's resolver settings. Fixing DNS that fails over a WireGuard tunnel covers that case.

Purge a public resolver's cache

Two large public resolvers have a page where you can refresh a cached name. Cloudflare's Purge Cache page for 1.1.1.1 takes a domain name and a record type, and refreshes that name in the 1.1.1.1 cache. Google Public DNS has a Flush Cache form that takes a domain name and an RR (resource record) type. Neither page says, in the text we could check, what its limits are or whether it clears negative answers. Read each page for those details.

You cannot purge the resolver of your ISP or your VPS provider. For those, you wait until the TTL runs out. Next time, lower the record's TTL before you make the change. Do it at least one old TTL in advance, so resolvers fetch the new value soon after you switch.

FAQ

How do I flush the DNS cache on Ubuntu 24.04?

Run sudo resolvectl flush-caches. It empties the cache that systemd-resolved keeps when /etc/resolv.conf points at the 127.0.0.53 stub. If nscd, dnsmasq or unbound is also installed, flush it too. For nscd, run sudo nscd -i hosts. For dnsmasq, run sudo pkill -HUP dnsmasq. For unbound, run sudo unbound-control flush_negative plus flush for the name.

Why does systemd-resolve give "command not found"?

The tool was renamed to resolvectl. Ubuntu's package file lists for 24.04 and 26.04 show only /usr/bin/resolvectl in the systemd-resolved package. Where old guides say systemd-resolve --flush-caches, use resolvectl flush-caches. Check what your system has with command -v systemd-resolve resolvectl.

I flushed the DNS cache and the domain still resolves to the old IP. Why?

The stale answer is usually upstream. A recursive resolver may have cached the record for its TTL. Or it cached an NXDOMAIN because someone queried the name before the record existed. Query the authoritative server with dig @<nameserver> name A +norecurse, and compare the result with dig @1.1.1.1 name A. If the authoritative answer is right, wait until the TTL runs out, or use the resolver's purge page.

Why does nginx keep proxying to the old IP after a DNS change?

nginx resolves a literal hostname in proxy_pass only once, when it loads its configuration. Run sudo nginx -s reload to resolve it again. For a lasting fix, put the URL in a variable and add a resolver line. On nginx 1.27.3 or later, you can instead use server ... resolve in an upstream block that has a zone.

Does Ubuntu cache negative DNS answers?

It depends on the release. On Ubuntu 24.04, the systemd-resolved default is Cache=no-negative, so the local stub does not keep NXDOMAIN answers. On Ubuntu 26.04, the default is Cache=yes, so the stub keeps them, and resolvectl flush-caches clears them. An upstream resolver can still hold its own negative answer for the TTL taken from the SOA record.