Mixed Content (HTTP on HTTPS)
Also Known As
TL;DR
Explanation
Mixed content occurs when a page served over HTTPS loads sub-resources over HTTP. There are two types with different browser treatment: active mixed content (scripts, stylesheets, iframes, XMLHttpRequest) is blocked by all modern browsers since 2020 — the resource simply doesn't load; passive mixed content (images, audio, video) triggers a security warning and Chrome attempts to auto-upgrade to HTTPS. Mixed content undermines HTTPS because an attacker on the network can intercept and modify the HTTP resource, injecting malicious content into an otherwise secure page. The most insidious source is CMS databases — content editors add images with http:// URLs before HTTPS migration, and those URLs persist in the database long after the site moves to HTTPS. A find-and-replace in the database is required, not just template changes. Chrome DevTools Security tab shows exactly which URLs caused mixed content.
Common Misconception
Why It Matters
Common Mistakes
- Fixing HTML templates but not database content — CMS-stored image URLs with http:// persist after HTTPS migration and cause mixed content on every page load.
- Hard-coded http:// URLs in CSS (background-image, @font-face src) — easy to miss during migration.
- Third-party embed code using http:// for their own scripts — must contact provider or find HTTPS version.
- Not testing after HTTPS migration — mixed content only appears after the switch; dev environments often don't enforce it.
Avoid When
- Do not rely on CSP upgrade-insecure-requests as a permanent fix — it does not upgrade active mixed content in all cases and is not a substitute for fixing URLs at source.
When To Use
- Fix mixed content immediately on any HTTPS site — active mixed content is blocked and breaks functionality.
- Use CSP header 'upgrade-insecure-requests' as a safety net during HTTPS migration to auto-upgrade passive mixed content.
Code Examples
<!-- HTTP image on HTTPS page — passive mixed content warning -->
<img src="http://example.com/product.jpg" alt="Product">
<!-- HTTP script on HTTPS page — BLOCKED by browser -->
<script src="http://cdn.example.com/analytics.js"></script>
<!-- Use HTTPS for all resource URLs -->
<img src="https://example.com/product.jpg" alt="Product">
<script src="https://cdn.example.com/analytics.js"></script>
<!-- Protocol-relative URLs as a fallback -->
<img src="//example.com/product.jpg" alt="Product">
<!-- Fix CMS database content -->
<!-- UPDATE posts SET content = REPLACE(content, 'http://', 'https://') WHERE content LIKE '%http://%' -->