OG Image CDN Caching: Why Your Image Won't Update and How to Fix It

Social platforms cache your og:image aggressively. Here's why CDN caching causes stale OG previews and how to bust the cache across Twitter, LinkedIn, Facebook, Slack, and Discord.

Why social platforms cache OG images

When a user shares a URL, Twitter, LinkedIn, Slack, and Discord's scrapers fetch your page HTML and then separately fetch the image at og:image. They cache the image on their own CDN — sometimes for hours, sometimes for weeks — so they can serve link previews fast without hammering your server.

The problem: if you update the image at the same URL, the cached version stays live on the platform's CDN until their TTL expires or you manually purge it.

Platform-by-platform cache behavior

PlatformTypical Cache TTLPurge Method
Twitter / X~7 dayscards.twitter.com/validator
LinkedIn~7 daysLinkedIn Post Inspector
Facebook~30 daysFacebook Sharing Debugger
Slack~24 hoursNo manual purge — wait or change URL
Discord~24 hoursNo manual purge — use URL versioning

Fix 1: Change the image URL (most reliable)

Every social platform treats a different URL as a new image. The fastest way to bust the cache is to change the og:image URL:

<!-- Before -->
<meta property="og:image" content="https://mysite.com/images/og-home.png" />

<!-- After (add a version query param or change the filename) -->
<meta property="og:image" content="https://mysite.com/images/og-home.png?v=2" />
<!-- OR -->
<meta property="og:image" content="https://mysite.com/images/og-home-v2.png" />

For dynamic OG images generated on-demand, append the content's updated_at timestamp as a query param:

<meta property="og:image"
  content="https://mysite.com/api/og?slug=my-post&v=1711234567" />

Fix 2: Set correct Cache-Control headers on your image

If your images are served from your own server or CDN, set a reasonable TTL so scrapers can revalidate after updates:

# For static OG images that change infrequently
Cache-Control: public, max-age=86400, s-maxage=86400

# For dynamic OG images that may change often
Cache-Control: public, max-age=3600, s-maxage=3600

# For versioned OG images (use long TTL since URL changes on update)
Cache-Control: public, max-age=31536000, immutable

Note: these headers control your CDN's behavior, not the social platform's internal cache. Once Twitter/LinkedIn have cached the image, you can't evict it remotely with headers alone.

Fix 3: Use platform debuggers to force re-scrape

Most platforms provide a tool to manually re-scrape a URL and refresh their cached preview:

Fix 4: Vercel / Next.js OG image caching

If you generate OG images with next/og (ImageResponse), Vercel caches them at the edge. To control this, set cache headers in your route handler:

// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const title = searchParams.get('title') || 'Default Title';

  return new ImageResponse(
    <div style={{ background: '#0f0f0f', width: '1200px', height: '630px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <h1 style={{ color: 'white', fontSize: 60, padding: '0 60px' }}>{title}</h1>
    </div>,
    {
      width: 1200,
      height: 630,
      headers: {
        // Cache for 1 hour at edge, 1 day in browsers
        'Cache-Control': 'public, s-maxage=3600, max-age=86400',
      },
    }
  );
}

Verify your OG image is refreshed

After making changes, paste your URL into OGFixer to see the live image that scrapers will fetch — not the cached version your browser shows. OGFixer always fetches fresh from the server, so you get ground truth.