How to check if mixed content exists on a page

Fetch the served HTML and grep it for plain HTTP subresources with curl -s https://example.com/ | grep -o -E 'src="http://[^"]*"'. Any line printed is an asset requested over HTTP from an HTTPS page. curl does not run scripts, so an empty result is a pre-filter rather than a pass, and the browser console decides.

Why check this

Mixed content is how a page that passed the HTTPS check still leaks. An image loaded over HTTP is a plain request carrying the referrer, and a script loaded over HTTP can rewrite the page. Run the grep in regression on every template that was touched, and run the browser check before release. The failure it prevents is a payment page whose analytics script is pulled over HTTP, blocked by the browser, and the form silently stops submitting.

Prerequisites

import http from 'node:http';
const page = `<!doctype html><html><head>
<link rel="stylesheet" href="http://cdn.example.com/site.css">
<script src="https://cdn.example.com/app.js"></script>
</head><body>
<svg xmlns="http://www.w3.org/2000/svg"><circle r="4"/></svg>
<img src="http://images.example.com/logo.png" alt="logo">
<a href="http://blog.example.com/post">Blog post</a>
</body></html>`;
http.createServer((req, res) => {
  res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
  res.end(page);
}).listen(8791, '127.0.0.1');

Steps

  1. Step 1.

    Grep the served HTML for every http:// string and count what comes back.

    curl -s http://127.0.0.1:8791/ | grep -o 'http://[^"]*' | sort | uniq -c
    
          1 http://blog.example.com/post
        1 http://cdn.example.com/site.css
        1 http://images.example.com/logo.png
        1 http://www.w3.org/2000/svg

    Four hits, two of which are not mixed content. Read the next two steps before filing any of them.

  2. Step 2.

    Scope the pattern to attributes, which drops the XML namespace.

    curl -s http://127.0.0.1:8791/ | grep -o -E '(src|href)="http://[^"]*"' | sort -u
    
    href="http://blog.example.com/post"
    href="http://cdn.example.com/site.css"
    src="http://images.example.com/logo.png"

    http://www.w3.org/2000/svg is gone. It is an SVG namespace identifier, never requested, and it appears in almost every modern page.

  3. Step 3.

    Narrow to the attributes that actually cause a request.

    curl -s http://127.0.0.1:8791/ | grep -o -E 'src="http://[^"]*"|rel="stylesheet" href="http://[^"]*"' | sort -u
    
    rel="stylesheet" href="http://cdn.example.com/site.css"
    src="http://images.example.com/logo.png"

    Two real findings. The <a href> from step 2 is a navigation target, not a subresource, so it is not mixed content.

  4. Step 4.

    Run the same scoped pattern against the page you are testing and count the hits.

    curl -s https://www.cloudflare.com/ | grep -c -E '(src|href)="http://[^"]*"'
    
    0

    The naive pattern from step 1 returns 31 hits on this same page, all of them the SVG namespace. The count that matters is this one.

  5. Step 5.

    Open the page in Chrome, press F12, and read the Issues tab in DevTools, then the Network panel with the Protocol column shown.

    The grep only sees HTML that arrived in the first response. Anything a script writes into the DOM, a CSS url() in a stylesheet, a fetch to an HTTP endpoint or a redirect that lands on HTTP is invisible to it. The browser is the only place where all of those appear, because it is the only thing that runs the page. Sort the Network panel by the Name column and look for entries whose scheme is http.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | src="http://…" on a script | Active mixed content | Release blocker. Browsers block it, so the feature is already broken. | | href="http://…" on a stylesheet link | Active mixed content | Same. A blocked stylesheet changes the layout, not only the padlock. | | src="http://…" on an image | Passive mixed content | Fix it. Browsers upgrade or block it depending on version, so behaviour varies by client. | | http://www.w3.org/2000/svg | An XML namespace identifier | Ignore. No request is made for it. | | href="http://…" on an anchor | A link to another page | Not mixed content. Worth fixing separately if it is your own domain. | | Grep is clean, the browser Issues tab is not | A script or a stylesheet built the URL at runtime | Trust the browser. Find the code that composes the URL. |

Common mistakes

Sign: A grep for http:// reports dozens of hits and none of them are real.Cause: SVG markup carries xmlns="http://www.w3.org/2000/svg" and structured data carries http://schema.org URLs. Both are identifiers, not addresses. On www.cloudflare.com the naive pattern returned 31 hits on 2026-09-11 and the attribute-scoped pattern returned 0.
Sign: The grep is clean and the browser still reports mixed content.Cause: curl returns the HTML as sent. Subresources injected by a script, referenced from inside a CSS file, or reached through an HTTP redirect never appear in that text. The grep is a pre-filter for the served markup only.
Sign: The page looks fine in the browser, so the finding is closed as invalid.Cause: Browsers upgrade passive mixed content to HTTPS silently where they can. The page renders, the request may still leave over HTTP on older clients, and the report is correct even though the screen is not.
Sign: Protocol-relative URLs such as //cdn.example.com are recorded as safe.Cause: They inherit the scheme of the page, which is correct on HTTPS today, and they resolve to HTTP when the same markup is opened from a local file or an HTTP staging host. Write the scheme out.

What to check next

FAQ

How to check a website for mixed content over HTTPS?

Run steps 1 to 4 on the served HTML for the pre-filter, then step 5 for the answer. The grep finds what the template hard-coded; the browser finds what the running page requested.

How to find mixed content warnings?

Open DevTools, Issues tab. The Network panel, with the Protocol column shown, lists the requests themselves so you can confirm which scheme each one used.

Why can curl not detect mixed content on its own?

curl downloads bytes. It does not parse HTML, run JavaScript, fetch stylesheets or follow subresource references, so it cannot know which URLs the page would have requested.

Does an HTTP link in an anchor tag count?

No. Mixed content covers subresources the page loads, not pages it links to. The link is worth fixing, and it belongs on a different ticket.

Is a protocol-relative URL safe?

It behaves correctly on an HTTPS page and incorrectly everywhere else. Write https:// explicitly so the markup does not depend on where it is opened.

Verified

Verified by Maks Vernycurl 8.21.0node 22.23.2

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

intermediate7 minpublished updated Maks Verny