Installing an SSL certificate is supposed to make your site secure. The padlock appears, the URL shows HTTPS, and for a moment everything looks correct. Then someone checks the browser console or uses a scanning tool and finds a list of insecure resources still loading over plain HTTP. The padlock breaks. Chrome shows Not Secure in the address bar. The SSL certificate is real, installed correctly, and doing its job — and the site is still flagged as partially insecure.
This is mixed content. It is one of the most common problems that follows an HTTPS migration, and it happens because SSL certificates secure the connection between the browser and the server but do nothing to update the URLs embedded in your pages, templates, database entries, and stylesheets. An SSL certificate does not rewrite your content. It only protects the transport. Everything that still points to http:// loads over an unencrypted connection regardless of your certificate.
This guide covers what mixed content is at the technical level, why browsers treat different types differently, how to find every insecure resource on a site, and how to fix it across every common environment: WordPress, static sites, custom PHP applications, and CDN configurations.
What Mixed Content Is and Why Browsers Object to It
Every HTTPS page makes a promise to the visitor’s browser: all data transmitted for this page is encrypted and the server’s identity has been verified. When that HTTPS page loads a resource over HTTP — an image, a script, a stylesheet, a font — that promise is broken for that resource. The resource travels over an unencrypted connection where it can be intercepted, read, and modified by anyone positioned between the user and the server.
The security risk is not theoretical. A man-in-the-middle attacker on a coffee shop network, a compromised router, or a malicious ISP can intercept any HTTP request in transit. With passive mixed content like images, an attacker can replace your product photos with offensive content, swap your branding, or inject tracking pixels. With active mixed content like JavaScript, the attack surface is far worse: an attacker who can modify a script loaded over HTTP can execute arbitrary code in the context of your HTTPS page, bypassing the certificate’s protection entirely.
A single JavaScript file loaded over HTTP on an otherwise fully HTTPS page gives an attacker complete control over that page’s behavior. They can read form inputs including passwords and card numbers, redirect users to phishing pages, inject hidden iframes, or exfiltrate any data the page handles. The HTTPS certificate on the page itself provides no protection against this because the malicious code runs inside the browser’s trusted context for your domain.
Browsers respond to this asymmetrically. Not all mixed content is treated with equal urgency because not all of it carries the same risk.
Active Mixed Content vs Passive Mixed Content: A Critical Distinction
The browser spec and the security community divide mixed content into two categories based on how much damage a compromised resource can do. This distinction determines whether the browser blocks the content entirely or merely warns about it.
| Type | Resources Affected | Browser Behavior | Risk Level | Padlock Effect |
| Active mixed content | Scripts (.js), stylesheets (.css), fonts, iframes, XMLHttpRequest/fetch calls, Web Workers | Blocked entirely in all modern browsers. The resource does not load at all. | Critical. A compromised script controls the entire page. | Page may break visually. Hard error in DevTools. |
| Passive mixed content | Images, audio files, video files | Historically loaded with a warning. Modern Chrome auto-upgrades these to HTTPS; if unavailable, blocks them. | Moderate. Attacker can modify visuals or inject tracking. | Padlock broken or removed. ‘Not Secure’ may appear. |
As of Chrome 94 (2021), Chrome auto-upgrades passive mixed content resources to HTTPS before fetching them. If the HTTPS version of an image or video is unavailable, Chrome blocks the resource rather than loading it over HTTP. This means the old behavior where images loaded with just a warning is no longer reliable. Passive mixed content that cannot be served over HTTPS now breaks visually on Chrome as well as Firefox and Safari.
How Mixed Content Gets Into a Site in the First Place
Understanding the sources prevents recurring problems after a fix. Mixed content appears through several distinct mechanisms, and each one requires a different remediation approach.
The HTTP-to-HTTPS migration problem
The most common source. A site runs on HTTP for years, accumulating content. Images are uploaded and stored with http:// in their database URLs. Links are written with full http:// paths. Theme templates contain hardcoded http:// references. When SSL is enabled and HTTPS goes live, none of that stored content is updated. The site serves HTTPS pages that reference all those HTTP resources, producing mixed content warnings across the entire site.
This is why the fix is not installing an SSL certificate and calling the job done. Migration requires updating every URL stored in the database and embedded in templates. The certificate installation is the beginning, not the end.
Third-party resources and embeds
YouTube embeds, social sharing widgets, advertising scripts, analytics tags, map embeds, payment form iframes, and any other content loaded from external domains can introduce mixed content if those external domains have not fully migrated to HTTPS. A Google Maps embed from 2016 using an http:// iframe src is mixed content. An old advertising network that still serves assets over HTTP is mixed content. A self-hosted font loaded via an http:// URL in a CSS file is mixed content.
Third-party content is harder to fix because you cannot update the source. The solutions are updating to the HTTPS version of the embed (most major services now exclusively use HTTPS), replacing the third-party resource with a HTTPS-capable alternative, or removing it.
Hardcoded URLs in theme files and templates
WordPress themes, page builder templates, and custom PHP files sometimes contain hardcoded http:// URLs pointing to resources on your own domain. Database search-and-replace tools fix URLs stored in the database but do not touch PHP template files, CSS files in the theme directory, or JavaScript files in plugins. Any http:// reference written directly into code rather than generated from database values requires a direct file edit.
CDN misconfigurations
Sites that use a CDN to serve static assets can introduce mixed content if the CDN is configured to serve assets over HTTP rather than HTTPS, if the CDN URL in the site configuration uses http://, or if the CDN’s SSL configuration is incomplete. A WordPress site that references its media library through a CDN URL must have that CDN URL configured with HTTPS for the CDN’s assets to not be mixed content.
User-submitted content
On sites that accept content from users, such as forums, comments, or blog networks, users can embed external images using http:// URLs that you have no control over. Stored user content in the database will contain these references and they will cause mixed content warnings on pages where that content appears. Content Security Policy headers are the most practical defense here because individually hunting down every user-submitted http:// URL is not feasible at scale.
How to Find Every Insecure Resource Before You Start Fixing
Attempting to fix mixed content without a complete inventory wastes time. Different sources of insecure resources require different fix methods, and some are easy to miss. Use more than one detection method.
Browser DevTools: the fastest first look
Chrome and Firefox both surface mixed content in the Console and Network panels. Open the page that is showing the broken padlock, press F12 to open DevTools, and click the Console tab. Mixed content warnings appear in yellow (passive) or red (active). Each entry shows the exact URL of the offending resource and the line in the HTML or CSS where the reference appears.
For a more complete view, open the Network tab, reload the page, and filter by the warning icon or by typing http:// in the filter bar. This shows every HTTP request made by the page, including requests that were blocked or auto-upgraded, with the full request URL, the file that triggered it, and the HTTP status.
| // In Chrome DevTools Console, look for entries like:
// Mixed Content: The page at ‘https://yourdomain.com’ was loaded over HTTPS // but requested an insecure resource ‘http://yourdomain.com/wp-content/uploads/2019/old-image.jpg’ // This request has been blocked; the content must be served over HTTPS.
// Or for passive content (now auto-upgraded in Chrome): // Mixed Content: The page at ‘https://yourdomain.com’ was loaded over HTTPS // but requested an insecure image ‘http://yourdomain.com/images/banner.jpg’ // This content should also be served over HTTPS.
// The URL after the single quotes is the resource you need to fix. // The file/line reference tells you where the http:// URL is coming from. |
Why No Padlock and online scanners
The Why No Padlock tool (whynopadlock.com) scans a single URL and lists every insecure resource it finds on that page. It is useful for a quick check without opening DevTools and for sharing results with developers or clients. Enter the URL, complete the verification, and it returns a list of every HTTP resource found on the page. The SSL Labs server test (ssllabs.com/ssltest) does not specifically scan for mixed content but does identify certificate chain issues and protocol configuration problems that can compound mixed content issues.
Screaming Frog for site-wide auditing
Screaming Frog SEO Spider in crawl mode can scan an entire site and flag all pages containing mixed content. The free version crawls up to 500 URLs. Configure it to check response codes and content type, enable JavaScript rendering if your site uses JavaScript to load resources, and look for the Security filter in the left panel after the crawl completes. This gives you a prioritized list of pages to fix, not just a single page.
WP-CLI for WordPress database scanning
For WordPress sites, WP-CLI provides the most thorough database-level scan for HTTP URLs:
| # Search for all http:// references in the WordPress database:
wp search-replace ‘http://yourdomain.com’ ‘https://yourdomain.com’ –dry-run –all-tables –report-changed-only
# The –dry-run flag shows what would change without making changes. # Review the output to confirm scope before running without –dry-run.
# When ready to apply: wp search-replace ‘http://yourdomain.com’ ‘https://yourdomain.com’ –all-tables
# Also search for protocol-relative or bare HTTP references in content: wp search-replace ‘src=”http://’ ‘src=”https://’ –all-tables –dry-run wp search-replace “src=’http://” “src=’https://” –all-tables –dry-run |
Fixing Mixed Content on WordPress
WordPress is the most common environment for mixed content problems because it stores all content URLs in a MySQL database. The SSL certificate installation does not touch the database. Every image URL uploaded before the HTTPS migration, every link written in a post, and every setting stored by plugins retains the original http:// prefix until it is explicitly updated.
Step 1: Update WordPress Address and Site Address
Before any database search-and-replace, confirm that WordPress itself is configured to use HTTPS:
- Log into the WordPress dashboard
- Go to Settings and click General
- Check both WordPress Address (URL) and Site Address (URL)
- Both must start with https:// not http://
- Click Save Changes
Changing the Site Address URL in WordPress settings immediately affects how WordPress generates all internal links. If you change it before SSL is properly configured, you can lock yourself out of the admin. Only make this change when the SSL certificate is installed, tested, and working.
Step 2: Search and replace HTTP URLs in the database
The Better Search Replace plugin performs a database-wide search-and-replace that correctly handles WordPress’s serialized data format. Raw MySQL search-and-replace tools can corrupt serialized arrays if used directly, breaking plugin settings and widget configurations. Better Search Replace handles serialization automatically.
- Install and activate Better Search Replace from the WordPress plugin directory
- Go to Tools and select Better Search Replace
- In Search for: enter http://yourdomain.com (use your actual domain)
- In Replace with: enter https://yourdomain.com
- Select all tables in the Select tables list
- Check Run as dry run first to preview changes without applying them
- Review the count of replacements shown
- Uncheck Run as dry run and click Run Search/Replace to apply
Run the search twice: once with your domain in the search field (http://yourdomain.com) and once with just the protocol and double-slash (http://) to catch any hardcoded references to external HTTP resources stored in the database. The second pass reveals third-party HTTP embeds stored in post content and widget settings.
Step 3: Fix hardcoded URLs in theme and plugin files
Better Search Replace only touches the database. Any http:// URLs written directly into PHP template files, CSS files in the theme directory, or JavaScript files in plugin folders require direct file editing. Use your hosting file manager or FTP client to search for http:// in your theme’s functions.php, header.php, footer.php, and any custom template files. Replace each instance with https://.
| # Via SSH, search all theme PHP files for http:// references:
grep -r ‘http://’ /var/www/html/wp-content/themes/your-theme/ –include=’*.php’ grep -r ‘http://’ /var/www/html/wp-content/themes/your-theme/ –include=’*.css’
# This outputs the file path and line number for each http:// occurrence. # Edit those files directly and replace http:// with https://
# For plugins (only edit if you maintain the plugin, not third-party plugins): grep -r ‘http://’ /var/www/html/wp-content/plugins/your-custom-plugin/ –include=’*.php’ |
Never directly edit third-party plugin files. Changes are overwritten on the next plugin update. If a plugin contains hardcoded http:// URLs, check whether an updated version of the plugin already fixes the issue. If not, contact the plugin author or replace the plugin with an HTTPS-compatible alternative.
The Really Simple SSL plugin: when to use it and when not to
Really Simple SSL and SSL Insecure Content Fixer are WordPress plugins that dynamically replace http:// with https:// in page output as it is generated. They work by filtering WordPress output rather than updating the database, which means they add a small amount of processing overhead to every page load and can mask underlying database issues rather than fixing them.
These plugins are appropriate for a temporary fix while you work through a proper database cleanup, or for catching edge cases that a database search-and-replace misses. They are not appropriate as a permanent long-term solution on high-traffic sites because the ongoing filtering overhead is unnecessary once the database URLs are properly updated. Treat them as scaffolding: useful during the fix process, removable afterward.
Fixing Mixed Content on Static Sites and Custom PHP Applications
Static HTML sites
For static HTML sites, mixed content sources are the HTML files themselves. A global search-and-replace across all HTML files in the site directory finds every http:// reference:
| # Find all http:// references in HTML files:
grep -r ‘http://’ /path/to/site/ –include=’*.html’ –include=’*.htm’
# Replace all http://yourdomain.com with https://yourdomain.com in HTML files: # (Linux/Mac with sed): find /path/to/site/ -name ‘*.html’ -exec sed -i ‘s|http://yourdomain.com|https://yourdomain.com|g’ {} +
# Also check CSS files for background-image and @import URLs: grep -r ‘http://’ /path/to/site/ –include=’*.css’
# And JavaScript files for any hardcoded fetch/XHR URLs: grep -r ‘http://’ /path/to/site/ –include=’*.js’ |
Custom PHP applications (non-WordPress)
PHP applications that generate URLs dynamically by concatenating the protocol with the domain are the easiest to fix: change the hardcoded http:// to https:// in the URL-building code, or better, use a relative protocol reference (// instead of http://) for internal resources so the resource inherits the page’s protocol automatically.
For custom applications with database-stored content, the approach is the same as WordPress: identify which database tables contain URL fields or content fields with embedded URLs, run a search-and-replace on those tables using SQL, and handle serialized data with custom deserialization logic if needed.
| // PHP: using relative URLs for internal resources
// Instead of: $asset_url = ‘http://yourdomain.com/assets/style.css’;
// Use protocol-relative (inherits the page’s protocol): $asset_url = ‘//yourdomain.com/assets/style.css’;
// Or better, generate dynamically using $_SERVER: $protocol = isset($_SERVER[‘HTTPS’]) && $_SERVER[‘HTTPS’] !== ‘off’ ? ‘https’ : ‘http’; $asset_url = $protocol . ‘://yourdomain.com/assets/style.css’;
// Or simply use root-relative paths for same-domain assets: $asset_url = ‘/assets/style.css’; |
Content Security Policy: The Scalable Long-Term Solution
Once all the known http:// references are fixed, two things remain: preventing new mixed content from being introduced, and catching any residual issues you missed during the manual cleanup. Content Security Policy (CSP) headers serve both purposes. They instruct the browser on what it is and is not allowed to load, and they can report violations back to you for monitoring.
upgrade-insecure-requests: the automatic upgrade directive
The upgrade-insecure-requests directive tells the browser to automatically attempt to upgrade any http:// resource request to https:// before making the request. If the HTTPS version exists, it loads transparently. If it does not exist, the browser blocks the resource rather than loading it over HTTP. This is a safety net, not a substitute for fixing the underlying URLs.
| # Add to your web server configuration (Nginx):
add_header Content-Security-Policy “upgrade-insecure-requests;”;
# Add to your web server configuration (Apache, in .htaccess or VirtualHost): Header always set Content-Security-Policy “upgrade-insecure-requests;”
# Or via HTML meta tag in the <head> of each page: # <meta http-equiv=”Content-Security-Policy” content=”upgrade-insecure-requests;”>
# WordPress: add to functions.php: add_action(‘send_headers’, function() { header(‘Content-Security-Policy: upgrade-insecure-requests;’); }); |
CSP report-only mode: monitoring without breaking
Before enforcing a strict CSP, use report-only mode to identify all mixed content violations across the site without blocking anything. The browser logs every policy violation and sends reports to a specified endpoint. This reveals mixed content on pages that your initial scan missed, particularly those that require authentication or contain user-specific content.
| # Report-only mode logs violations without blocking (Nginx):
add_header Content-Security-Policy-Report-Only “default-src https:; upgrade-insecure-requests; report-uri /csp-report-endpoint”;
# The /csp-report-endpoint needs to accept POST requests with JSON bodies. # Free services like report-uri.com provide a hosted endpoint for CSP reports.
# A minimal CSP for an HTTPS site that blocks all HTTP resources: add_header Content-Security-Policy “default-src https:; upgrade-insecure-requests;”;
# This blocks any resource not loaded over HTTPS. # Test in report-only mode first before switching to enforcement. |
CSP report-only mode is the most reliable way to find mixed content on dynamic sites where content varies by user, session, or page state. It catches violations in production that static scans cannot find because it observes actual browser requests rather than parsing HTML.
Enforcing HTTPS at the Server Level
The server-side redirect from HTTP to HTTPS prevents mixed content from the direction of visitors who type or click http:// links to your site. It does not fix mixed content within your pages, but it closes the gap where internal links using http:// generate a redirect rather than loading directly over HTTPS.
Nginx HTTPS redirect
| # Nginx: redirect all HTTP to HTTPS
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 your SSL configuration } |
Apache HTTPS redirect
| # In .htaccess or VirtualHost configuration:
RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Or using the simpler redirect directive: RewriteEngine On RewriteCond %{SERVER_PORT} 80 RewriteRule ^(.*)$ https://yourdomain.com/$1 [R=301,L] |
Verifying the Fix and Keeping Mixed Content Out
After applying fixes, verify that the padlock has returned and that no mixed content warnings remain in the browser console. Verification is not optional. A single missed resource keeps the padlock broken. Test multiple pages: the homepage, several post or product pages, the checkout or contact form, and any page with embeds or user-submitted content.
To prevent mixed content from reappearing after the initial fix:
- Set the WordPress URL settings to HTTPS permanently. Any future content added through the WordPress editor will use the site’s configured base URL, which will now be HTTPS.
- Train your content team. Anyone adding content to the site must paste HTTPS URLs for images and embeds, not HTTP. A brief documentation note saves hours of future debugging.
- Use relative URLs for internal assets in templates. An image referenced as /wp-content/uploads/image.jpg rather than http://yourdomain.com/wp-content/uploads/image.jpg cannot be mixed content because it inherits the page’s protocol.
- Monitor with a site audit tool. Screaming Frog, Sitebulb, or an automated CI step can flag any new http:// references introduced during content updates or plugin upgrades before they reach visitors.
- Keep the upgrade-insecure-requests CSP header active. It functions as a safety net for any http:// references that get introduced after cleanup, automatically upgrading them rather than displaying them as mixed content.
Frequently Asked Questions
What is a mixed content error?
A mixed content error occurs when a web page served over HTTPS loads one or more resources over plain HTTP. The HTTPS page is secure, but the HTTP resources are not. Browsers flag this because an attacker can intercept and modify the HTTP resources in transit, potentially compromising the security of the HTTPS page that loaded them. Active mixed content (scripts, stylesheets) is blocked entirely by modern browsers. Passive mixed content (images, video) is either auto-upgraded to HTTPS or blocked, depending on the browser.
Why does my site show a mixed content error after installing SSL?
Because installing an SSL certificate secures the connection between the browser and the server but does not update the URLs stored in your content. Every image URL, link, and resource reference that was written as http:// before the SSL installation remains http:// after it. The browser loads the HTTPS page from your server, then sees all those http:// resources and either blocks them or warns about them. The fix requires finding and updating every http:// URL in your database, template files, CSS, and JavaScript to use https:// instead.
What is the difference between active and passive mixed content?
Active mixed content covers resources that can execute code or significantly modify page behavior: JavaScript files, CSS stylesheets, fonts, iframes, and XMLHttpRequest or fetch calls. Browsers block active mixed content entirely because a compromised script can take over the entire page. Passive mixed content covers resources that render without executing code: images, audio, and video. Browsers historically loaded passive mixed content with a warning, but modern Chrome auto-upgrades these to HTTPS and blocks them if the HTTPS version is unavailable.
How do I fix mixed content in WordPress?
The complete fix involves four steps. First, update WordPress Address and Site Address in Settings to https://. Second, use the Better Search Replace plugin to do a database-wide replace of http://yourdomain.com with https://yourdomain.com across all tables. Third, search your theme files for hardcoded http:// references using SSH grep or your hosting file manager and update them manually. Fourth, add the Content-Security-Policy: upgrade-insecure-requests header to catch any remaining or future insecure resource references. Clear all caches after each step and verify with browser DevTools that the padlock is restored.
Can I fix mixed content without editing the database?
Partially. Plugins like Really Simple SSL and SSL Insecure Content Fixer dynamically rewrite http:// to https:// in WordPress page output without modifying the database. They work and can restore the padlock immediately. However, they add processing overhead to every page load because they filter output on every request. They are best used as a temporary solution while a proper database cleanup is planned, or for low-traffic sites where the overhead is not a concern. A proper database search-and-replace is the clean long-term resolution.
What is the upgrade-insecure-requests header and should I use it?
The Content-Security-Policy: upgrade-insecure-requests directive tells browsers to automatically try to load any http:// resource as https:// before making the request. If the HTTPS version exists, it loads without the mixed content warning. If the HTTPS version does not exist, the browser blocks the resource. It is a useful safety net but not a substitute for fixing the underlying http:// references because it adds a request overhead for every upgraded resource and provides no protection for resources that do not exist over HTTPS. Use it alongside a full URL cleanup, not instead of one.
Does mixed content affect SEO?
Yes, in two ways. Browsers that show a Not Secure warning or broken padlock signal distrust to visitors, increasing bounce rates on affected pages. Google also considers HTTPS a ranking signal and its crawlers flag pages with mixed content in the Security Issues section of Google Search Console. Pages with mixed content may not receive the full HTTPS ranking benefit. Beyond rankings, users who see the Not Secure indicator are measurably less likely to complete forms, make purchases, or share personal information, so the commercial impact extends well beyond search position.
How do I prevent mixed content from coming back after fixing it?
Three practices prevent recurrence: configure your site’s base URL to https:// so any new content added through the CMS uses HTTPS references by default; use relative URLs for internal asset references in templates (/assets/image.jpg rather than http://domain.com/assets/image.jpg) so they cannot be mixed content; and add the upgrade-insecure-requests Content Security Policy header to auto-upgrade any http:// references that get introduced in the future. For WordPress sites, the Really Simple SSL plugin can stay active as a passive safety net even after the database cleanup is complete.
