You ship the site. The tab shows a grey globe.
So you open the generator you bookmarked years ago, and it hands you a zip with twenty-six files in it, a browserconfig.xml for a Windows tile system that is long dead, eight Apple sizes from the iPhone 4 era, and eleven <link> tags to paste into your head. You paste them. The tab icon appears. Nobody thinks about it again until someone adds the site to an iPad home screen and gets a black square.
The favicon ico vs png vs svg argument has a clean answer now, and it is much smaller than the zip file suggests. Three image files at your document root. Four tags. Two more PNGs and a manifest if you want the site installable, and not otherwise.
What follows is the reasoning: what each format buys you, why a .ico at the root still earns its place in a world where every browser reads PNG, where SVG quietly falls apart, and the exact head block to copy. I’ll flag the places where browser behaviour is inconsistent or has shifted, because several of them are.
The short answer
Ship three files at your document root and reference them with four tags.
favicon.ico is a real multi-resolution ICO holding 16x16, 32x32 and 48x48 images. Browsers request this exact path by convention with no tag at all, and a long tail of feed readers, chat unfurlers and crawlers look nowhere else.
icon.svg is one vector file for browsers that support it, so the mark stays sharp at any size and any display density without you exporting another raster.
apple-touch-icon.png is 180x180, opaque background, corners left square. iOS scales it down for smaller uses and applies its own rounding.
Add icon-192.png, icon-512.png and a site.webmanifest if you want Android installability and a proper splash screen. That is five images at most. For a simple two-colour mark the whole set lands somewhere around 20 KB, which is less than a single hero photo.
The head block, in full
Put this in <head>. Order matters, so keep it.
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
Four lines. A few things about them are worth spelling out, because every one of these details is something I have watched somebody get wrong.
The href values start with /. Root-absolute, not relative. A relative href="favicon.ico" resolves against the current URL, so it works on / and silently 404s on /docs/getting-started/. This is the single most common cause of an icon that appears on the homepage and vanishes everywhere else.
rel="icon" is the correct relation. rel="shortcut icon" is a fossil from Internet Explorer 5, where shortcut was never a registered link type. Browsers tokenise the value and find icon anyway, so it still works, but there is no reason to keep typing it.
The sizes="32x32" on the ICO line is a hint to Chromium that this file is a small raster, which nudges it toward the SVG when it can use one. It is a hint, not a contract.
Ordering matters because Chromium walks the icon declarations and takes the last one it can actually decode. Put the SVG after the ICO and a modern Chrome uses the SVG, while an older client that cannot parse it drops back to the ICO. Reverse the order and you may get the ICO everywhere. Firefox’s selection logic is not identical, and I treat “last supported wins” as a Chromium rule rather than a web-wide guarantee.
You do not need <link rel="mask-icon"> any more unless you care about pinned tabs in older Safari. Newer Safari versions generally use your regular icon and theme-color there instead. Shipping the monochrome mask SVG is harmless, but it is no longer part of the minimum set. If you want the whole set produced for you, the favicon generator emits exactly these four lines alongside the files.
Why favicon.ico still belongs at the root
Here is the part most “you don’t need .ico any more” posts skip.
Every other icon in your set requires a browser to fetch your HTML, parse it, find the <link> tag, and resolve the URL. favicon.ico requires none of that. When a client wants an icon for your origin and has no markup to work from, it issues a request to https://yourdomain.com/favicon.ico and takes whatever comes back. That behaviour predates the <link rel="icon"> relation and it has never been removed.
Situations where the tag is not available include a bare URL pasted into a chat client that wants a thumbnail, a feed reader showing a source icon, a browser painting a tab for a page that returned a 500, a bookmark manager, and a dashboard tool that never renders your page at all. None of those read your head. There is also the log-noise argument, which is small but real: if nothing sits at /favicon.ico, your access logs collect a steady drip of 404s from clients trying the convention anyway.
I’d push back on the usual counter-argument here. People say ICO is obsolete because it is a Microsoft container from 1995. True, and irrelevant. The format’s age has nothing to do with whether browsers fetch that path, and they do. You are not shipping ICO because it is a good format. You are shipping it because it sits at a well-known URL.
An .ico is a container, not an image
This one trips up more developers than it should. A .ico file does not hold “a favicon”. It holds a directory of images, each at its own dimensions and bit depth, and the consumer picks the one it wants.
The structure is simple enough to read by hand. The file opens with a six-byte ICONDIR: two bytes reserved and always zero, two bytes of type (1 for icons, 2 for cursors), and two bytes giving the image count. After that comes one sixteen-byte ICONDIRENTRY per image: width and height as single bytes each (a zero means 256), a colour count byte, a reserved byte, two bytes of colour planes, two bytes of bits per pixel, then a four-byte length and a four-byte offset pointing at the image data. The image payloads follow.
Each payload is either a BMP-style bitmap (a BITMAPINFOHEADER with the height field doubled to account for the AND mask) or a complete PNG stream embedded verbatim. PNG-inside-ICO works in Windows Vista and later, and in browsers, but conservative tooling still encodes small sizes as BMP and reserves PNG compression for 256x256. For web-only icons either encoding is fine.
You can check what is actually inside a file without special software:
$ file favicon.ico
favicon.ico: MS Windows icon resource - 3 icons, 16x16, 32 bits/pixel,
32x32, 32 bits/pixel, 48x48, 32 bits/pixel
$ xxd -l 6 favicon.ico
00000000: 0000 0100 0300 ......
Those six bytes read as: reserved 0, type 1, count 3. Three images. If yours says 0100 for the count, someone handed you a single-resolution ICO, which is a PNG in a costume and defeats the point.
Multi-resolution matters because scaling a 32x32 bitmap down to 16x16 in a browser is a generic filter with no idea what your icon means, and it turns a one-pixel stroke into a grey smear. A hand-tuned 16x16 frame lets you snap strokes to the pixel grid and drop detail deliberately. The ICO Converter will show you what is inside a file you inherited.
What SVG buys you, and exactly where it fails
An SVG favicon is one file that renders correctly at 16 CSS pixels on a 1x display, at 32 device pixels on a Retina laptop, at whatever size a browser picks for a bookmark grid, and at whatever size the next device invents. That is the whole pitch, and it is a good one.
It also carries something no raster can: a @media (prefers-color-scheme: dark) block inside the file, so your icon can change colour when the browser chrome goes dark. That is genuinely useful, and it is a real technique rather than a trick, but it has enough subtleties (the media query resolves against the browser’s colour scheme, not the page’s) that it gets its own treatment in SVG favicons and dark mode.
The failures are worth knowing before you commit.
External references do not load. No <image href="photo.png">, no <link> to a stylesheet, no web fonts, no <script>. The file has to be self-contained: inline <style>, paths, and nothing else. Text elements are a particular trap, because if the font is not present the glyph falls back to something you did not choose, so convert type to outlines before exporting.
Detail dies at 16 pixels regardless of vector precision. Vector means infinitely scalable, not infinitely legible. A logo with a hairline rule and a serif wordmark rasterises at 16x16 into roughly 256 grey pixels, and no amount of mathematical exactness saves it. The fix is design, not format.
Safari support has been inconsistent. Chrome, Edge and Firefox handle SVG favicons; Chromium picked it up in version 80 in early 2020 and Firefox supported them earlier still. Safari is the one I would not rely on, and I am deliberately not quoting a version number for it, because the behaviour has shifted and my information may be stale by the time you read this. Check Can I Use for the current state. The practical consequence is the same either way: declare the ICO alongside and any client that cannot decode the SVG falls back on its own.
Where PNG still wins
PNG’s advantage is exact control at a specific size. You decide what the 16x16 looks like, pixel by pixel, and nothing downsamples it on the way to the screen.
That matters in three places. Inside your ICO, where the 16 and 32 frames are effectively hand-tuned PNGs. At 180x180 for iOS, where the platform wants a raster with an opaque background. And at 192 and 512 for Android installs, where the operating system composites your icon into a launcher shape.
PNG’s disadvantage is that every size is another file and another export you have to remember to regenerate when the brand changes. The old twenty-file bundles were an attempt to cover every device by brute force, and they aged badly because the device list kept moving. One SVG plus three well-chosen rasters ages much better.
Format comparison
| Format | Used by | Needs a <link> tag? |
Best size(s) | Notes |
|---|---|---|---|---|
.ico (multi-res) |
All browsers, crawlers, feed readers, chat unfurlers, OS shortcuts | No, fetched at /favicon.ico by convention |
16, 32, 48 in one file | The only format guaranteed to be requested with no markup. Container, not a single image. |
.svg |
Chrome, Edge, Firefox; Safari inconsistent | Yes, with type="image/svg+xml" |
Any, but design for 16 | Supports prefers-color-scheme. No external refs, no scripts, no fonts. |
.png (tab icon) |
All browsers | Yes | 32x32 | Redundant if you ship a multi-res ICO. Useful only if you refuse to build an ICO. |
.png (apple-touch-icon) |
iOS, iPadOS, Safari | Recommended; also probed at root path | 180x180 | Must be opaque. Do not pre-round the corners. |
.png (manifest icons) |
Android, Chrome install prompts, splash screens | Via site.webmanifest |
192x192 and 512x512 | Ship a separate maskable entry rather than tagging one file any maskable. |
.webp / .avif |
Patchy in icon context | Yes | n/a | Saves bytes you will not notice on a 3 KB file. Not worth the compatibility risk. |
Serving them correctly
Getting the bytes right and the headers wrong is a very common way to lose an afternoon. A server that does not recognise .webmanifest will happily hand it back as text/plain, and Chrome will ignore the manifest without a console error you would notice.
| File | Content-Type to serve | Notes |
|---|---|---|
favicon.ico |
image/x-icon |
image/vnd.microsoft.icon is the IANA-registered type; both are accepted by browsers. Pick one and be consistent. |
icon.svg |
image/svg+xml |
Never text/xml or text/plain. Gzip or Brotli it; SVG compresses extremely well. |
apple-touch-icon.png |
image/png |
Already compressed. Do not gzip. |
site.webmanifest |
application/manifest+json |
Many servers have no mapping for this extension. Add one, or name the file manifest.json and serve it as application/manifest+json anyway. |
Verify with a single request rather than guessing:
curl -sI https://example.com/favicon.ico
You want a 200, a sensible Content-Type, and no redirect. A 301 or 302 on the icon URL is legal, adds a hop, and causes trouble with some crawlers, including Google’s.
Caching deserves a note. You cannot fingerprint /favicon.ico or /apple-touch-icon.png, because their whole value is sitting at a fixed, guessable path. So do not put a one-year immutable cache on them unless you enjoy shipping a rebrand that half your users never see. Cache-Control: public, max-age=604800 (seven days) is a reasonable compromise for the conventional paths. The SVG and manifest PNGs are referenced by tag, so you can hash their filenames and cache those hard.
Worked example: counting the files and the bytes
Say Bruno is launching Ledgerly, a small invoicing app. The mark is a rounded square with a stylised “L” cut out of it, two colours, no gradient. Flat vector artwork, which is the easy case.
The old generator output he was about to ship: 26 files totalling around 92 KB, including eight legacy Apple sizes (57, 60, 72, 76, 114, 120, 144, 152), four Windows tile PNGs, a browserconfig.xml, and separate 16 and 32 PNGs on top of the ICO. Eleven tags in the head.
Here is what he ships instead, with realistic byte counts for artwork this simple:
| File | Contents | Approx. size |
|---|---|---|
favicon.ico |
16, 32 and 48 frames | 4.2 KB |
icon.svg |
Two paths, one style block | 0.6 KB (0.3 KB gzipped) |
apple-touch-icon.png |
180x180, opaque | 3.4 KB |
icon-192.png |
192x192 | 3.1 KB |
icon-512.png |
512x512 | 9.8 KB |
site.webmanifest |
JSON | 0.4 KB |
Add it up: 4.2 + 0.6 + 3.4 + 3.1 + 9.8 + 0.4 = 21.5 KB across six files, against 92 KB across 26. He has removed 20 files and about 70 KB.
The request count is the more interesting number. On a first desktop visit in Chrome the browser fetches the SVG and, separately, /favicon.ico by convention. Two requests, roughly 4.5 KB on the wire once the SVG is compressed. The 192 and 512 PNGs are never fetched unless somebody installs the app, and iOS fetches apple-touch-icon.png only when somebody adds the site to their home screen. So on the overwhelming majority of page views, the real cost of Bruno’s icon set is under five kilobytes. The 92 KB bundle was not costing 92 KB per visit either, since most of those files were never requested. It was costing him a cluttered repo root and eleven lines of head he could not explain.
Building the set: ten minutes, in order
- Start from one square master at 512x512, vector source if you have it. Crop tight, so the mark nearly fills the canvas, because a favicon that respects your brand’s clear-space rules reads smaller and worse than one that does not.
- Make a simplified variant for the small sizes: drop the wordmark, thicken any stroke thinner than about two pixels at the target size, remove gradients and shadows. People skip this step, and it is the one that decides whether the icon is legible.
- Export PNGs at 16, 32 and 48 from the simplified variant, and at 180, 192 and 512 from the full one. Image Resizer handles this if you are working from a raster and do not have a design tool open.
- Give the 180, 192 and 512 exports an opaque background. iOS composites transparency onto black, and a transparent apple-touch-icon on a dark mark is the classic “why is my home screen icon a black square” bug.
- Build the ICO from the 16, 32 and 48 PNGs as a single multi-frame file. PNG to ICO does exactly this and keeps all three frames.
- Write the SVG by hand or export it, then open it in a text editor and delete anything that references an external resource. Convert text to paths.
- Put
favicon.ico,icon.svgandapple-touch-icon.pngin the directory that maps to your document root. For most frameworks that ispublic/. For a Next.js app router project,app/favicon.icoandapp/icon.svgare handled by the framework’s own conventions instead. - Paste the four-line head block.
- Run
curl -sIagainst each of the four URLs and confirm a200with the rightContent-Type. - Open the site in a fresh profile or a private window. Not a hard reload: Chrome’s favicon cache survives one, which is why so many people conclude their change did not work when it did.
If you would rather not do steps 3 to 6 by hand, the ToolFiddle favicon generator takes one square image and produces the multi-resolution ICO, the PNG sizes, the manifest and the head block in a single pass. It runs entirely in your browser, so artwork for an unannounced product never leaves your machine. Nothing is uploaded and there is no account to make.
The manifest, and whether you need one
A site.webmanifest does one job for icons: it tells Android and Chromium what to use when somebody installs your site. If nobody is installing your site, it changes nothing visible.
{
"name": "Ledgerly",
"short_name": "Ledgerly",
"icons": [
{ "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" },
{
"src": "/icon-maskable-512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0b3d2e"
}
The maskable entry is separate on purpose. Android crops your icon to a platform-chosen shape, so a maskable icon has to fill the entire square with background colour and keep the meaningful part inside a central safe zone of roughly 80% diameter. Tagging a single file "any maskable" means the same artwork gets used both cropped and uncropped, and one of those two will look wrong.
A malformed manifest fails quietly. One trailing comma and Chrome discards the whole file with no visible complaint on the page. Paste it through the JSON Formatter before you commit, or check the Application panel in DevTools, which reports what it parsed.
Where people lose time
Relative paths. Covered above, still the number one cause. href="favicon.ico" on /blog/post/ requests /blog/post/favicon.ico.
A stale /favicon.ico shadowing your new icon. You moved to /assets/icon.svg, updated the tag, and the old file is still at the root. Some clients still take it. Replace it, do not just stop linking to it.
Build tools hashing the conventional path. Some bundlers fingerprint everything in the assets directory, so favicon.ico becomes favicon.a3f91c.ico and the convention request 404s. Check what landed in your build output, not what is in your source tree.
A CDN on a different host. cdn.example.net/favicon.ico is fine for the tagged reference and a problem for anything that expects the icon on the page’s own origin.
Data URI favicons. <link rel="icon" href="data:image/svg+xml,..."> renders in Chrome and Firefox and is handy for putting a red icon on staging. It is not crawlable, and it does nothing for the convention request.
Then the two that survive every cache clear you would normally try: a Content Security Policy whose img-src directive excludes the icon’s host, and a service worker still serving a cached 404 from before the file existed.
For the ordered debugging procedure that covers all of these plus Chrome’s separate favicon cache, work through the nine causes of a favicon not showing rather than guessing.
One caveat on all of the above. Browser icon selection is not specified with the precision you would want, and implementations have changed more than once. Everything here reflects behaviour at the time of writing. If your icon does something strange in a specific browser version, trust what you observe over what any article tells you, including this one.
Frequently asked questions
Do I still need a favicon.ico in 2026?
Yes, and the reason is not legacy browsers. Browsers and many non-browser clients request /favicon.ico by convention with no HTML tag at all, so the file at that exact path is the only icon that works when your markup is missing, cached oddly, or never parsed. It costs a few kilobytes. Keep it.
How many favicon files do I actually need?
Three for a normal website: favicon.ico at the root containing 16, 32 and 48 pixel images, one SVG, and a 180x180 apple-touch-icon.png. Add icon-192.png, icon-512.png and a web app manifest only if you want the site installable on Android. Everything past that is old advice that survived because generators kept shipping it.
What sizes should be inside my favicon.ico?
16x16, 32x32 and 48x48. The 16 is the tab, the 32 covers higher density displays and Windows shortcuts, and the 48 is what Google looks for when it picks a search result icon. Adding 64 or 128 costs bytes and buys almost nothing, because every consumer of larger icons reads PNG or SVG instead.
Do all browsers support SVG favicons?
No. Chrome, Edge and Firefox render SVG favicons reliably. Safari has been inconsistent and I would not build on it. Because a browser that cannot parse the SVG falls back to another declared icon, declaring the .ico alongside it costs nothing and removes the question entirely.
Can I use a PNG instead of an ICO at the site root?
You can serve a PNG at /favicon.ico and browsers will usually sniff it and render it, because they inspect the bytes rather than trusting the extension. It works. It also confuses tooling, some crawlers and anything that parses the ICO header, so it is a shortcut with no real payoff.
Does the favicon have to live at the site root?
favicon.ico does, because the convention request goes to the origin root and nowhere else. apple-touch-icon.png is looked for at the root too when no tag exists. The SVG and manifest PNGs can live anywhere you like, including a hashed build directory, as long as the link tags point at them with root-absolute paths.
Where that leaves you
Three files, four tags, about five kilobytes on a typical page view. If you are carrying a twenty-file bundle from an old generator, deleting most of it is safe and takes ten minutes, and the only thing you must not delete is the ICO at the root.
The formats are not competing. The ICO answers a request nobody makes explicitly, the SVG handles every display density you will ever meet, and the PNGs exist because two operating systems want rasters with opaque backgrounds. Each one is doing a job the others cannot.
Get the artwork legible at 16 pixels first. That decision matters more than the file formats, and no amount of correct markup rescues a logo that turns to grey mush in a tab. Everything after that is copy and paste.