You paste the link into the composer and wait for the card. Nothing arrives. No image, no title, not even a grey placeholder box, just the raw URL sitting in your draft looking like something you forgot to finish. So you open the page in your browser and it loads fine. You view source and og:image is right where you left it.
Almost everything written about broken previews assumes a card rendered and got something wrong. That is a different problem. A wrong card means a crawler read your page, stored an answer, and is showing you a stale copy of it, which is why clearing the cache fixes it. A missing card means no crawler ever got an answer. There is nothing cached to clear. Something either refused the request or handed back bytes the crawler could not use.
What Actually Happens When You Paste a Link
The thing that fetches your page is not a browser, and it is not Googlebot either. It is a single HTTP request from a datacenter, with no cookies, no session, no JavaScript engine, and a time budget measured in seconds. Meta's crawler documentation is unusually direct about the constraints it imposes: your Open Graph properties have to appear in the first 1 MB of the response, your server has to support gzip or deflate, and the content has to load within seconds.
Slack is even more frugal. Its robots page says Slackbot-LinkExpanding fetches as little of the page as it can, using HTTP Range headers, and caches what it finds globally for around thirty minutes. It is reading the top of your document and hanging up.
Which means the crawler is never really looking at your page. It is looking at the first chunk of bytes your server decides to hand an anonymous machine it has never seen before. Most blank cards are decided in that sentence.
Three things have to go right, in that order. The crawler has to reach your server. It has to find the tags in whatever came back. Then a second request, sometimes from a completely different fetcher, has to get your image. All three failures look identical in the composer, which is why so many people spend an afternoon fixing the wrong one.
Did the Crawler Reach Your Server At All?
Start here. It is the most common failure and the only one you can rule out in ten seconds. Pretend to be the crawler and look at nothing but the status code:
curl -sS -A 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)' -o /dev/null -w '%{http_code}' https://example.com/your-page
Anything other than 200 is your answer, and each code points somewhere specific. A 403 means something is inspecting the user agent and rejecting it. A 401 usually means basic auth is still switched on, which is the classic staging-site-went-live failure. A 404 on a URL that works in your browser points at a routing rule that treats unknown clients differently. A 503 or a timeout means the crawler's few seconds ran out before your origin answered.
Run it against each crawler you care about, because they are not treated alike by your infrastructure and they do not behave alike either:
- facebookexternalhit/1.1 serves Facebook, Instagram, Messenger, and WhatsApp previews.
- Twitterbot serves X cards. Version numbers change, so match on the name.
- LinkedInBot serves LinkedIn, and returns nothing at all when it is disallowed.
- Slackbot-LinkExpanding 1.0 reads the page and Slack-ImgProxy 0.19 fetches the image, as two separate clients.
- Discordbot/2.0 builds Discord embeds.
- Bluesky Cardyb/1.1 runs once, at compose time, and never comes back.
If you get a 403, check robots.txt first, but do not assume it is the culprit. The crawlers disagree wildly on whether they care. Meta obeys robots.txt for previews, with two caveats worth knowing: it caches your robots.txt for up to 24 hours, so a fix there is not instant, and it reserves the right to bypass the file when running security or integrity checks. LinkedInBot obeys it strictly. Slack states plainly that it does not honour robots.txt at all, on the reasoning that it does not follow links and is acting on behalf of a human. So robots.txt is neither a reliable way to block preview crawlers nor a reliable way to fix them.
The Firewall Rule You Wrote Two Years Ago
When robots.txt is clean and you are still getting a 403, the block is at the edge. This has become the single most common cause of a missing preview, and the reason is the wave of AI scraper blocking that everyone turned on between 2024 and now.
Here is the part that gets reported wrong. Cloudflare's much-discussed decision to block AI crawlers by default is not what breaks your previews. Cloudflare treats social and link preview fetching as its own behaviour category, names Facebook, Slack, Twitter, and Discord preview tools explicitly in its verified bots documentation, and excludes verified bots from its default configurations. The default is fine. What kills previews is the hand-written rule sitting above it: a custom WAF rule that challenges unrecognised user agents, an IP block on a datacenter range, a managed ruleset someone set to block rather than log. Cloudflare's own community forum is full of these, including people getting a 403 despite having written a skip rule that they expected to cover it.
The other version of this is geography. Preview crawlers fetch from wherever their infrastructure lives, not from your reader's country, so a country-level block or a compliance redirect can hide your tags from every platform at once while looking perfectly healthy to everyone on your team.
Could It Find the Tags in What Came Back?
A 200 gets you to the second gate, where the request succeeded and the tags still were not there. The biggest cause is that they were never in the response to begin with. If your framework injects meta tags after hydration, the crawler receives a shell. You can see why from how these fetchers are built: something that asks for a byte range and hangs up has plainly not rendered a page. Grep the raw bytes rather than trusting your browser's inspector, which shows you the DOM after JavaScript has had its way with it:
curl -sS -A 'facebookexternalhit/1.1' https://example.com/your-page | grep -i 'og:'
Empty output means the tags are client-rendered, or outside the head element, or past the point where the crawler stopped reading. Meta's 1 MB ceiling sounds generous until you meet a page that inlines its entire CSS bundle above the meta tags. There is also a subtle server-side version of this: Meta asks that you either honour Range header byte requests properly or ignore the header entirely. A server that splits the difference, answering with a 206 and a truncated body that stops before the head closes, will pass every browser test you throw at it and fail every crawler.
Redirects are the other budget problem. Each hop costs DNS, TLS, and a round trip, and the crawler is counting. WhatsApp gives up after about ten seconds for the entire chain, which is why its previews fail more often on mobile than anything you test at your desk. Affiliate and tracking links routinely stack three or four hops before the real page is reached.
One more, if you already fixed a firewall rule and nothing changed: your CDN may be serving the cached 403 it stored while the rule was active. Purge the edge cache for that URL before concluding the fix did not work.
Why the Title Shows But the Image Doesn't
This is the third gate, and it is a genuinely separate request that your allowlist probably does not cover. Discord fetches your HTML as Discordbot and then proxies your image as something else. In a discussion on its own API repo, Discord explained the split as deliberate: crawling a URL is a bot action, but proxying an image is done in response to a user loading it, so the image request carried a browser user agent instead. For years that meant bot management would wave the page through and block the picture. Reports suggest image fetching started identifying itself properly in late 2024, though video proxying has still been seen using the old Firefox string. Slack has the same architecture without the confusion, since Slack-ImgProxy announces itself honestly.
The practical consequence is that allowlisting the crawler is not the same as allowlisting the image fetch. Hotlink protection, referrer checks, and signed CDN URLs all block the second request while leaving the first one working, which produces exactly the half-rendered card people find so confusing.
Then there are the flat requirements. Meta's image documentation is the strictest published set, and clearing it clears most other platforms:
- An absolute https URL. A path like /img/card.png is not a valid og:image.
- At least 200 × 200 pixels, which is Meta's hard floor. Use 1200 × 630 at close to 1.91:1 for a full-width card.
- Under 8 MB. Above that, Facebook drops it.
- og:image:width and og:image:height declared in the markup.
- Reachable with no cookie, login, referrer check, or hotlink rule in the way.
- Served with a real image Content-Type, not text/html from a redirect.
That fourth point explains something that looks like magic otherwise. Meta says declaring the dimensions lets the crawler render your image immediately instead of asynchronously downloading and processing it first. Leave them out and the very first share of a URL often appears with no image, while every share afterwards looks correct, because by then the async job has finished. If your previews are broken only the first time each link goes out, this is why. The full set of tags worth declaring is in Open Graph tags explained.
Which Debugger Still Tells You Anything
Facebook's Sharing Debugger remains the most informative tool any platform offers, because it shows you the response its crawler actually received along with the errors it hit, which turns a guess into a reading. LinkedIn's Post Inspector re-fetches and reports on the spot. Be clear about its limit, which LinkedIn states directly in its own help article: the changes you make will only affect the preview for new posts that include the URL. The post that already went out stays broken.
X is the gap. Its Card Validator was retired and nothing replaced it, so every guide still telling you to run your URL through it is describing a page that no longer exists. Discord, Slack, and WhatsApp never published a debugger in the first place. For those four you need a third-party fetcher, or your own server logs, which is the underrated option here: grep your access log for the crawler user agent and you will see the request, the status code, and the timing without guessing at any of it.
Prelinq's Open Graph checker fetches a URL live and renders the card as X, LinkedIn, Facebook, Discord, Reddit, and Bluesky will each build it, which covers the platforms that gave up on giving you a tool.
Where Fixing Your Tags Stops Helping
Two honest limits. If the crawler was blocked deliberately, unblocking it is a real tradeoff rather than an oversight: facebookexternalhit generates heavy traffic, it is one of the noisiest agents in most access logs, and Meta reserves the right to ignore your robots.txt during integrity checks anyway. Someone may have blocked it on purpose and been right to.
The bigger limit is ownership. Every fix above assumes the server is yours. Most links people share are not: a news article, a partner's launch page, a merchant's product listing, an affiliate redirect. You cannot add og:image:height to a page you have no login for, and a redirect endpoint like go.brand.com/ref/yourcode was never built to serve preview tags in the first place, which is why those links so reliably render nothing at all. There is no lever to pull on someone else's site.
Prelinq moves the tags onto a page you do control. You set the title, description, and image, share the Prelinq link instead of the raw destination, and every crawler that comes looking reads your card. Anyone who clicks gets forwarded to the original URL.
It also removes this entire category of problem by construction. The page is server-rendered, so the tags are in the first bytes of the response. Nothing sits in front of it challenging unfamiliar user agents. The image is served without hotlink rules, with its dimensions declared, so the first share renders like the hundredth. And because the tags live on a URL you own rather than one you borrowed, they stay editable after you post, which is the one thing no debugger on any platform can give you. Two active links are free, which is enough to test a real share end to end before you need it to work.