Debugging & Troubleshooting

Debugging Mixed Content Errors in HTTPS Pages

How browsers block HTTP resources on HTTPS pages, detecting mixed content with DevTools and CSP reports, and systematic migration strategies.

What Is Mixed Content?

A page is served over HTTPS, but it loads at least one resource — a script, stylesheet, image, or iframe — over plain HTTP. This is mixed content, and modern browsers either block the resource or display a security warning.

The threat is real: an attacker with network access (coffee shop Wi-Fi, ISP) can intercept HTTP sub-resources and replace JavaScript files with malicious code, even though the main page is encrypted.

Active vs Passive Mixed Content

Browsers treat different resource types differently:

TypeExamplesBrowser Action
**Active**`<script>`, `<link rel=stylesheet>`, `<iframe>`, XHR/fetch**Blocked** — silently fails to load
**Passive**`<img>`, `<video>`, `<audio>`Loaded but **warning** in console

Since Chrome 84 (2020) and Firefox 83, passive mixed content is also auto-upgraded to HTTPS. If the HTTPS version doesn't exist, the resource fails to load. This means the old distinction matters less — all HTTP sub-resources cause problems.

Detecting Mixed Content

Chrome DevTools Console

The easiest starting point. Open DevTools (F12) → Console tab. Mixed content errors appear in red:

Mixed Content: The page at 'https://example.com/page' was loaded over HTTPS, but requested an insecure resource 'http://cdn.example.com/scripts/analytics.js'. This request has been blocked; the content must be served over HTTPS.

The error message gives you the exact URL. Go fix that URL.

DevTools → Network tab → filter by "mixed-content" to see all blocked requests without scanning the Console.

Content-Security-Policy-Report-Only

For large sites where you cannot manually visit every page, deploy a CSP in report-only mode to collect mixed content violations automatically:

Content-Security-Policy-Report-Only: default-src https:; report-uri /csp-violations

This header does not block anything — it only reports. Every mixed content violation sends a POST request to /csp-violations with a JSON body describing the blocked URL and the page it came from. Aggregate these reports over a week to build a complete list of HTTP URLs that need fixing.

Lighthouse Audit

Chrome's built-in Lighthouse tool (DevTools → Lighthouse) includes a "Does not use HTTPS" audit that flags mixed content. Run it on key pages to get a prioritized list.

Common Sources of Mixed Content

Hardcoded HTTP URLs in Code

<!-- Bug: hardcoded http:// -->
<script src="http://cdn.example.com/jquery.min.js"></script>
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto">

<!-- Fix: use https:// -->
<script src="https://cdn.example.com/jquery.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">

Search your codebase for hardcoded http:// in templates and static files:

grep -r 'http://' templates/ static/ --include='*.html' --include='*.css' --include='*.js'

Third-Party Embeds and Widgets

Older embed codes from analytics, social sharing, chat widgets, and payment processors sometimes use HTTP URLs. Check embed codes from:

  • YouTube/Vimeo embed iframes
  • Social share buttons
  • Live chat widgets (Intercom, Zendesk)
  • Payment processor scripts (Stripe, PayPal)

Always use the HTTPS version of embed codes. Most providers have updated their documentation — older embed codes on legacy pages may be the culprit.

Legacy Database Content

User-generated content, CMS body text, and imported data often contain HTTP URLs inserted years before HTTPS migration. These are invisible to static analysis.

-- Find HTTP URLs in PostgreSQL text columns
SELECT id, url FROM articles WHERE body LIKE '%http://%';
SELECT COUNT(*) FROM product_descriptions WHERE content ~ 'http://[^s]';

Fixing Mixed Content

The upgrade-insecure-requests CSP Directive

A quick stopgap for passive content and hardcoded URLs:

Content-Security-Policy: upgrade-insecure-requests

This directive tells the browser to automatically rewrite HTTP URLs in requests to HTTPS before fetching them. It works for images, stylesheets, and scripts. If the HTTPS version exists, the resource loads. If not, it fails.

upgrade-insecure-requests is a migration aid, not a permanent fix. The underlying URLs should still be corrected in your source.

Database URL Rewriting

For CMS or user content with embedded HTTP URLs, run a database migration:

# Django management command to rewrite HTTP URLs in a text field
from django.core.management.base import BaseCommand
from apps.blog.models import Article
import re

class Command(BaseCommand):
    def handle(self, *args, **options):
        articles = Article.objects.filter(body__contains='http://')
        count = 0
        for article in articles:
            # Replace http:// with https:// only for known-safe domains
            new_body = re.sub(
                r'http://(cdn\.example\.com|images\.example\.com)',
                r'https://\1',
                article.body
            )
            if new_body != article.body:
                article.body = new_body
                article.save(update_fields=['body'])
                count += 1
        self.stdout.write(f'Updated {count} articles')

Be conservative: only rewrite URLs for domains you control and know support HTTPS.

CSP Enforcement Rollout Strategy

Move from report-only to enforcement gradually:

  • Deploy Content-Security-Policy-Report-Only: default-src https:
  • Collect reports for 1-2 weeks to find all violations
  • Fix each violation (update URLs, rewrite database content)
  • Switch to Content-Security-Policy: default-src https: (enforcing)
  • Monitor error rates — new violations will now cause visible breakage

This staged approach ensures you don't break functionality by enforcing HTTPS before all resources are migrated.

Related Protocols

More in Debugging & Troubleshooting