How to Fix Mixed Content Warnings
Stop HTTP resources from breaking your HTTPS security
TL;DR
TL;DR (15 minutes):
Open DevTools Console to find mixed content errors. Update all http:// URLs to https:// in your code, CMS, and database. Add Content-Security-Policy: upgrade-insecure-requests header as a safety net. For resources that don't support HTTPS, self-host them or find alternatives.
Prerequisites
- HTTPS already configured on your site (see HTTPS setup guide)
- Access to your website's source code or CMS
- Database access if content URLs are stored there
- Browser with DevTools (Chrome, Firefox, Edge)
Understanding Mixed Content
Mixed content occurs when an HTTPS page loads resources over HTTP:
Why mixed content is dangerous: HTTP resources can be intercepted and modified by attackers. A malicious script injected through an HTTP resource has full access to your HTTPS page, including user data, cookies, and forms.
Types of Mixed Content
| Type | Resources | Browser Behavior |
|---|---|---|
| Active (Blocked) | Scripts, stylesheets, iframes, fetch/XHR | Blocked by default, breaks functionality |
| Passive (Displayed) | Images, audio, video | Loaded with warning, degrades security indicator |
Step 1: Find Mixed Content
Use browser DevTools
Open DevTools (F12) and check the Console tab for errors:
// You'll see errors like:
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,
but requested an insecure image 'http://cdn.example.com/image.jpg'.
This content should also be served over HTTPS.
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,
but requested an insecure script 'http://example.com/script.js'.
This request has been blocked; the content must be served over HTTPS.
Check the Security tab
In Chrome DevTools, the Security tab shows a summary:
- Green = Secure, no mixed content
- Yellow/Orange = Mixed content warnings
- Red = Certificate or security errors
Scan with online tools
# Use Why No Padlock for quick scan:
https://www.whynopadlock.com/
# Or JitBit's SSL checker:
https://www.jitbit.com/sslcheck/
# For larger sites, use a crawler like:
https://www.screamingfrog.co.uk/seo-spider/
Search your codebase
# Find hardcoded HTTP URLs in your code
grep -r "http://" --include="*.html" --include="*.js" --include="*.css" --include="*.jsx" --include="*.tsx" .
# Common patterns to look for:
grep -r "src=\"http://" .
grep -r "href=\"http://" .
grep -r "url(http://" .
grep -r "url('http://" .
grep -r "fetch('http://" .
grep -r "fetch(\"http://" .
Step 2: Fix Mixed Content in Code
Update to HTTPS URLs
// Before (mixed content)
<img src="http://example.com/image.jpg">
<script src="http://cdn.example.com/script.js"></script>
<link href="http://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
// After (secure)
<img src="https://example.com/image.jpg">
<script src="https://cdn.example.com/script.js"></script>
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
Use protocol-relative URLs (optional)
// Protocol-relative URLs inherit the page's protocol
// Works for both HTTP and HTTPS
<img src="//example.com/image.jpg">
// Note: Most modern sites are HTTPS-only, so explicit https:// is preferred
// Protocol-relative URLs are useful during HTTPS migration
Fix CSS and JavaScript
/* CSS - Before */
.hero {
background: url(http://example.com/bg.jpg);
}
@import url('http://fonts.googleapis.com/css?family=Roboto');
/* CSS - After */
.hero {
background: url(https://example.com/bg.jpg);
}
@import url('https://fonts.googleapis.com/css?family=Roboto');
// JavaScript - Before
fetch('http://api.example.com/data')
const img = new Image();
img.src = 'http://example.com/image.png';
// JavaScript - After
fetch('https://api.example.com/data')
const img = new Image();
img.src = 'https://example.com/image.png';
Step 3: Fix Content in Database/CMS
WordPress
# Use Better Search Replace plugin or WP-CLI
wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' --dry-run
wp search-replace 'http://yourdomain.com' 'https://yourdomain.com'
# Update WordPress settings
wp option update siteurl 'https://yourdomain.com'
wp option update home 'https://yourdomain.com'
Direct Database Update (MySQL)
-- Preview changes first
SELECT post_content
FROM wp_posts
WHERE post_content LIKE '%http://yourdomain.com%';
-- Update posts
UPDATE wp_posts
SET post_content = REPLACE(post_content, 'http://yourdomain.com', 'https://yourdomain.com');
-- Update postmeta
UPDATE wp_postmeta
SET meta_value = REPLACE(meta_value, 'http://yourdomain.com', 'https://yourdomain.com');
-- Update options
UPDATE wp_options
SET option_value = REPLACE(option_value, 'http://yourdomain.com', 'https://yourdomain.com')
WHERE option_name = 'siteurl' OR option_name = 'home';
PostgreSQL (Supabase, etc.)
-- Update URLs in a content column
UPDATE posts
SET content = REPLACE(content, 'http://yourdomain.com', 'https://yourdomain.com')
WHERE content LIKE '%http://yourdomain.com%';
-- Update URLs in JSON columns
UPDATE settings
SET config = REPLACE(config::text, 'http://yourdomain.com', 'https://yourdomain.com')::jsonb
WHERE config::text LIKE '%http://yourdomain.com%';
Step 4: Add CSP Upgrade Header
Use Content-Security-Policy to automatically upgrade HTTP requests to HTTPS:
Vercel (vercel.json)
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "upgrade-insecure-requests"
}
]
}
]
}
Netlify (_headers)
/*
Content-Security-Policy: upgrade-insecure-requests
Nginx
add_header Content-Security-Policy "upgrade-insecure-requests" always;
Apache (.htaccess)
Header always set Content-Security-Policy "upgrade-insecure-requests"
Next.js (next.config.js)
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: 'upgrade-insecure-requests',
},
],
},
];
},
};
CSP Warning:
upgrade-insecure-requests is a safety net, not a permanent fix. It upgrades HTTP to HTTPS, but if the resource doesn't support HTTPS, it will fail to load. Always fix the actual URLs in your code.
Step 5: Handle Third-Party Resources
Check if HTTPS is available
# Test if a resource supports HTTPS
curl -I https://example.com/resource.js
# If it works, just update the URL
# If it fails, you need an alternative
Self-host resources that don't support HTTPS
# Download the resource
curl -o public/scripts/third-party.js http://insecure-cdn.com/script.js
# Reference the local copy
<script src="/scripts/third-party.js"></script>
# Note: Check licensing before self-hosting
# Set up a process to check for updates
Find HTTPS alternatives
Many old HTTP resources have HTTPS alternatives:
- Google Fonts: fonts.googleapis.com supports HTTPS
- jQuery CDN: code.jquery.com supports HTTPS
- Bootstrap: cdn.jsdelivr.net supports HTTPS
- Font Awesome: cdnjs.cloudflare.com supports HTTPS
Security Checklist
Mixed Content Security Checklist
- All images load over HTTPS
- All scripts load over HTTPS
- All stylesheets load over HTTPS
- All fonts load over HTTPS
- All iframes load over HTTPS
- All API calls use HTTPS
- All WebSocket connections use WSS (not WS)
- Database content URLs updated
- CMS settings updated to HTTPS
- upgrade-insecure-requests header added
- All pages tested (not just homepage)
- Forms post to HTTPS endpoints
How to Verify It Worked
Check browser security indicator
Look for the padlock icon in the address bar. Click it to verify "Connection is secure" with no warnings.
Verify in DevTools Console
# Open DevTools (F12) > Console
# Look for absence of mixed content warnings
# Should see no yellow or red warnings about HTTP resources
Test multiple pages
# Don't just test the homepage - check:
# - Blog posts (may have old embedded content)
# - Product pages
# - User-generated content pages
# - Admin/dashboard pages
# - Checkout/payment pages (critical!)
Automated scanning
# Use a crawler to check all pages
# Lighthouse audit includes mixed content checks
lighthouse https://yourdomain.com --only-categories=best-practices
# Or use command line:
npx mixed-content-scanner https://yourdomain.com
Common Errors and Troubleshooting
Still seeing mixed content after fixes
# Clear browser cache (Cmd+Shift+R or Ctrl+Shift+R)
# Clear CDN cache if using one
# Check for dynamically loaded content:
# - Content loaded via JavaScript after page load
# - Lazy-loaded images
# - Content from APIs
# Check all pages, not just the one you're testing
Resource fails to load over HTTPS
# The server might not support HTTPS
curl -I https://problematic-resource.com/file.js
# Solutions:
# 1. Self-host the resource
# 2. Find an HTTPS alternative
# 3. Remove the resource if not essential
# 4. Use a proxy that serves over HTTPS (not recommended for security reasons)
CSP upgrade not working for some resources
# upgrade-insecure-requests doesn't work for:
# - Resources that don't exist on HTTPS
# - WebSocket connections (ws:// won't auto-upgrade to wss://)
# - Some embedded content
# Check which resources are failing:
# DevTools > Network > Filter by "mixed-content"
WordPress keeps reverting to HTTP
# Check these common causes:
# 1. Hardcoded URLs in theme files
grep -r "http://" wp-content/themes/your-theme/
# 2. Plugin settings storing HTTP URLs
# Check plugin options in wp_options table
# 3. wp-config.php settings
# Add these lines:
define('FORCE_SSL_ADMIN', true);
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
# 4. .htaccess redirect conflict
# Ensure redirect comes before WordPress rules
Mixed content on specific browsers only
# Some older browsers handle mixed content differently
# Chrome and Firefox are strictest
# Check if resource URL varies by browser:
# - Some analytics/tracking scripts serve different content
# - Check for browser-specific assets
What is mixed content?
Mixed content occurs when an HTTPS page loads resources (images, scripts, stylesheets) over HTTP. This breaks the security of your HTTPS page because those resources can be intercepted or modified by attackers on the network.
Will upgrade-insecure-requests fix all mixed content?
It will attempt to upgrade HTTP requests to HTTPS, but if the resource doesn't support HTTPS, it will fail to load entirely. Use it as a safety net while fixing the actual URLs. Always update source URLs for a permanent fix.
::faq-item{question="Why does my site show "Not Secure" with a valid certificate?"} Mixed content. Even with a valid SSL certificate, loading any resource over HTTP triggers browser warnings. Open DevTools Console (F12) to see specific mixed content errors and which resources are causing the problem. ::
Are protocol-relative URLs still recommended?
They're acceptable but less preferred now. Since most sites are HTTPS-only, explicit https:// URLs are clearer. Protocol-relative URLs were more useful during HTTP-to-HTTPS migrations when you needed to support both protocols.
How do I fix mixed content in user-generated content?
Run database updates to convert HTTP URLs to HTTPS. For ongoing content, sanitize and transform URLs on input or output. Consider using a content filter that automatically upgrades URLs when rendering pages.
Run a free security scan to find all mixed content issues on your site.
Start Free Scan