The short answer

Change every http:// resource URL on your HTTPS pages to https://, or host the file yourself. As a safety net, add the upgrade-insecure-requests directive to your Content-Security-Policy so browsers request any leftovers over HTTPS.

What goes wrong

An HTTPS page that loads a script over plain HTTP Top: the HTTPS page loads widget.js from an http:// address. Someone on the network can swap the script for their own, which would then run with full access to the page, so modern browsers block the request and the widget stops working. Bottom: the script is loaded from an https:// address, arrives encrypted and unmodified, and runs normally. Script over http:// HTTPS page Padlock in the address bar Script tag src="http://…/widget.js" Someone on the path Can swap the script Browser blocks it Widget breaks A swapped script could read everything typed Blocked scripts break page features Script over https:// HTTPS page Padlock in the address bar Script tag src="https://…/widget.js" Encrypted download Cannot be changed Script runs Page works as designed The whole page is protected No console errors or broken widgets An HTTPS page that loads a script over plain HTTP Top: the HTTPS page loads widget.js from an http:// address. Someone on the network can swap the script for their own, which would then run with full access to the page, so modern browsers block the request and the widget stops working. Bottom: the script is loaded from an https:// address, arrives encrypted and unmodified, and runs normally. Script over http:// HTTPS page Padlock in the address bar Script tag src="http://…/widget.js" Someone on the path Can swap the script Browser blocks it Widget breaks A swapped script could read everything typed Blocked scripts break page features Script over https:// HTTPS page Padlock in the address bar Script tag src="https://…/widget.js" Encrypted download Cannot be changed Script runs Page works as designed The whole page is protected No console errors or broken widgets
A script runs with full access to the page that loads it, so one http:// script undoes the protection HTTPS gives the rest of the page. Browsers block it, which also breaks whatever it powers.

Browsers sort mixed content into two kinds. Active content, such as scripts, stylesheets, iframes and fetch requests, can change the page and is always blocked. Passive content, such as images, audio and video, is now upgraded to HTTPS automatically by current Chrome, Edge and Firefox, and blocked if the upgrade fails.

How DNS Panopticon detects it

Security Posture reads the HTML of your HTTPS homepage and counts <script> tags whose src starts with http://. The result appears on the Website Application Security card.

FindingSeverityWhen it appears
Scripts load over plain httpMediumAt least one script on the HTTPS homepage is loaded from an http:// address, and the page’s Content-Security-Policy does not include upgrade-insecure-requests.

The check covers scripts written into the homepage HTML. Scripts added later by other scripts, and stylesheets, frames and images, are not counted, so check the browser console as well.

How to find it yourself

Open the page, then the browser’s developer tools (F12) and the Console tab. Blocked requests show messages such as “Mixed Content: The page at ‘https://example.com/’ was loaded over HTTPS, but requested an insecure script ‘http://…’. This request has been blocked.”

CommandWhat it shows
curl -s https://example.com | grep -o 'src="http://[^"]*"'Every http:// src attribute in the page HTML (macOS, Linux, WSL).
(Invoke-WebRequest -Uri https://example.com -UseBasicParsing).Content | Select-String -Pattern 'src="http://[^"]*"' -AllMatches | ForEach-Object { $_.Matches.Value }The same search in Windows PowerShell.
curl -sI https://example.comWhether a content-security-policy header already includes upgrade-insecure-requests.

Why it matters

  • Full page takeover. A script can read form fields, change links, redirect the visitor or load more code. If an attacker can replace it, they effectively control the page, padlock and all.
  • Broken features. Because browsers block the request, whatever the script does, such as a chat widget, analytics, a payment form or a menu, silently stops working for every visitor.
  • Older clients. Some embedded web views and old browsers do not block mixed content, so their users get the insecure version.

How to fix it

  1. Switch the URL to https://. Almost every CDN and third-party service supports HTTPS at the same address. Open the https:// version of the URL in a browser to confirm.
  2. Host it yourself if the provider has no HTTPS version, or replace the service.
  3. Search templates and content, not just the page you noticed. In a CMS, old posts and settings often store http:// URLs. On WordPress, WP-CLI can rewrite them: wp search-replace 'http://example.com' 'https://example.com' --dry-run, then run it again without --dry-run.
  4. Add upgrade-insecure-requests as a safety net. It tells the browser to request every http:// resource on the page over HTTPS instead. It only helps when the resource is actually available over HTTPS.
  5. Pin third-party scripts with Subresource Integrity (an integrity attribute) so the browser refuses a file that has been changed.
PlatformAdding upgrade-insecure-requests
nginxadd_header Content-Security-Policy "upgrade-insecure-requests" always;
ApacheHeader always set Content-Security-Policy "upgrade-insecure-requests"
CloudflareAutomatic HTTPS Rewrites rewrites http:// links in HTML it can upgrade
HTML only<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> in the page head

If you already send a Content-Security-Policy, add the directive to it rather than sending a second header.

FAQ

Are images over http:// a problem too?

Less so. Current browsers upgrade images, audio and video to HTTPS automatically and block them if that fails, so the risk is a broken image rather than a hijacked page. They are still worth fixing.

Should I use protocol-relative URLs like //cdn.example.net/file.js?

No longer. They were a workaround from when sites served both HTTP and HTTPS. Now that the page should always be HTTPS, write https:// explicitly.

Does upgrade-insecure-requests fix mixed content on its own?

It hides the problem when the resource is also available on HTTPS, which is usually the case. If it is not, the upgraded request fails and the feature still breaks, so fix the URLs too.

Why does DNS Panopticon still flag a script that my browser blocks?

Because the page still asks for it. Blocking keeps visitors safe in modern browsers, but the feature is broken and older clients may still load it.

Sources and further reading