Mixed content: move every subresource to https and keep the padlock
Switching http subresources to https, with an upgrade-insecure-requests backstop, restores the padlock and stops browsers blocking half your page
The threat
Your page is served over https, but somewhere in it a script, stylesheet, font, image, or iframe is still requested over plain http://. Browsers call this mixed content, and they do not treat it kindly. Active resources (scripts, stylesheets, iframes, fetch) are blocked outright, so part of your page silently stops working. Passive ones (images, media) load but downgrade the address bar: the padlock breaks and visitors see “Not secure” on a page you thought was locked down.
It is not only cosmetic. An http:// subresource travels unencrypted, so anyone on the network path can read it or swap it out. Swapping an image is a nuisance; swapping a script is a takeover, because that script runs with full access to your otherwise secure page. Fixing mixed content is about getting the whole page, not just the main document, onto https.
The exact fix
Step 1, find the http:// references. Load the page with DevTools open: the Console logs each one, for example Mixed Content: ... requested an insecure resource. Or grep your templates and source:
grep -rn "http://" src/
Look for src="http://, href="http://, url(http:// in CSS, and inline fetch('http://...').
Step 2, upgrade each one to https://. Almost every CDN and asset host serves the same file over TLS today.
<!-- before -->
<script src="http://cdn.example.com/app.js"></script>
<img src="http://images.yourdomain.com/logo.png">
<!-- after -->
<script src="https://cdn.example.com/app.js"></script>
<img src="https://images.yourdomain.com/logo.png">
Step 3, add a backstop header so anything you missed (or that creeps in later from third-party or user content) is auto-upgraded. On Netlify:
# netlify.toml
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "upgrade-insecure-requests"
Or the _headers form:
/*
Content-Security-Policy: upgrade-insecure-requests
If you cannot set response headers, a meta tag in <head> does the same job:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
The header is a backstop, not a magic wand.
upgrade-insecure-requestsrewriteshttp://tohttps://for requests to hosts that actually answer over TLS. If a subresource host has no https at all, the upgraded request just fails, so fix the URL or move the asset, do not lean on the header to conjure TLS that is not there. And if you already ship a full CSPCSPHTTP headerContent-Security-Policy: a response header that whitelists exactly which scripts, styles, images and connections a page may use, so an injected payload simply has no permission to run. (see the CSP page), append this directive to that policy rather than sending a secondContent-Security-PolicyCSPHTTP headerContent-Security-Policy: a response header that whitelists exactly which scripts, styles, images and connections a page may use, so an injected payload simply has no permission to run. header.
Verify it
curl -sI https://yourdomain.com | grep -i content-security
Confirm upgrade-insecure-requests is present in the policy. Then load the page with DevTools open: zero “Mixed Content” messages in the Console and a solid padlock in the address bar means every subresource is now coming over https.
Proof
The ApeCyber scanner loads your page the way a browser does, from the outside, touching nothing, and flags any http:// subresource pulled onto an https page. Shipping hardened headers on Netlify is exactly how apecyber.com holds A / 100, and dev3lop.com made the same run from a failing grade to A / 100. Ten minutes of cleanup, a clean −10 off the posture score, and the padlock stops flickering.