Mixed content is an HTTPS page that loads a script, stylesheet, image, frame or form target over plain HTTP. The padlock promises the page cannot be read or altered in transit, and an HTTP subresource breaks that promise: an attacker on the network can replace the script and take over the page. Browsers block most of it now, but the remaining cases leak data or silently break your site.

Why one HTTP resource undoes the whole page

TLS protects each connection individually. When an HTTPS page includes <script src="http://cdn.example/app.js">, the HTML arrived encrypted and authenticated, but the script travels as plain text over a connection anyone between the user and the server can read and rewrite. A script has full access to the page that included it: it can read the session cookie unless it is HttpOnly, rewrite the login form to post elsewhere, capture keystrokes and load further payloads. The encryption of the HTML is irrelevant once a single script slot is under the attacker's control. This is why browsers treat an insecure script, stylesheet or frame as a compromise of the entire page rather than of one file.

The attacker in that sentence is not hypothetical. Public Wi-Fi operators, compromised home routers, ISPs injecting advertisements and any government with network access can all rewrite plain HTTP responses. The technique needs no exploit; it needs only that the page ask for something over HTTP.

How the attack works in practice

Picture a user on hotel Wi-Fi visiting https://shop.example, which includes a tracking script from http://stats.example/t.js. The HTML arrives intact; TLS guarantees that. The browser then opens a plain HTTP connection for the script. The hotel's captive-portal box, or anyone who has joined the same network and poisoned its ARP table, sees that request and answers it first with a script of their choosing. That script now runs inside the origin shop.example: it can read the DOM, watch the checkout form, replace the payment iframe with a lookalike, or fetch /account with the user's cookies and post the result elsewhere. The user sees a padlock throughout.

Older browsers ran this attack silently. Current browsers refuse to load the script, so on a modern client the visible effect is that analytics stop working, which is also why mixed content persists: nobody notices a broken tracker. But the same HTTP URL is still requested by embedded webviews, by outdated browsers on unmanaged devices, by scrapers and previews, and by any tooling that does not implement mixed-content blocking, and the risk to those clients is the full takeover described above.

Active versus passive mixed content

Browsers divide mixed content by what an attacker could do with it. Active (or blockable) mixed content is anything that can change the page's behaviour or access its DOM: scripts, stylesheets, iframes, fonts, XMLHttpRequest and fetch, WebSocket connections (ws://), <object> data, and anything loaded by a script. Passive (or optionally blockable) mixed content can be observed or replaced but not used to run code: images, audio and video. A swapped image can still deceive a user or leak the URL and cookies of the request, which is why "passive" was never the same as "harmless".

ResourceClassChrome, Edge, Firefox, Safari behaviour today
Scripts, stylesheets, iframes, fonts, fetch/XHR, WebSocketsActiveBlocked. The console reports the URL; the page runs without the resource.
Images, audio, videoPassiveAutomatically upgraded to https://; if the secure URL fails, the resource is blocked rather than loaded insecurely.
Form with an http:// actionNeither (navigation)Not blocked. The browser shows a "not secure" warning on the form or the submission; the data is sent in the clear.
Links (<a href="http://">)Not mixed contentAllowed. The next page is a separate navigation; HSTS or HTTPS-Only mode may upgrade it.
Downloads started from an HTTPS page over HTTPMixed downloadBlocked or warned depending on file type; executables are blocked.

The practical consequence is that mixed content on a modern site rarely presents as a security hole in the user's browser and usually presents as breakage: a missing stylesheet, an image that will not load, a widget that silently fails. That is still a security finding, because the same HTTP URLs are reached by older clients, by non-browser fetchers and by anything that follows the link directly, and because the form case is not blocked at all.

The insecure form: the case browsers still allow

A login or checkout form on an HTTPS page whose action points to http:// submits the user's credentials or card details unencrypted, and the browser only warns rather than refusing. The reverse is also dangerous and more common: an HTTP page whose form posts to HTTPS. The submission is encrypted, but the page that contained the form was not, so an attacker could have rewritten the action before the user typed anything. A scanner reports both under Form submits over HTTP. The fix is a page served over HTTPS with an HTTPS or relative action, and HSTS so the HTTP version of the page cannot be served at all.

