Skip to main content
All fixes

SSL, TLS, security headers

Fix mixed content warnings on HTTPS pages

Your HTTPS page loads scripts, images, or iframes over HTTP, triggering 'Mixed Content' console errors. Browsers block active content and downgrade the lock icon.

What's happening

Mixed content occurs when an HTTPS page references subresources — scripts, stylesheets, iframes, images, fonts, fetch/XHR — over plain HTTP. The browser distinguishes 'active' mixed content (scripts, stylesheets, iframes, fetch) from 'passive' (images, audio, video). Active mixed content has been blocked outright by Chrome, Firefox, and Safari since 2020. Passive mixed content is auto-upgraded to HTTPS or blocked.

Users see a broken site or missing functionality; developers see 'Mixed Content: The page at https://example.com was loaded over HTTPS, but requested an insecure resource at http://...' in the console. The lock icon downgrades to a 'Not Secure' warning. The TLS handshake to the main page is fine; the issue is the application-layer references.

The fix is to update every subresource URL to HTTPS, or to use protocol-relative URLs and let the browser pick. The longer-term fix is to add a Content-Security-Policy that automatically upgrades insecure requests so a regression cannot ship.

Why it matters

Functionality breaks silently. A blocked analytics script means lost tracking; a blocked checkout iframe means lost revenue. The user sees 'something is wrong' without an error message.

Search engines penalize pages that lose the 'secure' badge. Google has used HTTPS as a ranking signal since 2014, and degraded HTTPS counts as worse than plain HTTP for ranking purposes.

PCI-DSS treats mixed content on a payment page as a control failure because the integrity of the page cannot be guaranteed. Scanners flag it, audits fail.

Common causes

  • Hardcoded http:// URLs in CMS content from before the HTTPS migration.
  • Third-party widgets (chat, analytics, social embeds) that ship HTTP-only fallback URLs.
  • Image src attributes pasted from upstream HTTP CDNs.
  • A reverse proxy strips the X-Forwarded-Proto header so the application generates absolute URLs with http://.
  • Old database rows with absolute URLs that survived the migration script.

Detect this on your site

Run a quick scan with the Full Site Audit. The tool surfaces this exact issue with the records and context needed to apply the fix below.

Open Full Site Audit

How to fix it

  1. 1

    Find every mixed-content reference

    Open the page in Chrome DevTools, go to the Console, and filter for 'Mixed Content'. The Network panel with the 'Blocked' filter shows every blocked request. The CheckFast Full Site Audit at /check crawls the whole site and lists every offender by URL.

  2. 2

    Update absolute HTTP URLs in templates and CMS

    Search the codebase for http:// references — git grep "http://" is a fast first pass. In WordPress, run wp search-replace 'http://example.com' 'https://example.com' --all-tables. In Drupal, use drush sql-query "UPDATE {node_field_data} SET ...". Audit any database that stores rendered URLs.

  3. 3

    Use HTTPS or protocol-relative URLs for third parties

    Every reputable third-party widget publishes an HTTPS URL — use it. Avoid the protocol-relative //cdn.example.com form on email templates (Gmail rewrites them) but it is safe in HTML. If a third party only offers HTTP, replace the vendor.

  4. 4

    Add upgrade-insecure-requests to CSP

    Set Content-Security-Policy: upgrade-insecure-requests (or include the directive in your existing CSP). The browser will automatically upgrade matching subresource requests from HTTP to HTTPS, masking residual references while you clean them up.

  5. 5

    Trust X-Forwarded-Proto in the application

    If your app generates absolute URLs, make sure it reads X-Forwarded-Proto from the reverse proxy. In Express set app.set('trust proxy', 1). In Rails set config.action_dispatch.tld_length and use config.force_ssl. In Next.js, when behind a proxy that sets the header, server-side requests will see https in headers().get('x-forwarded-proto').

  6. 6

    Re-crawl and confirm zero issues

    Re-run the Full Site Audit at /check after deploying. Mixed content findings should drop to zero. Add the audit to your CI pipeline so a regression in a new template fails the build.

Example

Content-Security-Policy: upgrade-insecure-requests; default-src 'self' https:; img-src 'self' https: data:;

CSP header that auto-upgrades insecure subresources

Frequently asked

It fixes same-origin and cross-origin subresources where the upgraded URL actually exists. If a third-party CDN does not serve HTTPS at the same path the request will fail rather than load over HTTP. Always verify after enabling.

Not really. They were a bridge during the HTTPS migration era. Today ship explicit https:// URLs — they are clearer in code review and avoid edge cases in email and view-source contexts.

Related fixes