A page served over HTTPS that loads any subresource — image, script, stylesheet, iframe, font, XHR — over plain HTTP is “mixed content.” All current browsers block active mixed content (scripts, iframes, XHR) outright. Passive mixed content (images, audio, video) used to be allowed with a warning; modern Chrome and Firefox upgrade or block these too. The page still loads; it just loads broken.
Why it matters
Three concrete failure modes:
- Scripts and stylesheets don’t execute. Layout collapses, interactivity dies, hydration fails. To a crawler, the page often looks like an empty shell.
- Render-blocking failures cascade into Core Web Vitals collapse. LCP misses because the hero image was blocked. CLS spikes because the stylesheet that sized the layout never loaded.
- Search Console flags it as a security issue. Mixed content shows up under Security Issues, and a page that browsers won’t render fully is unlikely to rank.
This is not a soft signal. Browsers actively break the page.
How to detect it
Open DevTools → Console. Mixed content warnings appear as red errors:
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,
but requested an insecure resource 'http://cdn.example.com/main.js'.
This request has been blocked; the content must be served over HTTPS.
At scale, two production-grade approaches:
- CSP
report-only. Set a Content Security Policy in report-only mode and collect violations:
Content-Security-Policy-Report-Only: default-src https:; report-uri /csp-reports
- Crawl with a headless browser that records all subresource requests, then filter for
http://schemes.
The fix
Two paths. Do them in order.
Step 1 — Fix the source
Find the HTTP URLs in your codebase and switch them to protocol-relative or absolute HTTPS:
<!-- Wrong -->
<img src="http://cdn.example.com/hero.jpg" />
<script src="http://analytics.example.com/tag.js"></script>
<!-- Right -->
<img src="https://cdn.example.com/hero.jpg" />
<script src="https://analytics.example.com/tag.js"></script>
Protocol-relative URLs (//cdn.example.com/hero.jpg) work but are considered legacy — write absolute HTTPS.
Step 2 — Upgrade legacy URLs at the response layer
Some mixed content is unfixable at the source — old database content with hardcoded http:// image URLs, user-generated content, third-party widgets. Use upgrade-insecure-requests to tell the browser to retry every HTTP subresource as HTTPS:
Content-Security-Policy: upgrade-insecure-requests
Or as a meta tag in <head>:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
This only works if the origin actually serves HTTPS. If http://cdn.example.com/hero.jpg upgrades to https://cdn.example.com/hero.jpg and the CDN doesn’t have a valid cert there, the resource fails entirely. Test before shipping.
WordPress
-- Find legacy http:// references in content
SELECT ID, post_title FROM wp_posts
WHERE post_content LIKE '%http://example.com%';
The Better Search Replace plugin handles this safely (it serializes PHP arrays correctly — a plain UPDATE query will corrupt them).
Nginx
Add the upgrade header globally:
add_header Content-Security-Policy "upgrade-insecure-requests" always;
Apache
Header always set Content-Security-Policy "upgrade-insecure-requests"
Common pitfalls
http://insrcset. Easy to miss in audits that only checksrc.http://in inline CSSurl(...). Same deal — most scanners check tags, not stylesheets.- Iframes from ad networks. Modern ad tags emit HTTPS but legacy creatives sometimes don’t. Lock the ad network into HTTPS-only delivery.
- Hardcoded
http://in JSON-LDimageproperties. Search engines fetch these. Broken image URLs in schema = lost rich result eligibility. upgrade-insecure-requestsas the only fix. It’s a patch, not a cure — it doesn’t fix the underlying data, and a future CSP change can re-expose the bug.