You changed the image, redeployed, pasted the link into Slack, and got the picture from three weeks ago. Or worse: a grey box with the domain name in it. This is the single most common Open Graph problem, and almost every case comes down to one of two things. Either the crawler cached a previous answer, or the crawler never successfully fetched your image in the first place.
Those two have completely different fixes, so the first job is telling them apart.
First, work out which problem you have
Open the page you are sharing and view source. Do not trust your framework's dev tools or a React component you believe renders the tag: look at the HTML the server actually sent. Search it for og:image.
- The tag is missing from the source. Your image was never the problem. The tag is being injected on the client, and crawlers do not run your JavaScript. Render it server side.
- The tag is there and the URL is right. You have a caching problem. Skip to forcing a refresh.
- The tag is there and the URL is wrong. Fix the URL first. The next section is a list of the ways it goes wrong.
Copy the value of the content attribute and paste it into a private browsing window. If the image does not load there, with no cookies and no session, it will not load for a crawler either.
Eight reasons a crawler never got your image
- The URL is relative.
content="/og.png"is not enough. A crawler resolvesog:imageon its own, without your page as a base, so it must be absolute and include the scheme:https://example.com/og.png. - It is behind a login, a preview deploy, or basic auth. Crawlers carry no cookies and none of the headers you set. A protected preview URL fails here, which is why the card works locally and breaks in production.
- robots.txt blocks it. Several unfurlers,
facebookexternalhitamong them, read robots.txt before fetching the image. A blanketDisallow: /api/will quietly kill a card served from an API route. - The content type is wrong. The response has to be
image/png,image/jpegorimage/webp. A route that returnstext/htmlon error, orapplication/octet-stream, gets discarded. - The file is too large. Stay under 5 MB. Some platforms cap lower, and a crawler that times out mid-download treats it as a failure rather than retrying.
- It redirects. Some crawlers follow redirects on
og:imageand some do not. Point at the final URL. - It is not reachable from outside your network.
localhost, a private IP, or an internal CDN hostname all look fine to you and are unreachable to everyone else. - The tag is inside
<body>. Meta tags belong in<head>. Some parsers stop reading at the first<body>tag and will never see it.
What each platform caches, and for how long
Every platform caches the first successful response it got, and a failure counts as a response. That last part is what catches people: if the crawler hit your page during a deploy and got a 404 for the image, some platforms will serve that empty result for days without trying again.
| Platform | Roughly how long | How to force a refresh |
|---|---|---|
| Around 7 days | Sharing Debugger, then Scrape Again | |
| Around 7 days, historically longer | Post Inspector, which re-crawls on submit | |
| X (Twitter) | Around 7 days | No public debugger. Change the URL |
| Slack | Around 30 minutes for the page, far longer for the image | No debugger. Change the URL |
| Discord | Hours to days | No debugger. Change the URL |
| WhatsApp and iMessage | Effectively indefinite per URL | No debugger. Change the URL |
| Until the next crawl | Request indexing in Search Console |
Treat those durations as folklore rather than contract. None of them are published guarantees, and they change.
Forcing a refresh
The reliable way: change the URL
Caches are keyed on the image URL. A URL that has never been seen cannot be stale. This is the only method that works on every platform at once, including the ones with no debugger, and it is what to reach for when something is going out in ten minutes.
<!-- Before -->
<meta property="og:image" content="https://example.com/og/launch.png" />
<!-- After: same file, new cache key -->
<meta property="og:image" content="https://example.com/og/launch.png?v=2" />
<!-- Better: a content hash, so it changes exactly when the image does -->
<meta property="og:image" content="https://example.com/og/launch.8f3c21.png" />A query string is enough for most platforms. A new file name is enough for all of them, and it is worth the extra step for anything you cannot re-announce.
The polite way: ask the platform to re-crawl
- Facebook, WhatsApp and Instagram share one cache. Run the page URL through the Sharing Debugger and press Scrape Again. The debugger also prints which tags it found, which makes it a useful validator even when nothing is broken.
- LinkedIn re-crawls whenever you submit a URL to the Post Inspector. LinkedIn has historically been the worst offender for long caching, so check here before you announce anything there.
- X, Slack, Discord and iMessage publish no debugger. Change the URL, or share the link into a private channel once to warm the cache with the correct version before the real post goes out.
- Google picks the image up on its next crawl. Use URL Inspection in Search Console and request indexing if you need it sooner.
While you are in there, check the whole tag set
Most broken cards are missing more than the image. This is the full set worth having, and it is what the Ship It section of the OG image generator builds for you from whatever you type into the editor.
<meta property="og:title" content="Your headline" />
<meta property="og:description" content="One supporting sentence." />
<meta property="og:image" content="https://example.com/og/launch.png" />
<meta property="og:url" content="https://example.com/post" />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary_large_image" />twitter:cardset tosummary_large_imageis what turns the X preview from a small square thumbnail into a full width card. Without it the image is technically working and still looks broken.og:urlshould be the canonical URL. It is what platforms key their cache on, and it stops three tracking-parameter variants of one page being cached as three different previews.- You do not need
twitter:image. X falls back toog:image. Set it only when you deliberately want a different picture on X. - Adding
og:image:widthandog:image:heightlets a platform reserve the right space before the file finishes downloading. Use the real numbers, usually 1200 and 630.
How to stop it happening again
- Version the file name, not just the query string. If the image is generated at build time, put a content hash in the name. The problem disappears permanently, because the URL then changes exactly when the picture does.
- Never point
og:imageat a URL you intend to overwrite in place. That is the setup that produces this bug. - Validate before you announce, not after. Run the URL through the Sharing Debugger and the Post Inspector as the last step of shipping, while a mistake is still cheap to fix.
- Check the card at a small size. WhatsApp and iMessage render previews at a fraction of the size of a Slack unfurl. A headline that works at full size can be unreadable there, so keep it short and keep it away from the edges.
The two minute checklist
- View source on the live page and confirm
og:imageis in the HTML the server sent. - Confirm the URL is absolute, with
https://on the front. - Open that URL in a private window. It has to load with no login.
- Confirm robots.txt does not disallow the path it sits on.
- Confirm the response is under 5 MB with an image content type.
- Add
?v=2to the URL, or rename the file. - Run the page through the Sharing Debugger and the Post Inspector.
- Share into a private channel to check the result before it goes public.
If the picture still has not moved after all of that, the tag on the page is almost certainly not the tag you think it is. Go back and view source one more time.