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,.localBonjour 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
getaddrinfocalls. Older command-line tools and some apps still talk to it. - The application itself. Browsers run their own resolver caches.
curldoesn’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 mDNSResponderThis works in 2026, on Sequoia and Sonoma. The two commands are doing different things:
dscacheutil -flushcacheflushes the DirectoryService cache (the legacygetaddrinfopath).killall -HUP mDNSRespondersends 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:
- Check the file actually saved.
cat /etc/hosts | grep example. macOS’s TextEdit silently creates.txtcopies in some configurations; if you “saved” with TextEdit and the change isn’t in the file, that’s why. - 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.
- Test with
dig, not the browser.dig api.staging.example.combypasses everything except the actual DNS path. Ifdigreturns the right IP but Chrome still uses the old one, you have a browser cache problem, not a system one. - Restart mDNSResponder fully, not just SIGHUP. A stuck mDNSResponder occasionally won’t honor SIGHUP.
sudo launchctl stop com.apple.mDNSResponderfollowed bysudo launchctl start com.apple.mDNSResponderforces 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. Thenchrome://net-internals/#socketsand 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.
