DEV Community

Merlonix
Merlonix

Posted on Originally published at merlonix.com

A Broken-Link Check Counts 404s. The Resource That Breaks Your Padlock Returns 200.

Originally published on the Merlonix blog.

A broken-link checker does one well-defined thing: it walks the <a href> links on a page, fetches each target, and flags the ones that come back wrong — a 4xx, a 5xx, a timeout, a connection that never opens. That answers a real question: which links point at something that isn't there. Run a clean scan and you get a green report and a reasonable feeling that the page is healthy.

But "every link resolves" and "this page works in a browser" are not the same claim, and the gap between them is a resource class a status-code check is structurally blind to. The resource most likely to visibly break an HTTPS page doesn't fail with a status at all. It returns 200. It's mixed content — an http:// subresource loaded by an https:// page — and because the resource genuinely exists, a checker that grades links on their HTTP status waves it straight through.


The 200 that still breaks the page

When a page served over https:// pulls in a subresource over plaintext http:// — an image, a script, a stylesheet, an iframe — that's mixed content. The subresource itself is usually fine: request it directly and it's a healthy 200. Nothing is "broken" in the sense a link checker means. The break happens in the browser, on the security boundary, and it depends on what kind of resource it is:

  • Active mixed content — a <script>, a stylesheet <link>, or an <iframe> over http:// — is blocked outright. The browser refuses to load it on a secure page. A blocked script means the JavaScript that depended on it silently doesn't run; a blocked stylesheet means the page renders unstyled. No 404 anywhere — the file was reachable, the browser just won't use it.
  • Passive mixed content — an <img>, <video>, or <audio> over http:// — modern browsers try to auto-upgrade to https://, and if that secure version doesn't exist, they block it too. Either way the padlock stops being a clean padlock: the address bar drops to "Not fully secure," and on the blocked case you get a missing image on a page whose image URL returns a perfectly good 200 over http.

So the failure is real and user-visible — a dead script, an unstyled page, a broken image, a downgraded padlock — but it is invisible to any tool that decides "broken" by fetching a URL and reading its status code. The URL isn't broken. The combination of a secure page and an insecure subresource is.

Why it shows up silently

Mixed content is rarely typed in on purpose. It leaks in on a timeline you don't control:

  • A third-party embed — an analytics snippet, a chat widget, an ad tag, an old badge — hardcodes an http:// script URL.
  • A CDN or asset host is referenced with an absolute http:// URL that survives a move to HTTPS because nothing ever re-fetched it to notice.
  • A protocol migration flips the site to HTTPS but leaves absolute http:// URLs baked into content, a theme, or a database field.
  • A Content-Security-Policy that had upgrade-insecure-requests gets relaxed to ship one stubborn widget, and every previously-upgraded subresource quietly reverts.

Every one of those ships green through a broken-link check, because every one of those URLs answers 200.

How to actually find it

Mixed content is found by looking at the protocol of the subresources a secure page loads, not the status of its links:

  1. The browser console is the ground truth: it logs Mixed Content: warnings for every upgraded or blocked resource, and the padlock's site-information panel tells you the page isn't fully secure. This catches resources injected by client-side JavaScript, which a static scan of the served HTML can't see.
  2. CSP turns it into a signal you don't have to eyeball: Content-Security-Policy-Report-Only: block-all-mixed-content (or the reporting directives) will report every insecure subresource without breaking anything, so you can find them before you enforce.
  3. A checker that inspects subresource protocols — not just link statuses — catches the ones present in the served HTML in a single pass.

The free broken-links checker does both halves in one scan: it walks the page's <a href> links and reports the ones that return an error status or time out or fail to connect — and, separately, it reads the page's <img>, <script>, stylesheet, and <iframe> subresources and flags every one loaded over http:// on an https:// page as mixed content, with the exact insecure URLs. It reads the HTML the server sends, so resources injected later by client-side JavaScript still need the browser console — but the hardcoded http:// embeds that cause most real mixed-content breakage are exactly the ones in that served HTML. No signup, one page at a time.

What a link check is good for — and what to check past it

A broken-link check isn't the wrong tool; it's a narrow one. Dead outbound links, a deleted product page still linked from your nav, a 5xx on a resource you own — all real, all worth catching, all what a status check is for. Just don't read "every link resolves" as "this page is fine over HTTPS." Once the links are clean, the questions a status code can't answer are:

  • Does any http:// subresource load on an https:// page? (Mixed content — the padlock and the blocked-script break.)
  • Does the page's sitemap point at URLs that are actually reachable? (A sitemap listing dead <loc> entries misroutes crawlers, and it's another thing a spot-check of visible links skips.)

The one-line version: a broken-link check grades URLs by their status code, and mixed content is the failure that keeps a perfect status code while breaking the page — a 200 resource the browser blocks, on a secure page whose padlock quietly downgrades. Check the links, then check whether any of them are insecure subresources on a secure page.

And it comes back. A redeploy that re-adds an old widget, a CDN swap, a CSP relaxed to ship one integration — each reintroduces an http:// subresource that answers 200 and passes the next link check exactly like it passed the last one. Merlonix crawls your site on a schedule, finds broken links and mixed content across every page, and alerts you the moment a new one appears — the same way it watches SSL and DNS: continuously, from outside your stack, so a padlock that silently went from clean to "not fully secure" is something you hear from monitoring, not from a customer. Run the free broken-links scan to see where a page stands right now, and browse the rest of the free tools while you're there.

Top comments (0)