macOS DNS caching: why /etc/hosts edits don’t take effect immediately and the dscacheutil + mDNSResponder restart dance

You edit /etc/hosts on your Mac to point api.staging.example.com at a local Docker container. You hit it in the browser. It still resolves to the live AWS IP. You hit it from curl. Same thing. You wonder if your edit even saved. It did. macOS has just decided that what you wrote in /etc/hosts is a suggestion, not an instruction.

This post is about why that happens, why the same flush command everyone tells you to run is sometimes wrong, and a small handful of commands that will actually invalidate the right cache for your scenario.

Why your edit didn’t take

macOS doesn’t resolve names through a single mechanism. There are at least three in play, depending on what kind of name you ask for:

  • mDNSResponder handles most resolution: /etc/hosts, your DNS servers, .local Bonjour names, and DNS-SD service discovery. It runs as a system daemon and aggressively caches answers based on each record’s TTL.
  • DirectoryService cache (dscacheutil) sits in front of mDNSResponder for legacy getaddrinfo calls. Older command-line tools and some apps still talk to it.
  • The application itself. Browsers run their own resolver caches. curl doesn’t, but a lot of GUI apps do — especially Electron-based ones (VS Code, Slack, Discord) that bundle Chromium’s resolver.

When you edit /etc/hosts, mDNSResponder picks up the change on its next lookup for an uncached name — but anything it has cached, it will keep serving until the TTL expires. And the application on top might have its own cached value that’s older still.

The right flush command for the right cache

The internet’s most-copied StackOverflow answer for “flush DNS cache on Mac” is:

sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder

This works in 2026, on Sequoia and Sonoma. The two commands are doing different things:

  • dscacheutil -flushcache flushes the DirectoryService cache (the legacy getaddrinfo path).
  • killall -HUP mDNSResponder sends SIGHUP to mDNSResponder, which causes it to re-read its config files (including /etc/hosts) and drop its in-memory cache.

Run them together. sudo is required for both — without it, you’ll get permission errors and the commands will silently fail to flush.

When that’s not enough

If your hosts edit still doesn’t take after running both commands, walk down this list:

  1. Check the file actually saved. cat /etc/hosts | grep example. macOS’s TextEdit silently creates .txt copies in some configurations; if you “saved” with TextEdit and the change isn’t in the file, that’s why.
  2. Confirm there’s no whitespace problem. The hosts file is whitespace-sensitive — IP, then tab or space, then hostname. Mixed tabs and spaces or a stray comma will make the line silently invalid.
  3. Test with dig, not the browser. dig api.staging.example.com bypasses everything except the actual DNS path. If dig returns the right IP but Chrome still uses the old one, you have a browser cache problem, not a system one.
  4. Restart mDNSResponder fully, not just SIGHUP. A stuck mDNSResponder occasionally won’t honor SIGHUP. sudo launchctl stop com.apple.mDNSResponder followed by sudo launchctl start com.apple.mDNSResponder forces a clean restart.

Browser caches: the part nobody warns you about

Chrome, Edge, and any Electron app (VS Code, Slack, Discord, Postman) keep an internal DNS cache that’s independent of macOS. Even after you flush mDNSResponder, Chrome will happily use a stale entry it cached 30 seconds before your hosts edit.

  • Chrome / Chromium / Edge: visit chrome://net-internals/#dns, click Clear host cache. Then chrome://net-internals/#sockets and click Flush socket pools. Both are needed for in-flight connections.
  • Safari: doesn’t have a developer-visible cache, but a quick fix is Develop menu → Empty Caches (or the Cmd+Option+E shortcut).
  • Firefox: about:networking#dns, click Clear DNS Cache.
  • Electron apps: usually require a full quit and relaunch — there’s no exposed cache-clear UI.

One-liner you can put in your shell config

I keep this in ~/.zshrc for the staging-environment days when I’m bouncing between hosts files:

flushdns() {
  sudo dscacheutil -flushcache
  sudo killall -HUP mDNSResponder
  echo "DNS cache flushed at $(date '+%H:%M:%S')."
}

The timestamp is small but useful — it confirms it actually ran, and the second time you call it in a debugging session, you can compare timestamps to know whether the cache or the network is the problem.

If you’re using a corporate VPN or 1.1.1.1 / 8.8.8.8 client

Cloudflare’s WARP client, Tailscale Magic DNS, NextDNS, and most corporate VPN clients install their own DNS interceptor that sits in front of macOS’s resolver. mDNSResponder still consults /etc/hosts, but the VPN client may rewrite or shortcut the answer before mDNSResponder sees it.

Test with scutil --dns — it dumps every resolver macOS knows about, in priority order. If you see something like resolver #1: nameserver 100.100.100.100 at the top, that’s a tunneled DNS, and your hosts edit may not be consulted at all for hostnames that resolver claims responsibility for. Toggle the VPN client off, retest, and you’ll know.

Photo: Network switch with patch cables by Brett Sayles on Pexels.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.