Mixed Content
Updated June 17, 2026
3 min read
Mixed content is when a page served over HTTPS loads sub-resources (scripts, styles, images, fonts, or iframes) over plain HTTP, breaking the page's encryption guarantees. Even though the main document is encrypted, those http:// requests travel in the clear and can be read or tampered with in transit.
What is mixed content?
When a browser loads https://example.com, it expects every byte of the page to be delivered over an encrypted, integrity-protected channel. Mixed content happens when the HTML references additional assets using http:// URLs instead of https:// — a hardcoded analytics script, an old image CDN, an embedded widget, or a stylesheet.
Browsers split mixed content into two categories:
- Passive (display) mixed content — images, video, and audio. These can be modified in transit but can't directly alter the rest of the page. Browsers typically still load them but downgrade the lock icon and log a console warning.
- Active mixed content — scripts, stylesheets, iframes, XHR/fetch, and web fonts. Because these can read and rewrite the entire page, modern browsers block them outright by default.
The result is a page that is partly insecure, with broken functionality wherever active resources were silently blocked.
Why it's a risk / how it's exploited
The whole point of HTTPS is that a network attacker — someone on the same Wi-Fi, a malicious ISP, or anyone in a man-in-the-middle position — can't read or change what's on the wire. An http:// resource throws that away for that request.
Concrete scenario: your HTTPS checkout page loads http://cdn.example.com/app.js. An attacker on the victim's network intercepts that plain-HTTP request and returns their own JavaScript instead of the real file. Because the script runs in the context of your HTTPS origin, it can read session cookies, scrape the payment form, exfiltrate data, or redirect the user — and the address bar still shows the padlock. The user has no signal that anything is wrong.
That's why browsers block active mixed content: a single insecure script can fully compromise an otherwise-encrypted page. Passive mixed content is lower severity but still leaks information (which images a user views) and undermines page integrity.
How to detect it
Open the page in Chrome or Firefox, open DevTools, and check the Console tab. Mixed content produces explicit messages like Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure resource 'http://...'. This request has been blocked. The Security panel and the lock icon also flag it.
From the command line you can grep the rendered HTML for hardcoded insecure references:
curl -s https://example.com | grep -Eo 'http://[^"'\'' ]+'
This catches http:// URLs in the initial markup, though resources injected by JavaScript at runtime will only show up in the browser console. A website vulnerability scanner crawls the whole site and flags every mixed-content reference across pages, including ones you'd miss checking by hand.
How to fix it
Serve every resource over HTTPS. In practice:
- Replace hardcoded
http://URLs in templates, CSS, and JavaScript withhttps://(or protocol-relative is no longer recommended — use explicithttps://). - Confirm each third-party origin actually serves the asset over HTTPS; if it doesn't, host it yourself or switch providers.
- As a backstop, add a Content-Security-Policy header that auto-upgrades insecure requests:
nginx
add_header Content-Security-Policy "upgrade-insecure-requests" always;
Apache
Header always set Content-Security-Policy "upgrade-insecure-requests"
upgrade-insecure-requests tells the browser to rewrite http:// sub-resource requests to https:// before sending them, so legacy URLs you missed still load securely. It's a safety net, not a substitute for fixing the source URLs — if the target host genuinely has no HTTPS endpoint, the upgraded request will simply fail.
Mixed content is part of basic HTTPS hygiene, alongside disabling old protocols like TLS 1.0. Once every page and resource is consistently HTTPS, the padlock means what users assume it means.
FAQ
Why are my images loading but my scripts aren't? Images are passive mixed content (loaded with a warning), while scripts are active mixed content and blocked by default. The blocked script explains the broken functionality.
Does upgrade-insecure-requests fix everything automatically?
It upgrades requests where an HTTPS version exists at the same host. If the resource is only available over HTTP, the request fails — you still need to fix or replace those URLs.
Not sure whether your HTTPS pages are pulling in insecure resources? Run a full scan to find mixed content and other HTTPS issues across your whole site.
