Fix WordPress Mixed Content and HTTPS Errors (Complete Guide)
Mixed content warnings break padlocks and block resources. How to find every insecure URL in WordPress and fix them without breaking the site.
WordPress mixed content errors occur when a page loaded over HTTPS still requests resources (images, scripts, or stylesheets) over HTTP — fix them by running a database search-replace to update all http:// URLs to https://, then use the Really Simple SSL plugin or a Content-Security-Policy upgrade-insecure-requests header to catch anything missed.
You installed an SSL certificate. The padlock appeared. Then you noticed some pages show a broken padlock or no padlock at all - and the browser console is full of "Mixed Content" warnings. This is one of the most common problems after migrating a WordPress site from HTTP to HTTPS, and it can range from a cosmetic issue to a complete breaking of scripts and styles.
This guide covers what mixed content is, how to find every instance of it, and how to fix it - permanently.
What is mixed content?
Mixed content means your page was loaded over HTTPS but some of its resources (images, scripts, stylesheets, iframes, fonts) are being requested over HTTP. Browsers treat this as a security issue because:
- Passive mixed content (images, audio, video) - displayed with a warning but not blocked by default in most browsers. The padlock turns grey or shows a warning icon.
- Active mixed content (scripts, stylesheets, iframes, XMLHttpRequest) - blocked entirely by modern browsers. If a JS file loads over HTTP on an HTTPS page, Chrome and Firefox simply refuse to load it.
Step 1 - Find the mixed content URLs
Browser developer tools
Open DevTools (F12), go to the Console tab, and reload the page. Mixed content errors appear immediately:
Mixed Content: The page at 'https://example.com/page/' was loaded over HTTPS, but requested an insecure resource 'http://example.com/wp-content/uploads/image.jpg'.
The Console shows the URL of every insecure resource. The Network tab, filtered by "Mixed Content" (available in Chrome), lists them with their load status.
Why Inspector should not be your only method
The browser only shows you mixed content errors for the page you are currently viewing. A site with 500 posts can have thousands of insecure URLs buried in content you have not manually checked. You need to scan all of it at once.
SSL check tools
Tools like Why No Padlock (whynopadlock.com) and SSL Shopper's SSL Checker scan a URL and report all insecure resources without you opening DevTools. Useful for a quick check on specific pages.
Search the database directly
The most thorough approach. Connect to your database and run:
SELECT ID, post_title, post_content FROM wp_posts WHERE post_content LIKE '%http://%' AND post_status = 'publish';
This finds every published post or page with an HTTP URL in the content. The result set can be large on old sites - do not be alarmed.
Also check post meta:
SELECT post_id, meta_key, meta_value FROM wp_postmeta WHERE meta_value LIKE '%http://%';
And options (widget content, theme settings):
SELECT option_name, option_value
FROM wp_options
WHERE option_value LIKE '%http://%'
AND option_name NOT IN ('cron', 'rewrite_rules');
Step 2 - Fix URLs in the database
Use Better Search Replace
The Better Search Replace plugin (free, by WP Engine team) lets you search the entire database and replace a string, handling serialised data correctly. Incorrect handling of serialised data is the most common reason DIY SQL replacements corrupt WordPress installations.
- Install and activate Better Search Replace
- Search for:
http://yourdomain.com - Replace with:
https://yourdomain.com - Select all tables
- Check "Run as dry run?" first to preview
- Uncheck "Run as dry run?" and run it for real
Use WP-CLI search-replace
The WP-CLI command is the most reliable method and handles serialised data, GUIDs, and custom tables:
wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' --all-tables --dry-run # Remove --dry-run to apply: wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' --all-tables
After running this, flush your cache and permalinks:
wp cache flush wp rewrite flush
Important: do not replace absolute URLs for other domains
The replacement should only be for your own domain. If your content links to http://anotherdomain.com, leave those alone. They are third-party resources - you cannot force them to load over HTTPS from your end.
Step 3 - Update WordPress address and site URL
Check that your WordPress Address and Site URL are set to HTTPS. In Settings > General:
- WordPress Address (URL):
https://yourdomain.com - Site Address (URL):
https://yourdomain.com
Or set them directly in wp-config.php (which takes precedence over the database):
define( 'WP_HOME', 'https://yourdomain.com' ); define( 'WP_SITEURL', 'https://yourdomain.com' );
If these are still set to http://, every WordPress-generated URL (login form, script sources, media uploads) will be HTTP.
Step 4 - Force HTTPS at the server level
Even after fixing the database, you need server-level redirects to catch any remaining HTTP requests. This also protects against visitors bookmarking HTTP URLs.
Nginx redirect
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com www.yourdomain.com;
# ... rest of config
}
Apache .htaccess redirect
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Place this before WordPress's own rewrite rules in .htaccess.
Cloudflare
If your site is behind Cloudflare, enable "Always Use HTTPS" in the SSL/TLS > Edge Certificates section. This redirects HTTP requests at the CDN level before they even reach your server.
Also ensure your Cloudflare SSL mode is set to "Full (strict)" - not "Flexible". Flexible mode causes a redirect loop when your server also has SSL.
Step 5 - Add the HTTPS_SERVER_VAR fix for reverse proxies
If your WordPress is behind a load balancer or proxy (Cloudflare, Nginx reverse proxy, WP Engine, Kinsta), PHP may not see the original HTTPS connection. WordPress might generate HTTP URLs even though your server config is correct.
Add this to wp-config.php:
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
&& 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
This tells PHP to treat the request as HTTPS when the proxy passes the X-Forwarded-Proto: https header.
Some hosts use different header names. On AWS ALB, it is HTTP_X_FORWARDED_PROTO. On Kinsta and other hosts, you may need to use the Kinsta MU plugin which handles this automatically.
Step 6 - Check hardcoded HTTP URLs in theme and plugin files
Database replacements fix content. But hardcoded URLs in PHP, CSS, and JavaScript files need manual editing.
Search your theme directory:
grep -r "http://" /wp-content/themes/your-theme/ --include="*.php" --include="*.css" --include="*.js"
Common places hardcoded HTTP URLs appear:
- Theme CSS files:
background-image: url(http://...) - Theme PHP:
echo '<img src="http://...">' - Google Fonts: older themes load fonts with
http://fonts.googleapis.com(update tohttps://or remove entirely) - Social share buttons: older plugins generate HTTP URLs for share counts APIs
For third-party plugins, the fix is to update the plugin. Do not modify plugin files directly - they get overwritten on update.
Step 7 - Fix embedded media with HTTP URLs
Uploaded images and embedded media that were uploaded before the HTTPS migration may have their original HTTP URL stored in the attachment metadata. The database replace in Step 2 covers the post_content and post_meta, but some page builders store URLs in custom fields with serialised structures.
For Elementor specifically, after running the search-replace you need to regenerate Elementor's CSS:
WP Admin > Elementor > Tools > Regenerate Files & Data
For Beaver Builder:
WP Admin > Beaver Builder > Tools > Clear Cache
Step 8 - Set a Content Security Policy (CSP)
Once your site is fully HTTPS, add a CSP header that instructs browsers to upgrade or block any HTTP resources. This is a final safety net:
add_header Content-Security-Policy "upgrade-insecure-requests";
upgrade-insecure-requests tells the browser to automatically upgrade HTTP sub-resource requests to HTTPS. This handles any remaining HTTP URLs you missed without breaking them, as long as the resource is available over HTTPS.
Do not use block-all-mixed-content unless you are certain every resource is HTTPS - it causes visible breakage rather than silent upgrades.
Step 9 - Add HSTS
Once HTTPS is fully working, add HTTP Strict Transport Security to tell browsers to always use HTTPS for your domain:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Start with a short max-age (86400 = 1 day) and extend it after confirming nothing is broken. HSTS is stored by the browser and cannot be overridden - if you set a 1-year HSTS header and then need to revert to HTTP for any reason, visitors who already have the header cached will not be able to access the site.
Verifying the fix
After all steps:
- Open your site in Chrome with DevTools open, Console tab
- Hard reload (Ctrl + Shift + R) to bypass cache
- Navigate to several different page types (homepage, post, category, checkout if WooCommerce)
- Confirm zero mixed content warnings in Console
- Check the padlock is fully green/locked with no warning indicator
Run your site through SSL Labs (ssllabs.com/ssltest/) for a comprehensive SSL health check including certificate validity, protocol support, and header configuration.
Related reading
Frequently Asked Questions
How do I fix mixed content errors in WordPress?
Why does WordPress still show mixed content after installing an SSL certificate?
What is the difference between active and passive mixed content in WordPress?
How do I find all mixed content URLs on my WordPress site?
// new_articles
Get notified when new guides drop
Practical WordPress guides from a working agency owner. No filler. Unsubscribe any time.
Was this article helpful?
Thanks for the feedback!