Mixed content: why an http resource breaks an https page

An https page that loads a script, stylesheet or image over plain http has mixed content. The browser blocks the script and the stylesheet outright and tries to upgrade the image, and nothing on screen says so. The fix is https on every address, or embedding the resource.

Mixed content is what a browser calls an http resource inside an https page: an image, a font, a stylesheet or a script fetched over the unencrypted scheme by a page that was itself delivered securely.

The markup. The highlighted line is the part this term is about.
The markup. The highlighted line is the part this term is about.

Browsers refuse the script and the stylesheet without asking, upgrade the image to https and block it if that fails, and show nothing on screen but a gap and a console line.

It is one of the five reasons images do not show and a common reason a chart library never arrives. This guide covers why the rule exists, what is blocked and what is upgraded, and the fix.

<!-- on a page served over https -->
<script src="http://example.com/chart.js"></script>   <!-- blocked -->
<link rel="stylesheet" href="http://example.com/a.css"> <!-- blocked -->
<img src="http://example.com/logo.png">                <!-- upgraded, or blocked -->

The page is secure; these parts are not. Browsers block the ones that could change the page.

Why blocking mixed content is not pedantry

An http request can be modified in transit by anything on the network path. If that request is for a script, whoever modified it now controls your page — the padlock remains, the address is right, and the page is running somebody else's code.

Mixed content is an http resource inside an https page. Scripts and stylesheets over http are blocked outright; images are upgraded or blocked. The page shows a gap with no error, which is why it is easy to miss.
Mixed content is an http resource inside an https page. Scripts and stylesheets over http are blocked outright; images are upgraded or blocked. The page shows a gap with no error, which is why it is easy to miss.

So a secure page loading an insecure script is not slightly less secure. It is as insecure as the script.

A page split across files ✗ Works only inside its own folder ✗ Styling vanishes when sent alone ✗ Images turn into empty boxes ✗ Breaks the moment a file is renamed One self-contained file ✓ Renders anywhere it lands ✓ Styling travels with it ✓ Images are carried inside ✓ Nothing to keep together
A page whose resources are inside it or at https addresses loads whole. One reaching over http loads with holes.

What is blocked, what is upgraded

Resource Behaviour
Script Blocked
Stylesheet Blocked
Font Blocked
fetch / XHR Blocked
Frame content Blocked
Image Upgraded to https; blocked if that fails
Audio / video Upgraded; blocked if that fails

Anything that can alter the page is refused. Passive content — images, media — is attempted over https first, which usually works because most hosts serve both.

Why the symptom misleads

Blocked resources fail quietly unless the console is open. So:

  • A blocked script means "the page renders but nothing works" — which sends people looking at their JavaScript.
  • A blocked stylesheet means "the page has no styling" — which sends them looking for a missing file.
  • A blocked font means "the typeface fell back" — which sends them checking font names.

In each case the cause is four characters in an address. Open the console first; the message says exactly what was blocked.

Finding every instance

grep -n 'http://' index.html

What to look for: http:// immediately after a quote mark. http:// inside body text, in a comment, or in a JSON-LD @context value is harmless — those are identifiers, not requests.

The usual hiding places:

/* in CSS */
background-image: url("http://example.com/bg.png");
@import url("http://example.com/base.css");
@font-face { src: url("http://example.com/f.woff2"); }

CSS is where these survive longest, because nobody greps stylesheets.

Fixing it

Preferred: use https. Nearly every host serves it now. Change four characters.

Protocol-relative addresses — //example.com/x.js — inherit the page's scheme. They work, and they break when the file is opened locally, because // then resolves to file://. Avoid them; write https explicitly.

Host it yourself if the remote host genuinely has no https. A host without https in the current decade is not a host to depend on anyway.

A stopgap while migrating:

Content-Security-Policy: upgrade-insecure-requests

The browser rewrites http requests to https automatically. It papers over the references rather than fixing them, and it fails for any host that genuinely has no https. Useful during a migration, not as an end state.

In a single-file page

The same rule applies, plus one extra: a page opened from a disk over file:// may behave differently from the same page served over https, so a mixed-content problem can appear only after publishing.

The safest answer for a page that has to travel is to have no outside requests at all — see self-contained HTML. Embedded images and a system font stack cannot be mixed content, because there is nothing to request.

Why images are treated more gently than scripts

An image over http can be tampered with on the way, but the damage is a wrong picture. A script over http can be replaced with anything, and it runs with the page's full permissions, so the browser blocks it without asking.

Stylesheets can carry enough to rewrite the page, so they are blocked too. Images, audio and video are "optionally blockable": browsers first try the same address over https, and if that fails, block it and demote the padlock.

The distinction is why a page can look almost right with mixed content: the words and the pictures are there and the chart is missing.

Assistants and older templates still write http:// addresses for libraries and fonts from habit, and pages copied from an old site carry them along. A page that was fine on a local http server breaks the moment it is published at https. Search the file once for http:// before it goes anywhere.

Fixing mixed content: 4 steps

  1. Open the console on the https page. Each blocked resource is listed with its http:// address.
  2. Change http:// to https:// on each one. Most hosts serve both; the address usually just works.
  3. If the host has no https, move or embed the resource. Copy the image somewhere that serves https, or embed it as a data URI.
  4. Search the file for http:// before publishing. One search catches all of them before a reader sees a gap, and a self-contained file has none to find.

Questions people ask

What is mixed content?

Resources loaded over http by a page served over https. The secure page is loading insecure parts, and browsers block the ones that could alter the page.

Is everything blocked?

Scripts, stylesheets, fonts and fetches are blocked outright. Images and media are often upgraded to https automatically and blocked if that fails.

How do I find every instance?

Search your source for http:// — the quote mark after it matters, since http:// inside ordinary text is harmless.

What is upgrade-insecure-requests?

A policy telling the browser to rewrite http requests to https automatically. Useful while migrating, not a substitute for fixing the references.

Keep reading