A favicon is requested by the browser shell rather than by the page. That architectural detail is why the normal cache-busting habits — a query string on the CSS, a hard reload — do nothing for icons. The stale copy can survive in four separate layers.
One: the browser treats icons as shell resources
Chrome and Safari keep favicons in a database keyed by origin, separate from the HTTP cache. A normal reload does not touch it, and a hard reload usually does not either. A fresh profile, a private window, or clearing “cached images and files” is the only reliable check on your own machine.
Two: /favicon.ico still wins
Browsers request /favicon.ico from the domain root when no icon is declared, and some still prefer it when the declared file fails. If an old ICO sits at the root while you updated a PNG in /assets, most clients will keep showing the ICO. Replace or remove both files as one operation.
# Confirm what is actually being served right now
curl -sI https://example.com/favicon.ico | head -5
curl -sI https://example.com/favicon.svg | head -5
# Check the bytes, not just the status
curl -s https://example.com/favicon.ico -o /tmp/icon.ico
file /tmp/icon.ico # expect: MS Windows icon resource, 16x16/32x32/48x48 Three: cache-control on the icon path
Static asset pipelines often apply a one-year immutable cache header to everything under /assets. That is correct for hashed filenames and wrong for icons referenced by a stable path. If the icon URL never changes and the header says max-age=31536000, no client will see the update until the year is out.
Four: a service worker is serving the old manifest
An installed PWA caches the manifest and the referenced icons. Updating the server does nothing until the service worker updates, which can take a full navigation cycle. Bumping the icon filename forces the worker to fetch a new asset even when the manifest cache is stale.
A clean reset procedure
- 01 Publish the new icon under a versioned filename: favicon-2026.png.
- 02 Update every declaration that points at the old file, including the manifest.
- 03 Replace /favicon.ico at the root in the same deploy, or delete it.
- 04 Purge the icon paths at your CDN, not just the HTML.
- 05 Verify with curl that the new bytes are being served from the edge.
- 06 Test in a fresh browser profile, never in the tab you have been reloading.
Why a query string is the wrong long-term fix
Appending ?v=2 works once. Next update it becomes ?v=3, then ?v=4, and every client still holds the old entry under a stable path. Versioning the filename gives you the same cache-busting benefit without accumulating query-string variants in logs and analytics.