Finding mixed content on your site

Do not audit by loading pages and watching for warnings. Mixed content lives in places you will not see by clicking around: a hard-coded http:// image URL in a blog post written in 2014, a theme's stylesheet that loads a font over HTTP, an email template reused on a web page, a third-party widget embed copied from documentation years ago, a og:image meta tag, or a JavaScript file that builds URLs with a string prefix. Search the sources instead.

  1. Search templates and code for the literal string http://, excluding comments and links: grep -rn 'http://' --include='*.html' --include='*.js' --include='*.css' --include='*.php' .
  2. Search the database. On WordPress, wp search-replace 'http://example.com' 'https://example.com' --dry-run shows every row that would change; other CMSs have an equivalent or a plain SQL LIKE '%http://%'.
  3. Load key pages with the console open. Every blocked or upgraded request is logged with its originating file and line.
  4. Deploy a report-only policy so real visitors report what your test pages missed: Content-Security-Policy-Report-Only: default-src https:; report-to csp.
  5. Run an external scan to confirm the public pages are clean and catch the cases your CMS injects at render time.

Resources requested by JavaScript at runtime are the hardest to find from source. A widget loader that assembles "http://" + host + path, a legacy analytics snippet, or a map library that fetches tiles over HTTP will not show up in a grep for a literal HTTPS page. The console and the report-only policy catch these, and so does searching your bundled JavaScript for the substring 'http: rather than the full scheme-and-slashes form.

Protocol-relative URLs (//cdn.example/lib.js) were a common workaround during the HTTPS transition and should now be replaced with explicit https://: they only exist to allow HTTP, and you no longer want to allow HTTP.

Fixing it: URLs first, then a safety net

The correct fix is to change each URL to https://, or to a relative path for resources on your own domain. Nearly every third party that still matters serves over HTTPS; one that does not is a resource you should replace, not work around. Once the sources are clean, add a Content Security Policy directive that instructs the browser to upgrade any HTTP subresource you missed:

Content-Security-Policy: upgrade-insecure-requests

With this directive, the browser rewrites http:// to https:// for every subresource, form action and same-site navigation before the request is made. It is a safety net, not the fix, for two reasons. It only helps browsers that honour CSP, and it can only upgrade a host that actually offers HTTPS; a resource whose host is HTTP-only will fail to load, which is the same failure as being blocked. The older block-all-mixed-content directive is deprecated and adds nothing now that browsers block active content by default.

Serve your own domain with Strict-Transport-Security. Once the browser has seen the HSTS header, it upgrades every request to your domain before sending it, which eliminates same-origin mixed content entirely, including URLs in old content you never found.

If the site sits behind a CDN or load balancer that terminates TLS, check that your application sees the request as HTTPS. A framework that generates absolute URLs from the request scheme will otherwise write http:// into every canonical link, redirect and asset URL, and you will be chasing mixed content forever. Django's SECURE_PROXY_SSL_HEADER, WordPress's HTTP_X_FORWARDED_PROTO handling in wp-config.php and Express's trust proxy setting exist for exactly this.

What a scanner reports

The Scan.now website scanner fetches the page over HTTPS, parses the HTML and reports every subresource referenced with an http:// URL under Mixed content, distinguishing active from passive, and reports HTTP form targets separately. It examines the markup as served and does not execute scripts, so resources injected at runtime by JavaScript are outside its view; the report-only CSP above is the way to see those. The severity is high for active content and for forms because both represent either a takeover path on older clients or a broken page on current ones. The security headers checker reports whether upgrade-insecure-requests and HSTS are present. This guide is part of the website security hub; the transport layer that mixed content undermines is explained in SSL and TLS Explained, and the browser-side setting that refuses HTTP entirely is covered in HTTPS-Only Mode.

Our position: any http:// reference on an HTTPS site is a bug, regardless of whether the browser would block it. The distinction between active and passive content tells you how urgent the fix is; it does not tell you whether to make it.