You installed an SSL certificate, forced HTTPS across your site, and then opened Chrome's developer tools to find a wall of red warnings. The padlock is gone, replaced by a grey info icon or a "Not Secure" label. Your site is serving HTTPS but still loading some resources over plain HTTP, and browsers treat that as a security violation. This is a mixed content error, and it is one of the most common post-SSL problems webmasters face. The good news is that every single mixed content error is fixable, usually in under an hour, once you know where to look.

What Mixed Content Actually Means

When a browser loads an HTTPS page, it expects every resource on that page, images, scripts, stylesheets, fonts, iframes, API calls, to also come over an encrypted connection. If even one resource is requested over HTTP, the browser flags the entire page. There are two severity levels:

  • Passive mixed content — images, audio, and video loaded over HTTP. Browsers usually still display them but show a warning or remove the padlock. Chrome and Firefox are making this stricter with each release.
  • Active mixed content — scripts, stylesheets, iframes, and XHR/fetch requests over HTTP. Browsers block these entirely because a HTTP script can rewrite the page and steal data. If your JavaScript or CSS loads over HTTP, it simply will not run.

Both types need to be fixed. Passive mixed content is a reputation problem today and a breakage problem tomorrow as browsers continue tightening restrictions.

Why Mixed Content Errors Happen

Understanding the root causes saves you from chasing symptoms. The most common triggers are:

  1. Hard-coded HTTP URLs in your database or theme — a WordPress post saved years ago with src="http://yourdomain.com/image.jpg" will survive an SSL migration unchanged.
  2. Third-party embeds and widgets — an old YouTube embed URL, a weather widget, an ad network snippet, or a payment badge that still points to an HTTP endpoint.
  3. CDN or asset URLs not updated — you switched to HTTPS but your CDN origin or asset base URL in the CMS config still reads http://.
  4. External fonts or libraries pulled from HTTP — a Google Fonts or jQuery CDN link using http:// instead of https://.
  5. Relative protocol-less URLs that resolve wrong — URLs written as //example.com/resource that resolve to HTTP if there is an intermediate redirect issue.
  6. Plugins or page builders that hard-code the site URL at install time and never update when you switch to HTTPS.

Step 1: Find Every Mixed Content Error

You cannot fix what you cannot see. Use all three of these methods to build a complete picture before touching any files.

Browser Developer Tools

Open your site in Chrome or Firefox. Press F12 (or Cmd+Option+I on Mac) and click the Console tab. Mixed content warnings appear in yellow or red and show the exact URL of every offending resource. Click the Network tab, reload, and filter by the type (JS, CSS, Img) to isolate blocked resources. Copy every flagged URL into a text file.

Command-Line Crawl with curl or wget

For a quick check of a specific page's source, run:

curl -s https://yoursite.com/page/ | grep -Eo '(src|href|action)="http://[^"]+"'

This greps the raw HTML for any attribute pointing to an HTTP URL. Pipe to a file if the output is long:

curl -s https://yoursite.com/ | grep -Eo '(src|href|action)="http://[^"]+"' > mixed.txt

Online Scanners

Tools like Why No Padlock, SSL Shopper, and JitBit's Mixed Content Checker crawl your pages and report every HTTP resource they find. Run your homepage and a few key landing pages through these before and after your fix to confirm everything is clean.

After fixing mixed content, DNS or CDN caching can sometimes serve a stale version of your page. Use the DNS Propagation Checker to confirm your domain is resolving to the correct server across global nameservers before concluding that a persistent issue is mixed content related rather than a caching or propagation problem.

Step 2: Fix Mixed Content in WordPress

WordPress is where the majority of mixed content problems live because the site URL is stored in the database, and every post, image attachment, and option record can carry an http:// prefix from before your SSL migration.

Use the Better Search Replace Plugin

  1. Install and activate Better Search Replace from the WordPress plugin directory.
  2. Go to Tools > Better Search Replace.
  3. In the Search for field, enter: http://yourdomain.com
  4. In the Replace with field, enter: https://yourdomain.com
  5. Select all tables (Ctrl+A or Cmd+A).
  6. Run a dry run first by checking Run as dry run and clicking Run Search/Replace. It will tell you how many rows would change without touching anything.
  7. Uncheck the dry run option and run it for real. The plugin handles serialized data correctly, which is critical in WordPress.

Use WP-CLI on the Command Line

If you have SSH access, this is faster and more reliable for large databases:

wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' --all-tables --precise --skip-columns=guid

The --precise flag handles serialized PHP data safely. The --skip-columns=guid prevents breaking WordPress's internal GUID tracking for posts.

Update WordPress Site URL Settings

Even after a database search-replace, confirm these settings under Settings > General in the WordPress admin. Both WordPress Address (URL) and Site Address (URL) must start with https://. If they do not, update them manually and save.

Step 3: Force HTTPS at the Server Level

A server-level rewrite catches anything your CMS misses and redirects stray HTTP requests before they can create mixed content.

Apache (.htaccess)

RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Place this at the top of your .htaccess file, before any WordPress or application rewrites. Test with curl -I http://yourdomain.com and confirm you see a 301 redirect to the HTTPS version.

