You put a favicon.ico next to your pages, published the site, and the tab still shows a blank page icon or a grey globe. Or the icon appears on your machine and not on the live address. Or it shows in the tab but not on an iPhone's home screen, or not in Google.
A browser takes a site's icon from a <link rel="icon"> tag in the page, and when the page has none it may request /favicon.ico at the root of the origin instead. When a favicon is missing, check those two lookups before anything else: one of them is landing on an address that does not answer with an image. Do not start by clearing a cache. Ask the server what it returns for the icon's address — three requests, below — and the status and content type point at the cause.
The 30-second version
| What you see | What it means | Fix |
|---|---|---|
The icon's address returns 404 |
The file is not in what you published, or not at that path | Put it in the folder the host serves, and link to it explicitly |
It returns 200 with text/html |
The host answered with a page — usually your home page — in place of a missing file | Publish the icon; a single-page-app fallback is hiding the miss |
| It returns an image, and the tab is still wrong | The page links to a different address, or an old copy is being reused | Read the <link> tags the page really has; give a changed icon a new file name |
| The site is under a sub-path, and only the origin root misses | /favicon.ico points above your site |
Add a <link> whose address includes the sub-path |
| Tab is right, iPhone home screen or Google is wrong | Those two read their own rules | apple-touch-icon; a square icon, declared on the home page, that Google can crawl |
What is actually happening
The HTML Standard defines both lookups in its section on the icon link type.
The tag. A page can declare any number of <link rel="icon"> elements. When there are several, the browser must select the most appropriate one using their type, media and sizes attributes; when several are equally appropriate, it must use the last one declared. If the chosen icon turns out to be unusable — the standard's example is an unsupported format — the browser must try the next most appropriate.
The fallback. For a page with no such tag, the standard describes one more step, and describes it only for documents loaded over HTTP or HTTPS: a browser may request /favicon.ico, resolved against the document's address, and use the response as if it had been declared. That sentence carries three consequences:
/favicon.icostarts with a slash, so it resolves to the root of the origin — not to the folder your page is in.- The step is written for
http:andhttps:documents. Nothing in it covers a page opened from disk. - It is a "may". A link tag is the lookup you control.
The standard also says there is no default type for an icon, and that the browser must expect the resource to be an image. An HTML page delivered at the icon's address is not what it is told to expect.
Check it
Three requests. The first prints the <link> tags whose rel mentions an icon, from the HTML your host actually sends. The other two ask for favicon.ico — next to the page, and at the root of the origin — and print the final status and content type.
page=https://example.com/my-site/ # the site's home page, ending in a slash
origin=https://example.com # scheme and host only
curl -sL "$page" | grep -ioE "<link[^>]*rel=[\"']?[^\"'>]*icon[^>]*>"
curl -sL -o /dev/null -w "%{http_code} %{content_type}\n" "${page}favicon.ico"
curl -sL -o /dev/null -w "%{http_code} %{content_type}\n" "$origin/favicon.ico"When the home page is the root of its own domain, the last two are the same request. If the first command prints a tag, the address in its href is the one the browser uses: repeat the second request against that address. The search is line by line over the HTML as served: a tag split across lines, or one added later by a script, will not show, so if it prints nothing, also look at the page's <head> in the browser's developer tools before concluding there is no tag.
A healthy answer is 200 with an image type. nginx's stock table, for example, maps ico to image/x-icon, png to image/png and svg to image/svg+xml; the name registered for the .ico format is image/vnd.microsoft.icon.
The causes, one by one
The file is not in what you published
A 404 means the host has no file at that address. With a build tool, check where the build copies files from. Vite, for one, has a public directory whose files are served at the root path during development and copied to the root of the output folder as-is, and its documentation says to reference them with a root-absolute path — public/icon.png is /icon.png. Then look inside the folder you actually uploaded: if the icon is not in it, the host cannot serve it. Publishing the wrong folder, and a file name that differs only in upper and lower case, are covered in why a site works locally but breaks after deploying.
The host answers with your home page
This is the one a status code can hide. A single-page-app fallback tells the host to answer unknown paths with index.html, and a missing favicon.ico is an unknown path like any other.
- Netlify. The rule
/* /index.html 200will, in Netlify's words, serveindex.htmlinstead of giving a 404 no matter what URL the browser requests. By default a file that exists is still served as itself; a rule forced with200!rewrites even then. - Cloudflare Pages. A project with no top-level
404.htmlis treated as a single-page app, and Pages serves the root for any incoming path that does not correspond to a static asset. The page does not give the status. - Vercel. The documented single-page rewrite sends
/(.*)to/index.html. The same page advises that a rewrite'ssourceshould not be a file, because precedence is given to the filesystem before rewrites are applied. No status is given there either. - dropto.run. A site with single-page mode on answers an unknown path with
index.htmland a 200; with it off, the same path is a 404. This is read from the router's code (apps/router/src/index.ts, as of commitfc73de4), not measured on a live site.
The symptom is a content type of text/html on a response that is not a 404. Other hosts and servers have fallbacks of their own, and the test is the same on all of them: an HTML body where an image should be. Publishing the file fixes it wherever a real file is served ahead of the fallback, which each of the four passages above describes — a forced Netlify rule being the exception. Why a static site 404s on refresh explains the fallback itself.
The site lives under a sub-path
GitHub Pages serves a user or organisation site at https://<owner>.github.io and a project site at https://<owner>.github.io/<repositoryname>. On a project site the fallback request goes to https://<owner>.github.io/favicon.ico — the root of the origin, outside your repository's path. A tag written as href="/favicon.ico" lands in the same place. The third command above is the one that shows it.
Declare the icon with the sub-path in it:
<link rel="icon" href="/my-repo/favicon.ico">If Vite builds the site, set base: its documentation says asset references in your .html files are automatically adjusted to respect that option during the build. It does not say whether that reaches an icon kept in public, so open the built index.html and confirm the href changed. The same sub-path breaks scripts and stylesheets, which the works-locally article covers.
The tag points somewhere else
The first command shows what the page really declares, which is often not what you remember writing. Three things to look for:
- More than one tag. If a template's default icon is declared after yours and their attributes make the two equally appropriate, the last one declared is used.
- A relative
href.href="favicon.ico"is resolved against each page's own address — the same rule that makes/aboutand/about/differ — so it can work on the home page and miss on/blog/post/. Write the full path from the root, including the sub-path if the site has one. - An
http://address. On an HTTPS page that is mixed content. MDN describes browsers upgrading insecure image, audio and video requests to HTTPS and blocking insecure requests for all other resource types; it does not say which of the two an icon gets, and in neither is the address fetched as written. Writehttps://, or a path from the root.
rel="shortcut icon" is not the problem. The standard still permits it for historical reasons, though MDN calls shortcut non-conforming and tells authors to drop it.
You replaced the icon and the old one stays
Check first that the server has the new file — save it with curl -sL -o check.ico and the icon's address, and open what you got. If it does and the tab still shows the old one, a stored copy is being reused somewhere, and you do not need to find out where. MDN's guide to HTTP caching notes that a cache distinguishes resources from one another by their URLs, so a stored response is not reused once the URL changes, and that changing the URL each time the content changes is common practice. Give the new icon a new name and point the tag at it:
<link rel="icon" href="/favicon-2.png">You cannot do this with the fallback, whose address is fixed — one more reason to declare a tag. Which layer is holding an old file is the subject of why a site still shows the old version after a deploy.
You opened the page from disk
The standard describes the /favicon.ico fallback for pages loaded over HTTP or HTTPS, and a double-clicked index.html is neither. A root-absolute href does not help there: on a file: page a path starting with a slash asks for the root of your disk. Serve the folder — npx serve, python3 -m http.server — before deciding the icon is broken. Why a page works in the chat and is blank as a file covers what else changes on file:.
iOS and Google read their own rules
iPhone and iPad home screen. MDN notes that iOS does not use the icon link type to pick a home-screen icon; it uses the non-standard apple-touch-icon. Apple's own page on it, now in its documentation archive, shows the tag with sizes such as 180x180, and says that when no tag is present the website root directory is searched for files with the apple-touch-icon prefix, such as apple-touch-icon.png.
<link rel="apple-touch-icon" href="/apple-touch-icon.png">Google Search. Google's favicon guidelines tell you to add a <link> tag to the header of the home page; they do not mention a /favicon.ico fallback, so do not count on one there. The icon must be square and at least 8×8 pixels, and Google recommends one larger than 48×48. Googlebot has to be able to crawl the home page and Googlebot-Image the icon file, so a robots.txt rule that blocks either one blocks the icon. Google supports one favicon per hostname, wants its URL to be stable, and says crawling can take anywhere from several days to several weeks. A correct icon that is missing from results the day after launch is not yet a bug.
When not to do this
- Don't clear caches first. Ask the server first. If it returns a 404 or an HTML page for the icon's address, no amount of clearing produces an icon.
- Don't rename the icon on every deploy. A new name is the fix for a replaced icon, and Google asks for a stable URL. Rename when the image changes.
- Don't paste a block of twenty link tags you cannot explain. The standard requires that
sizesname only sizes that actually exist, and every extra tag is one more address that has to answer. - Don't rely on the
/favicon.icofallback for a site under a sub-path, or a page that will be opened from disk. For Google's results, its guidelines ask for the tag.
Questions
Do I need a <link> tag if I have favicon.ico in the root?
For the browser tab, not strictly, on a site served from the root of its own origin: the HTML Standard lets a browser request /favicon.ico when a page declares no icon. Declare one anyway. Google's guidelines ask for the tag, it works under a sub-path, and it lets you change the file name when the image changes.
Can a favicon be a PNG or an SVG?
As far as the HTML Standard goes, yes: it sets no default type for an icon, and it defines sizes="any" for a scalable icon such as an SVG. Browser support for SVG icons is a separate question, outside this article. The standard does say a browser that cannot use one icon must try the next most appropriate, and that it selects among them by type, media and sizes — so if you declare an SVG, declare an .ico or PNG as well, each with its type.
Why does /favicon.ico return 200 when the file does not exist?
A single-page-app fallback is answering with index.html. Look at the content type: text/html means the browser asked for an image and got your home page. Publish a real icon and, unless a rule forces the rewrite, the host serves that instead.
Why does the icon show on my computer but not on the live site?
Something about the address changed between the two. Run the three requests above against the live site, and test the exact address the browser uses: the href of the tag the first request prints, or the origin root when there is no tag. A 404 there means the file is not at that address — not published, or published under a sub-path the address leaves out. A 200 with text/html means a fallback answered in its place.
Why is my favicon missing from Google results?
Google reads the icon from a <link> tag on the home page, on its own schedule. Check that the tag is there, that the icon is square, that robots.txt does not block the home page or the icon file, and that the address has not changed recently. Google says crawling can take from several days to several weeks.
Why is the icon on an iPhone's home screen not my favicon?
MDN notes that iOS does not read rel="icon" for the home screen. Add an apple-touch-icon tag; Apple's archived page also describes a search of the website root directory for apple-touch-icon.png when there is no tag.