Complete Guide to Fixing Mixed Content on HTTPS Pages
If you've ever seen a broken image or non-working script on your HTTPS site, you've probably encountered mixed content issues. These problems are frustrating for users and create security vulnerabilities that can compromise your entire site.
You'll learn how to identify and fix mixed content issues quickly, so your HTTPS site works properly and stays secure.
Understanding Mixed Content
What is Mixed Content?
Mixed content occurs when a web page served over HTTPS (secure) contains resources (images, scripts, stylesheets, iframes, etc.) that are loaded over HTTP (insecure). This creates a security vulnerability because:
- Security Risk: HTTP resources can be modified in transit by attackers
- User Trust: The browser shows security warnings to users
- Functionality Issues: Browsers block or degrade mixed content, breaking your site
Types of Mixed Content
Active Mixed Content (Blocked by Browsers)
- Scripts: JavaScript files loaded over HTTP
- Stylesheets: CSS files loaded over HTTP
- Iframes: Embedded content loaded over HTTP
- WebSockets: WebSocket connections over HTTP
- XMLHttpRequests: AJAX requests over HTTP
Passive Mixed Content (Warnings Only)
- Images: Images loaded over HTTP
- Audio/Video: Media files loaded over HTTP
- Fonts: Web fonts loaded over HTTP
Browser Behavior
Modern browsers handle mixed content differently:
- Chrome/Edge: Blocks active mixed content, shows warnings for passive
- Firefox: Blocks active mixed content, shows warnings for passive
- Safari: Blocks active mixed content, shows warnings for passive
- Mobile Browsers: Generally more restrictive
Identifying Mixed Content Issues
1. Browser Developer Tools Analysis
Step-by-Step Detection:
- Open Developer Tools (F12 or right-click → Inspect)
- Navigate to Console Tab - Look for mixed content warnings
- Check Network Tab - Look for blocked or failed requests
- Reload the page and monitor for mixed content errors
Common Console Messages:
Mixed Content: The page at 'https://example.com' was loaded over HTTPS, but requested an insecure resource 'http://example.com/image.jpg'. This request has been blocked.
2. Automated Detection Tools
Online Mixed Content Scanners
# Using curl to check for HTTP resources
curl -s https://example.com | grep -i "http://"
# Using wget to check for mixed content
wget --spider --recursive --no-parent https://example.com 2>&1 | grep -i "http://"
Browser Extensions
- Mixed Content Scanner (Chrome Extension)
- HTTPS Everywhere (Firefox/Chrome Extension)
- SSL Labs SSL Test (Online Tool)
3. Code Analysis
Search for HTTP References:
# Search HTML files
grep -r "http://" src/ templates/ public/
# Search CSS files
grep -r "url(http://" src/ styles/ public/
# Search JavaScript files
grep -r "http://" src/ js/ public/
Common Sources of Mixed Content
1. Hardcoded HTTP URLs
HTML Templates:
<!-- VULNERABLE -->
<img src="http://cdn.example.com/image.jpg" alt="Image">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto">
<!-- SECURE -->
<img src="https://cdn.example.com/image.jpg" alt="Image">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
CSS Files:
/* VULNERABLE */
background-image: url('http://cdn.example.com/bg.jpg');
@import url('http://fonts.googleapis.com/css?family=Roboto');
/* SECURE */
background-image: url('https://cdn.example.com/bg.jpg');
@import url('https://fonts.googleapis.com/css?family=Roboto');
JavaScript Files:
// VULNERABLE
const imageUrl = 'http://cdn.example.com/image.jpg';
fetch('http://api.example.com/data');
// SECURE
const imageUrl = 'https://cdn.example.com/image.jpg';
fetch('https://api.example.com/data');
2. Third-Party Integrations
Common Third-Party Services:
- Google Analytics
- Google Fonts
- jQuery CDN
- Bootstrap CDN
- Social media embeds
- Payment processors
- Analytics tools
3. Content Management Systems (CMS)
WordPress:
// Check for HTTP URLs in database
SELECT * FROM wp_posts WHERE post_content LIKE '%http://%';
// Check for HTTP URLs in options
SELECT * FROM wp_options WHERE option_value LIKE '%http://%';
Drupal:
-- Check for HTTP URLs in content
SELECT * FROM node__body WHERE body_value LIKE '%http://%';
4. Dynamic Content
User-Generated Content:
- User-uploaded images
- User-created content with HTTP links
- Comments with HTTP references
- Forum posts with HTTP links
Comprehensive Fix Strategies
1. Protocol-Relative URLs (Legacy Approach)
What they are:
<!-- Protocol-relative URL -->
<img src="//cdn.example.com/image.jpg" alt="Image">
Why to avoid:
- Browsers are deprecating support
- Can cause issues in some contexts
- Less explicit than HTTPS
Better approach:
<!-- Explicit HTTPS -->
<img src="https://cdn.example.com/image.jpg" alt="Image">
2. Content Security Policy (CSP) Upgrade
CSP Header for Mixed Content:
Content-Security-Policy: upgrade-insecure-requests
Implementation Examples:
Nginx:
add_header Content-Security-Policy "upgrade-insecure-requests" always;
Apache:
Header always set Content-Security-Policy "upgrade-insecure-requests"
Express.js:
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests');
next();
});
Next.js:
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: 'upgrade-insecure-requests'
}
]
}
];
}
};
3. Server-Side URL Rewriting
Nginx Configuration:
# Rewrite HTTP to HTTPS for specific domains
location ~* \.(jpg|jpeg|png|gif|css|js|woff|woff2|ttf|eot)$ {
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
# Rewrite HTTP to HTTPS for external resources
location / {
sub_filter 'http://cdn.example.com' 'https://cdn.example.com';
sub_filter_once off;
}
Apache Configuration:
# Rewrite HTTP to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Rewrite HTTP resources to HTTPS
RewriteRule ^(.*)$ - [E=HTTPS:on]
Header always set Content-Security-Policy "upgrade-insecure-requests"
4. CDN and Edge Solutions
Cloudflare:
// Cloudflare Workers
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const response = await fetch(request)
const newResponse = new Response(response.body, response)
// Add CSP header
newResponse.headers.set('Content-Security-Policy', 'upgrade-insecure-requests')
return newResponse
}
AWS CloudFront:
{
"Comment": "Mixed Content Fix",
"DefaultCacheBehavior": {
"TargetOriginId": "origin",
"ViewerProtocolPolicy": "redirect-to-https",
"ResponseHeadersPolicy": {
"SecurityHeadersConfig": {
"ContentSecurityPolicy": {
"ContentSecurityPolicy": "upgrade-insecure-requests"
}
}
}
}
}
5. Application-Level Fixes
JavaScript URL Rewriting:
// Function to convert HTTP URLs to HTTPS
function upgradeToHttps(url) {
if (url.startsWith('http://')) {
return url.replace('http://', 'https://');
}
return url;
}
// Apply to all images
document.querySelectorAll('img[src^="http://"]').forEach(img => {
img.src = upgradeToHttps(img.src);
});
// Apply to all stylesheets
document.querySelectorAll('link[href^="http://"]').forEach(link => {
link.href = upgradeToHttps(link.href);
});
// Apply to all scripts
document.querySelectorAll('script[src^="http://"]').forEach(script => {
script.src = upgradeToHttps(script.src);
});
CSS URL Rewriting:
/* Use CSS custom properties for dynamic URLs */
:root {
--cdn-url: 'https://cdn.example.com';
}
.image {
background-image: url(var(--cdn-url)/image.jpg);
}
6. Database and Content Fixes
WordPress Database Fix:
-- Update HTTP URLs to HTTPS in posts
UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://', 'https://');
-- Update HTTP URLs in post meta
UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'http://', 'https://');
-- Update HTTP URLs in options
UPDATE wp_options SET option_value = REPLACE(option_value, 'http://', 'https://');
Drupal Database Fix:
-- Update HTTP URLs in content
UPDATE node__body SET body_value = REPLACE(body_value, 'http://', 'https://');
-- Update HTTP URLs in fields
UPDATE node__field_image SET field_image_target_id = REPLACE(field_image_target_id, 'http://', 'https://');
Testing and Validation
1. Manual Testing Checklist
Browser Testing:
- Test in Chrome, Firefox, Safari, Edge
- Check mobile browsers (iOS Safari, Chrome Mobile)
- Verify no mixed content warnings in console
- Confirm all resources load correctly
- Test functionality (forms, scripts, styles)
Network Testing:
- Use browser DevTools Network tab
- Check for blocked requests
- Verify all requests use HTTPS
- Test with slow network conditions
2. Automated Testing
Playwright Test Example:
const { test, expect } = require('@playwright/test');
test('should not have mixed content', async ({ page }) => {
const mixedContentErrors = [];
page.on('console', msg => {
if (msg.type() === 'error' && msg.text().includes('Mixed Content')) {
mixedContentErrors.push(msg.text());
}
});
await page.goto('https://example.com');
// Wait for page to load
await page.waitForLoadState('networkidle');
// Check for mixed content errors
expect(mixedContentErrors).toHaveLength(0);
});
Cypress Test Example:
describe('Mixed Content Tests', () => {
it('should not have mixed content warnings', () => {
cy.visit('https://example.com');
// Check console for mixed content warnings
cy.window().then((win) => {
const consoleSpy = cy.spy(win.console, 'error');
cy.visit('https://example.com').then(() => {
cy.wrap(consoleSpy).should('not.have.been.calledWith',
Cypress.sinon.match(/Mixed Content/));
});
});
});
});
3. Online Testing Tools
SSL Labs SSL Test:
- URL: https://www.ssllabs.com/ssltest/
- Tests for mixed content issues
- Provides detailed security analysis
Security Headers Test:
- URL: https://securityheaders.com/
- Checks for CSP and other security headers
- Identifies mixed content issues
Mixed Content Scanner:
- Browser extension for Chrome/Firefox
- Scans pages for mixed content
- Provides detailed reports
Advanced Solutions
1. Progressive Enhancement
Graceful Degradation:
// Check if HTTPS is available, fallback to HTTP
function getResourceUrl(path) {
const httpsUrl = `https://cdn.example.com${path}`;
const httpUrl = `http://cdn.example.com${path}`;
// Try HTTPS first
return fetch(httpsUrl, { method: 'HEAD' })
.then(response => response.ok ? httpsUrl : httpUrl)
.catch(() => httpUrl);
}
2. Service Worker Implementation
Service Worker for URL Rewriting:
// service-worker.js
self.addEventListener('fetch', event => {
if (event.request.url.startsWith('http://')) {
const httpsUrl = event.request.url.replace('http://', 'https://');
event.respondWith(fetch(httpsUrl));
}
});
3. Build-Time URL Rewriting
Webpack Configuration:
// webpack.config.js
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.CDN_URL': JSON.stringify('https://cdn.example.com')
})
],
module: {
rules: [
{
test: /\.(css|scss)$/,
use: [
{
loader: 'string-replace-loader',
options: {
search: 'http://cdn.example.com',
replace: 'https://cdn.example.com',
flags: 'g'
}
}
]
}
]
}
};
Monitoring and Maintenance
1. Continuous Monitoring
Automated Scanning:
#!/bin/bash
# mixed-content-check.sh
URL="https://example.com"
TEMP_FILE="/tmp/mixed-content-check.html"
# Download page
curl -s "$URL" > "$TEMP_FILE"
# Check for HTTP URLs
HTTP_COUNT=$(grep -c "http://" "$TEMP_FILE")
if [ "$HTTP_COUNT" -gt 0 ]; then
echo "WARNING: Found $HTTP_COUNT HTTP URLs on $URL"
grep -n "http://" "$TEMP_FILE"
exit 1
else
echo "SUCCESS: No HTTP URLs found on $URL"
exit 0
fi
CI/CD Integration:
# .github/workflows/mixed-content-check.yml
name: Mixed Content Check
on: [push, pull_request]
jobs:
mixed-content-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check for mixed content
run: |
./scripts/mixed-content-check.sh
2. Regular Audits
Monthly Checklist:
- Scan all pages for mixed content
- Check third-party integrations
- Review user-generated content
- Test in multiple browsers
- Verify CSP headers
- Check CDN configurations
Common Pitfalls and Solutions
1. Third-Party Service Issues
Problem: Third-party services don't support HTTPS Solution:
- Contact the service provider
- Find HTTPS alternatives
- Use proxy servers
- Implement CSP upgrade-insecure-requests
2. Legacy Content
Problem: Old content with hardcoded HTTP URLs Solution:
- Database updates
- Content migration scripts
- Regular content audits
- User education
3. Development vs Production
Problem: Different configurations between environments Solution:
- Environment-specific configurations
- Automated testing
- Configuration management
- Documentation
Do a systematic cleanup
- Search the codebase for
http://and replace withhttps://where supported - Update config files and environment variables that contain hostnames
- Migrate third‑party scripts to documented
https://endpoints or official packages - Update CMS content and media URLs to
https://
Hidden offenders to check
- Grep CSS for
url(http://and fonts loaded over http - Scan sitemaps and top pages with a headless fetcher to capture console warnings
- Check tag managers, A/B testing, and analytics snippets for legacy URLs
- Avoid protocol‑relative URLs like
//example.com/...- instead explicitly usehttps://
Mitigation strategies that can help
These options reduce mixed content during cleanup, but they don’t solve every case. Use them as a bridge while you fix sources, and as a future failsafe. When the website is using proper HTTPS sources, keep your mitigation strategies, but stop relying on automatic upgrades. Instead, ensure that all assets are properly requested over the correct https:// URLs, instead of http://.
Content Security Policy
Content-Security-Policy: upgrade-insecure-requests
This tells the browser to try https:// for subresources that are accessed with http://. It helps during cleanup but will not fix sources that do not support HTTPS. For more information on how Content-Security-Policy (CSP) works, read our Content Security Policy guide.
CDN and edge rewrites
CDNs can upgrade insecure subresource URLs at the edge without a code change. Use this to reduce breakage during a transition, but scope rules to known hosts and verify that those origins actually serve HTTPS.
- Cloudflare: enable Automatic HTTPS Rewrites, optionally add Transform Rules to upgrade specific origins and block http‑only ones
- AWS CloudFront: use Functions or Lambda@Edge to rewrite
http://→https://for a whitelist of origins, log rewrites and skip when an origin lacks HTTPS - Fastly: use VCL or Compute@Edge to normalize schemes to
httpsfor known domains - Akamai: use Property Manager/EdgeWorkers to enforce
httpson allowed hostnames - Azure Front Door: add routing rules to enforce HTTPS and, when proxied, rewrite subresource URLs for known backends
Avoid blanket rewrites for unknown domains. Keep the scope narrow and auditable.
Server redirects
Force HTTPS with 301 redirects for HTTP requests at your web server and app layer. This fixes same‑origin mixed content where pages reference http://your-domain/.... It does not fix third‑party assets that don’t support HTTPS though.
Update canonical URLs, sitemaps, OG tags, and CMS templates to absolute https:// links. Verify cookies (use Secure) and CORS/asset URLs all work over HTTPS. When the site is fully migrated, consider enabling HSTS to prevent downgrades. See the HTTPS implementation guide for Nginx, Apache, IIS, Next.js, and CDN examples.
Adding an automated mixed content check to CI
Playwright (browser automation testing tool) example - fail the build on mixed content warnings
// scripts/mixed-content-check.ts
import { chromium } from 'playwright'
const urls = [
'https://your-site.example/',
'https://your-site.example/pricing',
'https://your-site.example/blog'
]
let hadMixed = false
;(async () => {
const browser = await chromium.launch()
const ctx = await browser.newContext()
const page = await ctx.newPage()
page.on('console', (msg) => {
if (msg.type() === 'warning' && /Mixed Content/i.test(msg.text())) {
hadMixed = true
console.warn(msg.text())
}
})
for (const url of urls) {
await page.goto(url, { waitUntil: 'networkidle' })
}
await browser.close()
if (hadMixed) {
console.error('Mixed content detected')
process.exit(1)
}
})()
Or run a quick search in CI to catch new http:// strings:
rg -n "http://" src/ content/ public/
Common pitfalls
- Protocol‑relative URLs (
//) pulling HTTP from legacy hosts - CSS files hiding HTTP font/image URLs
- Hardcoded links in marketing tools or CMS blocks
Release checklist
- DevTools shows no mixed content warnings on key pages
- Sitemap, canonical, and OG image links are https
- All third‑party embeds use documented
https://endpoints - Images, fonts, CSS, and JS load over https in the Network tab
- Some automated mitigation attempt strategy in place, such as the
upgrade-insecure-requestsCSP directive
Conclusion
Replace HTTP links with HTTPS in the source - code, configuration, and content. Use CSP auto upgrade and CDN rewrites as temporary aids, not permanent fixes. Keep it clean with automated checks.
Next steps
- Verify headers quickly with the Security Headers Test
- Scan your public pages for issues in the Barrion dashboard