Nginx (nginx.conf or site config)

server { listen 80; server_name yourdomain.com www.yourdomain.com; return 301 https://$host$request_uri; }

After editing the config, test for syntax errors and reload:

nginx -t && systemctl reload nginx

Step 4: Fix Mixed Content in Themes, Plugins, and Custom Code

If your theme or a plugin hard-codes HTTP URLs, the database search-replace will not always catch them because they live in PHP files, not the database.

Search your theme and plugin files recursively:

grep -r "http://" /var/www/html/wp-content/themes/your-theme/ --include="*.php" grep -r "http://" /var/www/html/wp-content/plugins/ --include="*.php"

When you find a hard-coded HTTP URL in a file you control, edit it to https://. For third-party plugins you cannot edit, check whether an update exists that resolves the issue, or contact the developer. Some older unmaintained plugins need to be replaced entirely.

For external libraries like jQuery, Bootstrap, or Google Fonts, update every CDN link. Replace:

src="http://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"

With the HTTPS version:

src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"

Or better still, use protocol-relative URLs where appropriate, though full HTTPS URLs are clearer and less error-prone.

Step 5: Use a Content Security Policy Upgrade Header as a Safety Net

Once your content is mostly clean, you can add an HTTP response header that instructs browsers to automatically upgrade any remaining HTTP sub-requests to HTTPS. This does not fix the root cause but acts as a fallback while you track down the last few stragglers.

In Apache's .htaccess or server config, add:

Header always set Content-Security-Policy "upgrade-insecure-requests"

In Nginx:

add_header Content-Security-Policy "upgrade-insecure-requests";

This header tells the browser: if you see an HTTP URL for a sub-resource, silently request it over HTTPS instead. It works for resources that actually exist on HTTPS. If the resource genuinely does not support HTTPS at all, it will fail to load rather than load insecurely, which is the correct browser behaviour.

If you are still seeing the wrong IP address or unexpected redirects after fixing mixed content, use the DNS Lookup tool to verify your A and CNAME records are pointing to the correct server. A misconfigured DNS record can send traffic to an HTTP-only server even when your primary site is fully HTTPS.

Step 6: Fix Mixed Content on Non-WordPress Sites

For static HTML sites, the approach is straightforward: search all HTML, CSS, and JavaScript files recursively and replace HTTP asset URLs with HTTPS:

grep -r "http://" /var/www/html/ --include="*.html" -l find /var/www/html/ -name "*.html" -exec sed -i 's|http://yourdomain.com|https://yourdomain.com|g' {} +

Be careful with the sed command: use it only on files you have backed up, and limit the search to your own domain to avoid accidentally rewriting legitimate references to other sites.

For Drupal sites, the Drupal core provides a path to run a database search-replace via Drush:

drush sqlq "UPDATE node__body SET body_value = REPLACE(body_value, 'http://yourdomain.com', 'https://yourdomain.com')"

Run this carefully in a staging environment first. Drupal stores content in many tables, so a full database dump and targeted search across all text fields is the safest approach.

How to Verify the Fix

After making changes, clear every cache: your browser cache, your server-side cache (Varnish, Redis, W3 Total Cache, WP Super Cache, LiteSpeed Cache), and your CDN cache (Cloudflare, Fastly, AWS CloudFront). Then:

  1. Open the page in Chrome in an incognito window to bypass local cache.
  2. Open DevTools (F12), go to the Console tab, and reload. Zero mixed content warnings means success.
  3. Check the Network tab and filter by each resource type. Every request should show https:// in the URL column.
  4. Run the page through Why No Padlock or SSL Shopper again. Both tools should now report a clean padlock.
  5. Verify the padlock icon in the browser address bar is solid and shows a valid certificate when clicked.

How to Prevent Mixed Content in the Future

Fixing mixed content once is frustrating. Fixing it repeatedly is avoidable with a few guardrails in place:

  • Always use HTTPS-first asset URLs when writing new content or adding new embeds. Train your content team to do the same.
  • Audit your site quarterly using a crawler. New plugins, new embeds, and new posts can introduce HTTP URLs at any time.
  • Keep the upgrade-insecure-requests CSP header in place even after the initial cleanup. It is lightweight and catches edge cases.
  • Test SSL configuration on staging before deploying changes to production. A staging environment with the same SSL setup catches problems before your visitors do.
  • Use a relative URL base tag or configure your CMS to always generate absolute URLs using the HTTPS site URL setting so that auto-generated links never produce HTTP.
  • Check third-party scripts regularly. A widget or ad network that switches to HTTP overnight will break your padlock without you touching a single file.

Summary

Mixed content errors are almost always a migration artifact or a hard-coded URL problem. The fix follows a consistent pattern: find every HTTP resource using DevTools and command-line grep, update the database and files to use HTTPS, enforce HTTPS at the server level with a 301 redirect, and add the upgrade-insecure-requests header as a fallback. After clearing your caches and verifying with DevTools, your padlock should be solid green and your site's security posture measurably improved.