Mixed Content: Why and How to Fix It
What mixed content warnings mean and how to eliminate HTTP resources on HTTPS pages.
Mixed Content: Why and How to Fix It
Mixed content occurs when an HTTPS page loads resources (images, scripts, stylesheets) over plain HTTP. Browsers either block these resources or display warnings, breaking your site's functionality and eroding visitor trust.
What Mixed Content Is
When your site is served over HTTPS, the browser expects all resources on the page to also use HTTPS. If any resource is loaded over HTTP, that's mixed content.
There are two types:
- Mixed active content (scripts, stylesheets, iframes, fetch requests) — browsers block these entirely. Your site breaks.
- Mixed passive content (images, audio, video) — browsers may load them with a warning, or block them depending on settings. Your padlock icon disappears.
Why Browsers Care
An HTTPS page with HTTP resources is only partially secure. An attacker on the network can intercept and modify the HTTP resources — injecting malicious scripts, replacing images, or stealing data. The HTTPS connection is meaningless if part of the page is unencrypted.
Finding Mixed Content
Method 1: Browser Developer Console
Open your site in Chrome, press F12, and check the Console tab. Mixed content errors look like:
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,
but requested an insecure image 'http://example.com/logo.png'.
Each error tells you exactly which resource is the problem.
Method 2: Search your codebase
Look for hardcoded HTTP URLs in your source code:
grep -rn "http://" --include="*.html" --include="*.css" --include="*.js" --include="*.php" --include="*.erb" .
Method 3: Search the database
For CMS sites (WordPress, etc.), mixed content often lives in the database — post content, widget settings, theme options:
# WordPress example
wp search-replace 'http://example.com' 'https://example.com' --dry-run
Tip: Always use
--dry-runfirst to see what would change before actually modifying the database.
Fixing Common Sources
Images
The most common source. Update image URLs from http:// to https://:
<!-- Before -->
<img src="http://example.com/photo.jpg">
<!-- After -->
<img src="https://example.com/photo.jpg">
<!-- Best: use protocol-relative or relative URLs -->
<img src="/photo.jpg">
Scripts and Stylesheets
These cause the most damage because browsers block them entirely:
<!-- Before -->
<script src="http://cdn.example.com/jquery.min.js"></script>
<link href="http://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<!-- After -->
<script src="https://cdn.example.com/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
CSS Background Images
Often forgotten because they're buried in stylesheets:
/* Before */
.hero {
background-image: url('http://example.com/bg.jpg');
}
/* After */
.hero {
background-image: url('https://example.com/bg.jpg');
}
Iframes
Embedded maps, videos, and widgets:
<!-- Before -->
<iframe src="http://www.google.com/maps/embed?..."></iframe>
<!-- After -->
<iframe src="https://www.google.com/maps/embed?..."></iframe>
Fonts
Web fonts loaded from external services:
/* Before */
@font-face {
src: url('http://example.com/font.woff2');
}
/* After */
@font-face {
src: url('https://example.com/font.woff2');
}
Quick Fix: upgrade-insecure-requests
If you have too many mixed content issues to fix one by one, use the upgrade-insecure-requests CSP directive as an immediate stopgap:
Nginx:
add_header Content-Security-Policy "upgrade-insecure-requests;" always;
Apache:
Header always set Content-Security-Policy "upgrade-insecure-requests;"
HTML meta tag:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests;">
This tells the browser to automatically rewrite HTTP URLs to HTTPS before making requests.
Warning: This only works if the resources are actually available over HTTPS. If a third-party server doesn't support HTTPS, the request will fail silently instead of loading over HTTP.
WordPress-Specific Fixes
WordPress sites are particularly prone to mixed content after migrating to HTTPS:
Update WordPress URLs in Settings:
Settings > General > WordPress Address (URL) → https://example.com
Settings > General > Site Address (URL) → https://example.com
Search and replace in the database:
wp search-replace 'http://example.com' 'https://example.com' --all-tables --precise
Common plugins that help: - Really Simple SSL (quick fix, but masks the underlying problem) - Better Search Replace (for manual database updates)
Tip: The best long-term fix is always to update the actual URLs rather than relying on automatic upgrades.
upgrade-insecure-requestsand plugins are stopgaps while you do the real cleanup.
Verifying the Fix
After making changes:
- Clear your browser cache (or use incognito mode)
- Open the browser console — there should be zero mixed content warnings
- Check that the padlock icon appears in the address bar
- Test multiple pages, not just the homepage — mixed content often hides on inner pages
Run a SiteWatch scan after fixing to confirm your site is clean across all monitored pages.