You did everything right. og:title is set, og:image is an absolute HTTPS URL to a 1200×630 JPG, and "view source" shows the tags in the first HTML response. Then you paste the link into LinkedIn and get a bare URL. Facebook shows the domain and nothing else. Slack shows a title but no image.
When the tags are correct and the preview is still empty, the problem is usually not your markup. Something in front of your app is refusing to talk to the crawler. This post is a checklist for that layer: CDNs, WAFs, bot protection, geo rules and hotlink protection.
When someone shares a link, the platform (not the person) fetches your page. That request looks nothing like a browser visit:
| Platform | Typical user agent (prefix) | Where the request comes from | |---|---|---| | Facebook / Messenger / Instagram DMs | facebookexternalhit/1.1, sometimes Facebot | Meta's data centres | | LinkedIn | LinkedInBot/1.0 | LinkedIn's servers | | X | Twitterbot/1.0 | X's servers | | Slack | Slackbot-LinkExpanding 1.0 | Slack's servers | | Discord | Discordbot/2.0 | Discord's servers | | Telegram | TelegramBot (like TwitterBot) | Telegram's servers | | WhatsApp | WhatsApp/2.x | The sender's phone, not a server |
These requests run no JavaScript, keep no cookies, never solve a CAPTCHA, often come from another country, and sometimes arrive in bursts when a post spreads. To a modern bot-protection product, that looks a lot like a scraper. So it gets a challenge page, a 403, or a 429, and the platform quietly gives up.
The platform never tells the person sharing the link. They just see an ugly card and assume your site is broken.
Before touching any dashboard, find out which crawlers fail. This loop fetches the page and the OG image the way each crawler would, and prints the status code and content type:
What you want to see for every row: 200 text/html for the page and 200 image/jpeg (or png/webp) for the image, with a sensible size.
What the bad rows usually look like: 403 text/html: a WAF rule or bot-fight mode blocked it. 503 or 200 text/html on the image URL: a JavaScript challenge or "checking your browser" page was served instead of the file. The 200 is a lie. The crawler got HTML, not a JPG. 429: rate limiting. 301 loops or a login page: an auth wall or a redirect that only browsers survive.
One important limit: curl from your laptop is not curl from Meta's network. A rule that blocks by country, ASN or IP reputation will let your request through and block the real crawler. If curl looks fine but the platform still fails, go to Step 2.
Each big platform has an official tool that shows its own fetch result: Facebook Sharing Debugger (developers.facebook.com/tools/debug) shows the response code Meta got, the final URL after redirects, and which tags it read. If it says "Response Code: 403" or "Could not scrape URL", that is your firewall talking. The Facebook link preview guide on OG Scope explains how the cache behaves once the fetch works, which matters because Facebook keeps the broken scrape until you ask for a new one. LinkedIn Post Inspector (linkedin.com/post-inspector) is blunter than the post composer. If its crawler cannot fetch the image file, it tells you, while the composer just drops the picture. The LinkedIn preview notes cover which tags LinkedIn reads and why it can keep showing an older card.
There is also a useful middle step between curl and the platform tools. A third-party checker such as OG Scope fetches your page from its own server, without running JavaScript, and draws estimated cards for Facebook, LinkedIn, X, Slack, WhatsApp, Discord and Telegram. If OG Scope can read your tags but Facebook cannot, the block is specific to Meta's network or user agent. If neither can, the block is broader, or the tags only exist after JavaScript runs.
CDN bot-protection features (Cloudflare Bot Fight Mode, "I'm Under Attack" mode, similar settings at other providers) challenge clients that do not behave like browsers. Preview crawlers never pass a JavaScript challenge. If you turned on an aggressive mode during a traffic spike and forgot about it, your previews have been broken since that day. Custom WAF rules that match on user agent
Rules like "block anything that is not Chrome, Safari or Firefox" or "block user agents containing bot" are common copy-paste snippets. They take out every preview crawler in one line. Geo-blocking
If you block all countries except your own (very common for local stores, banks, and sites that got hammered by foreign traffic), remember that Meta, LinkedIn, X and Slack mostly fetch from data centres in the US and Europe. Your Pakistani or Indonesian visitors can see the page fine and still get a blank card in their group chats.
WhatsApp is the exception. Its preview is built on the sender's phone, so a geo rule usually does not break WhatsApp, but a user-agent rule or a "browsers only" challenge will. Rate limiting
A popular post can make a platform re-fetch the URL many times in a short window. A strict per-IP limit on / or on your image path can start returning 429s at exactly the moment the post is getting attention. Hotlink protection on the image path
Hotlink protection rules decide whether to serve an image based on the Referer header. Crawlers often send none, or send their own. If your rule only serves images when the referer is your own domain, the HTML fetch succeeds and the image fetch fails, which is the classic "title but no picture" card. The OG image checker on OG Scope is handy here because it reports whether the image file itself loaded, separately from the tags. Signed or expiring image URLs
