Mixed Content: the page was loaded over HTTPS, but requested an insecure resource

Your site moved to HTTPS and now a script, API call or stylesheet silently fails, the padlock shows a warning, and the console is full of Mixed Content errors. Something on the page still points at plain <code>http://</code>.

What the browser does

Request over http from an https pageWhat happens
<script>, stylesheets, <iframe>, fontsBlocked (“active” mixed content)
fetch(), XMLHttpRequest, WebSocket ws://Blocked; your code sees a network error
<img>, <audio>, <video>Upgraded to https automatically in current Chrome, Firefox and Safari; broken if the server has no https
Form posting to http://Warning before submitting, or blocked
Links (<a href="http://…">)Allowed: navigating away isn't mixed content

The reason: anything loaded over http can be read or changed by whoever sits on the network. A tampered script would control your whole “secure” page.

Find every insecure URL

Fixes

  1. Use https URLs. Change http:// to https://, or use a relative path (/js/app.js) for your own files. Avoid protocol-relative //cdn… URLs; just use https.
  2. Make sure the other side has HTTPS. If an API only speaks http, call it from your server instead of the browser, or put it behind HTTPS.
  3. Behind a proxy or tunnel, trust the forwarded scheme: Express app.set('trust proxy', 1), Django SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'), Laravel TrustProxies, Rails config.force_ssl. Otherwise redirects and asset links come out as http.
  4. WordPress: set both WordPress Address and Site Address to https, then search-replace http://yourdomain in the database (WP-CLI wp search-replace).
  5. Safety net: the header Content-Security-Policy: upgrade-insecure-requests (check a whole policy with the CSP header explainer) tells browsers to rewrite your page's http subresource URLs to https before requesting them. It doesn't help if the target has no https.
# nginx: tell the browser to upgrade, and redirect plain http
add_header Content-Security-Policy "upgrade-insecure-requests" always;
server { listen 80; return 301 https://$host$request_uri; }

Localhost and development

http://localhost and http://127.0.0.1 count as potentially trustworthy, so an http page on localhost can load them. But an https page (say your deployed site) calling http://localhost:3000 is still blocked, and Chrome adds extra checks for public sites reaching private networks. Run your dev API over https, or proxy it through the same origin.

Related

TypeError: Failed to fetch · Expired or invalid SSL certificate · CORS errors explained · 301 vs 302 vs 307 vs 308 · Cookie not being set (Secure cookies on http) · Wirebook's lessons on how the web works · ERR_SSL_PROTOCOL_ERROR · X-Frame-Options explainer · WebSocket connection failed · NotAllowedError getUserMedia

Questions

What is mixed content?

An HTTPS page loading a script, style, image, iframe or API call over plain HTTP. The insecure part could be read or altered on the network, so browsers block or upgrade it.

Why is my fetch blocked as mixed content?

fetch and XMLHttpRequest to an http:// URL from an https page are always blocked. Use the https URL of the API, or call it from your server.

Does upgrade-insecure-requests fix mixed content?

It rewrites your page's http subresource URLs to https before loading them, which fixes it as long as those servers support HTTPS.

Are images blocked as mixed content?

Modern browsers auto-upgrade http images, audio and video to https. If the server has no HTTPS, the image fails to load.

Why do I get mixed content behind Cloudflare or a load balancer?

TLS ends at the proxy, so your app sees plain http and builds http URLs. Configure it to trust X-Forwarded-Proto.

Published .

Try it

Related guides

Newest guides