DEV Community

linkbruno.com
linkbruno.com

Posted on Edited on

How to find mixed content (http:// links) on an HTTPS site

Your site moved to HTTPS years ago, but the old http:// URLs are still in there: in blog posts, in image tags, in a footer someone hard-coded. Some of them cause browser warnings, some get blocked outright, and some just add a redirect hop. Here's how to find them and what to do with each kind.

Two kinds of mixed content

Active mixed content is anything that can change the page: scripts, stylesheets, iframes, fetch requests. Browsers block these on HTTPS pages, so a script loaded over http:// simply doesn't run. The result is a broken feature with no visible error.

Passive mixed content is images, audio and video. Modern browsers try to upgrade these to HTTPS automatically. If the HTTPS version doesn't exist, the image breaks.

Plain <a href="http://..."> links aren't mixed content (they're navigation), but on your own domain they still trigger an extra redirect for every click, and on other domains they may point to sites that no longer serve HTTP at all.

Find it in the browser

Open DevTools, then the Console. Mixed content shows up as warnings like:

Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,
but requested an insecure image 'http://example.com/img/logo.png'.
Enter fullscreen mode Exit fullscreen mode

Good for one page; not practical for a whole site.

Find it with grep in your source

If your content is in files:

grep -rnoE 'http://[^"'"'"' )>]+' --include='*.html' --include='*.md' --include='*.php' . \
  | grep -v 'http://www.w3.org' | sort | uniq
Enter fullscreen mode Exit fullscreen mode

The w3.org filter removes XML namespace strings, which look like URLs but aren't requests.

Find it in a WordPress database

Old posts store absolute URLs in the content:

SELECT ID, post_title
FROM wp_posts
WHERE post_status = 'publish'
  AND post_content LIKE '%src="http://%';
Enter fullscreen mode Exit fullscreen mode

To replace your own domain safely, use WP-CLI, which handles serialized data correctly:

wp search-replace 'http://example.com' 'https://example.com' --dry-run
wp search-replace 'http://example.com' 'https://example.com'
Enter fullscreen mode Exit fullscreen mode

Always run the dry run first and take a database backup.

Crawl the rendered pages

Source searches miss URLs built by templates, plugins and themes. A small crawler over the rendered HTML catches those:

import requests, re
from urllib.parse import urljoin

def insecure_resources(url):
    html = requests.get(url, timeout=15, headers={"User-Agent": "Mozilla/5.0"}).text
    found = set()
    for attr, val in re.findall(r'(src|href|srcset|data-src)=["\']([^"\']+)', html):
        for part in val.split(","):
            u = part.strip().split(" ")[0]
            if u.startswith("http://"):
                found.add((attr, u))
    return found

for page in ["https://example.com/", "https://example.com/blog/"]:
    for attr, u in insecure_resources(page):
        print(page, attr, u)
Enter fullscreen mode Exit fullscreen mode

Feed it the URLs from your sitemap to cover every page.

What to do with each finding

Finding Action
Your own domain over http Change to https (or a root-relative path)
Third-party resource that supports https Change to https
Third-party resource with no https Host it yourself, replace it, or remove it
Plain link to an external http site Check it still works; update to https if available

Don't switch to protocol-relative URLs (//example.com/...). They were a workaround for the HTTP era and just add ambiguity now.

Add a safety net

The upgrade-insecure-requests directive tells browsers to fetch every http:// resource over HTTPS:

Content-Security-Policy: upgrade-insecure-requests
Enter fullscreen mode Exit fullscreen mode

It hides the warnings, but if the HTTPS version of a resource doesn't exist, it still breaks. Use it as a backstop, not a fix.

Keep it fixed

New mixed content comes back with every pasted embed and copied snippet. A scheduled crawl that checks every link and image on the site catches regressions; linkbruno.com is built for that kind of ongoing check, alerting when a link or image breaks.

Related reading

Top comments (0)