A broken image is easy to miss. On some pages it shows as a small icon with alt text, on others it leaves a blank gap, and if it's a CSS background you might see nothing at all. Visitors notice, though, and so do search engines when your product photos or diagrams return 404.
Here's why images break, how to find every broken one on a page or a site, and how to fix them so they stay fixed.
Why images break
Almost every broken image comes from one of these:
-
Case-sensitive paths.
Photo.JPGandphoto.jpgare the same file on macOS and Windows, but different files on a Linux server. An image that works locally can 404 in production. -
Relative paths that moved.
img/logo.pngon/aboutmeans/about/img/logo.png, not/img/logo.png. Moving a page to a deeper URL silently breaks it. - Deleted uploads. Someone cleans up the media library or a CDN bucket, and old posts still reference the files.
- Hotlinked images. You embedded an image from another site. They renamed it, removed it, or started blocking requests from other domains.
- A migration. The domain or folder structure changed, and the image URLs inside old content didn't.
-
Only some sizes are broken. A
srcsetlists five widths and one of them is missing. Most desktop browsers look fine, but some phones get the broken one.
Find broken images on one page
DevTools Network tab
Open the page, press F12, go to Network, choose the Img filter and reload. Click the Status column header to sort. Anything 404, 403 or 5xx is a problem. This also catches CSS background images and images loaded by JavaScript, because the browser actually requested them.
Try a narrow window too, or switch on device emulation, so the browser requests the smaller srcset candidates.
A console snippet
For <img> tags, the browser already knows which ones failed. An image that finished loading with a width of zero didn't decode:
const broken = [...document.images].filter(
(img) => img.complete && img.naturalWidth === 0
);
console.table(broken.map((img) => ({ src: img.currentSrc || img.src, alt: img.alt })));
Scroll to the bottom of the page first. Lazy-loaded images (loading="lazy") aren't requested until they are near the viewport, so they won't show up as failed yet.
Check every srcset candidate
The browser only fetches one candidate per image. To test all of them, collect the URLs and request each one:
const urls = new Set();
document.querySelectorAll('img[src], img[srcset], source[srcset]').forEach((el) => {
if (el.src) urls.add(el.src);
(el.srcset || '').split(',').forEach((part) => {
const u = part.trim().split(/\s+/)[0];
if (u) urls.add(new URL(u, location.href).href);
});
});
for (const url of urls) {
const res = await fetch(url, { method: 'HEAD' }).catch(() => null);
if (!res || !res.ok) console.warn(res ? res.status : 'failed', url);
}
Cross-origin images may fail this check because of CORS even when they load fine. Verify those with curl.
Check images from the command line
curl is the most reliable way to see what the server actually returns:
curl -sI https://example.com/images/hero.webp | head -n 5
Look at two things: the status line, and content-type. A 200 with content-type: text/html means the server returned an HTML page (often a "not found" template) instead of an image. That's a broken image that a status-only check would pass.
To test a list of image URLs at once:
while read -r url; do
curl -s -o /dev/null -w '%{http_code} %{content_type} '"$url"'\n' "$url"
done < images.txt | grep -v ' image/'
Anything printed is either an error status or not an image.
Find broken images across a whole site
Checking pages one by one doesn't scale past a handful. For a full site, you need a crawler that reads every page and tests every image URL it finds. Desktop SEO crawlers can do this, and linkbruno.com checks images along with links in its free 25-page scan, then lists each missing image with the page it's on.
Whatever you use, make sure it reports the source page for each broken image. A list of dead image URLs is not much help if you can't find which posts contain them.
How to fix them
Wrong case or path. Rename the file or fix the reference so they match exactly. Prefer root-relative paths (/images/logo.png) or absolute URLs in content, so moving a page doesn't break its images.
Deleted files. Restore from a backup or re-upload. If you can't, replace the image or remove the <img> tag. Don't leave an empty frame on the page.
Hotlinked images. Download the image (if you have the right to use it) and host it yourself. Relying on someone else's server for your page content is how this happened in the first place.
After a migration. Add redirects from the old image URLs to the new ones, the same way you would for pages, and then update the content so the redirect isn't needed.
Missing srcset sizes. Regenerate the image sizes. In WordPress, a thumbnail regeneration plugin does this; in a static site, rerun the image build step.
Make broken images less visible when they do happen
Images will break again eventually, so give them a graceful failure:
<img src="/images/chart.png" alt="Monthly signups, Jan to Jun" width="800" height="450">
-
Meaningful
alttext shows in place of the image and keeps the content understandable. -
widthandheightreserve the space, so a missing image doesn't make the layout jump.
You can also hide the broken-image icon with a small handler, but use it as a safety net, not a fix:
document.addEventListener('error', (e) => {
if (e.target.tagName === 'IMG') e.target.classList.add('img-missing');
}, true);
A short checklist
- Run the console snippet on your key pages after every deploy.
- Keep image paths lowercase and root-relative.
- Host your own images instead of hotlinking.
- Ship redirects for old image URLs when you migrate.
- Crawl the whole site for images, not just links, a few times a year.
Most broken images on a typical site trace back to one or two causes. Once you find the pattern, the fix is usually a single rename or redirect rule.
Top comments (0)