Introduction

Mixed content occurs when an HTTPS page loads resources (scripts, stylesheets, images, iframes) over HTTP. Modern browsers block active mixed content (scripts, stylesheets) and warn about passive mixed content (images, videos):

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

Blocked resources cause broken styling, missing functionality, and console errors.

Symptoms

  • Browser console shows "Mixed Content" warnings and blocked resource errors
  • Stylesheets, scripts, or fonts fail to load on HTTPS pages
  • Page appears unstyled or partially broken
  • Images show a broken icon or do not display
  • Some browsers show a "Not Secure" warning in the address bar despite HTTPS

Common Causes

  • Hardcoded http:// URLs in HTML templates, CSS files, or JavaScript
  • Third-party resources (CDNs, analytics, fonts) referenced with HTTP protocol
  • Database content containing HTTP image URLs from before the site migrated to HTTPS
  • CSS @import or url() using HTTP protocol
  • Dynamically generated content (API responses, CMS content) containing HTTP URLs

Step-by-Step Fix

  1. 1.Replace HTTP URLs with protocol-relative or HTTPS URLs:
  2. 2.```html
  3. 3.<!-- BAD -->
  4. 4.<link rel="stylesheet" href="http://cdn.example.com/styles.css">
  5. 5.<script src="http://cdn.example.com/app.js"></script>

<!-- GOOD: Protocol-relative --> <link rel="stylesheet" href="//cdn.example.com/styles.css">

<!-- BETTER: Explicit HTTPS --> <link rel="stylesheet" href="https://cdn.example.com/styles.css"> <script src="https://cdn.example.com/app.js"></script> ```

  1. 1.Add Content-Security-Policy upgrade-insecure-requests to automatically upgrade HTTP to HTTPS:
  2. 2.```html
  3. 3.<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
  4. 4.`
  5. 5.Or as an HTTP header:
  6. 6.`
  7. 7.Content-Security-Policy: upgrade-insecure-requests
  8. 8.`
  9. 9.This tells the browser to automatically upgrade all HTTP subresource requests to HTTPS.
  10. 10.Fix database content with HTTP URLs. For MySQL/MariaDB:
  11. 11.```sql
  12. 12.UPDATE wp_posts
  13. 13.SET post_content = REPLACE(post_content, 'http://example.com', 'https://example.com')
  14. 14.WHERE post_content LIKE '%http://example.com%';
  15. 15.`
  16. 16.Fix CSS references to HTTP resources:
  17. 17.```css
  18. 18./* BAD */
  19. 19.@import url('http://fonts.googleapis.com/css?family=Roboto');
  20. 20..hero { background-image: url('http://cdn.example.com/hero.jpg'); }

/* GOOD */ @import url('https://fonts.googleapis.com/css?family=Roboto'); .hero { background-image: url('https://cdn.example.com/hero.jpg'); } ```

  1. 1.Use a Content-Security-Policy Report-Only header to identify remaining mixed content:
  2. 2.`
  3. 3.Content-Security-Policy-Report-Only: upgrade-insecure-requests; report-uri /csp-report
  4. 4.`
  5. 5.Monitor the /csp-report endpoint to find pages still serving mixed content.

Prevention

  • Enable HSTS (HTTP Strict Transport Security) to ensure all requests use HTTPS:
  • `
  • Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • `
  • Run automated mixed content scanning in your CI pipeline using tools like mixed-content-scan
  • Add CSP upgrade-insecure-requests as a default security header on all pages
  • Audit third-party dependencies quarterly to ensure they support HTTPS
  • Use relative URLs (/path/to/resource) for same-origin resources
  • Test all pages with browser console open after HTTPS migration to catch remaining HTTP references