Mixed content is the odd one out among SSL problems. There is no warning page to click through, no error code to search for, and nothing wrong with your certificate. The page loads, the address bar looks normal, and yet something on it is missing: a logo that will not appear, a slider that never starts, a checkout button that does nothing, a layout that has lost its styling.
That happens because your page arrived over HTTPS but asked for one of its own pieces over plain HTTP, and the browser refused to let that piece through. This guide explains what browsers do with mixed content today, how to find the exact URLs responsible, and how to fix them at the source instead of papering over them.
Quick answer:
Mixed content happens when a page served over HTTPS requests a resource over plain HTTP. It is not a certificate fault and it produces no error page. Browsers block scripts, stylesheets, iframes, fonts and similar resources outright, and silently rewrite HTTP requests for images, audio and video to HTTPS (Chrome since version 80 and 86, Firefox since 127, Safari since 18). If the HTTPS version does not exist, the resource is dropped rather than loaded insecurely.
To fix it, open the browser console to list the offending HTTP URLs, then correct them where they are written: in your templates, in your database, or in the plugin or third-party tag that inserted them.
What mixed content is
A page has mixed content when its HTML is delivered over an encrypted HTTPS connection, but the page then asks for other files over an unencrypted HTTP connection. The document is protected. The images, scripts or stylesheets it pulls in are not.
The reason browsers treat this so strictly is that the weakest connection sets the security of the whole page. An attacker positioned on the network can read or rewrite anything that travels over HTTP. Swapping an image is a nuisance. Swapping a script is a takeover, because a script can read your session, alter the page, capture what visitors type into a form, or redirect them elsewhere. The lock in the address bar would still be there while it happened, which is precisely why browsers stopped allowing it.
Specifications and browsers sort mixed content into two groups. The older names were passive and active. The current names describe what the browser does with each group.
Upgradeable mixed content (formerly passive)
Media that is displayed but cannot change the rest of the page:
- Images loaded through the
srcattribute of an<img>element - Images referenced from CSS, such as a background image
- Audio and video loaded through
<audio>,<video>or a<source>element
Blockable mixed content (formerly active)
Everything else, because everything else can influence the page:
- Scripts, including worker and service worker scripts
- Stylesheets and web fonts
- Frames and iframes
- Requests made by
fetch()andXMLHttpRequest, plus EventSource and beacon endpoints - Form submissions targeting an HTTP address
- Objects, embeds, web app manifests, favicons and subtitle tracks
- Responsive images, meaning anything requested through
srcsetor a<picture>element
That last item catches people out. A plain image is upgradeable, but the same image delivered through a responsive image set counts as blockable and is not upgraded. If one of two visually identical images disappears and the other does not, this is usually why.
Save 10% on SSL Certificates when ordering from SSL Dragon today!
Fast issuance, strong encryption, 99.99% browser trust, dedicated support, and 25-day money-back guarantee. Coupon code: SAVE10
What browsers do with mixed content now
Advice written before 2021 usually says that browsers merely warn about mixed content and that older browsers let it through. Neither describes what happens now. Every current engine both blocks and upgrades by default, so an HTTP resource on an HTTPS page is either fetched securely or not fetched at all, apart from the narrow exceptions described below.
Blockable resources are blocked
A script, stylesheet, iframe or font requested over HTTP from an HTTPS page is not fetched at all. The browser cancels the request and writes an error to the console. Nothing is retried and nothing is substituted. Whatever that file was responsible for simply does not happen, which is why a blocked stylesheet produces an unstyled page and a blocked script produces a feature that appears on screen but does not respond.
Upgradeable resources are rewritten to HTTPS, or dropped
For images, audio and video the browser does not block the request. It rewrites the URL from HTTP to HTTPS before sending it, without asking. This is called autoupgrading, and the important part is what happens next: if the server has no HTTPS version of that file, or the certificate on that host is not valid, the browser does not fall back to HTTP. The resource is not loaded at all.
The behaviour shipped separately in each engine:
- Chrome and Chromium browsers: audio and video from version 80 (February 2020), images from version 86 (October 2020). Chrome Platform Status lists both as enabled by default on desktop, Android and WebView.
- Firefox: images, audio and video from version 127 (June 2024). Everything else is treated as blockable.
- Safari: images, audio and video from Safari 18 (September 2024), following the same Mixed Content Level 2 rules.
Chromium leaves two gaps in the upgrade, and both are worth knowing because the resource then stays on HTTP and really does load insecurely. It does not upgrade a URL whose host is a bare IP address, on the grounds that a certificate for an IP address may not exist, and it does not upgrade local network requests. In both cases the console says so explicitly.
The padlock usually will not tell you
Older guides promise a broken padlock, a yellow triangle, or a “Not secure” label as the giveaway. Expect none of that, because the indicators were built for a world where insecure resources still loaded.
Chrome and Firefox both key their connection indicator on whether insecure content actually loaded, not on whether the page asked for any. Autoupgrading means it usually did not: the request was rewritten to HTTPS, or it failed. Either way nothing insecure was loaded, so an ordinary lock is the correct report. The indicator still downgrades when something insecure genuinely does load, which after autoupgrading means the visitor deliberately allowed it or the resource fell into one of the exceptions above.
This makes mixed content harder to notice than it used to be, not easier. Nothing in the interface flags it. You have to look in the console.
What you actually see
Because there is no error page, mixed content is usually reported as something else. The common symptoms are:
- An image, logo or background that is missing on the live site but fine in the editor or on staging
- A page that renders as raw unstyled text, which points at a blocked stylesheet
- A slider, map, chat widget, video embed or cookie banner that never appears or never responds
- A form that submits to nowhere, or an analytics or ad tag that stops recording
- Something that works on one page of the site and not on another, because only one of them carries the hardcoded HTTP URL
The reliable signal is in the browser console, where every affected request is logged individually.
How to find the URLs causing it
You cannot fix mixed content until you know exactly which URLs are involved and which file writes them. Work through these in order.
1. Read the browser console
Open the affected page and press F12, or Ctrl+Shift+I on Windows and Linux, or Cmd+Option+I on macOS. You can also right-click the page and choose Inspect. Select the Console tab and reload the page, since messages are only recorded while the page is loading.
A blocked resource is logged as an error and names the file that was refused:
Mixed Content: The page at 'https://example.com/pricing/' was loaded over HTTPS,
but requested an insecure script 'http://cdn.example.net/js/widget.js'.
This request has been blocked; the content must be served over HTTPS.
An upgraded resource is logged as a warning instead. This one is easy to skim past, but it still marks a URL you need to correct:
Mixed Content: The page at 'https://example.com/pricing/' was loaded over HTTPS,
but requested an insecure element 'http://example.com/images/banner.jpg'.
This request was automatically upgraded to HTTPS, For more information see
https://blog.chromium.org/2019/10/no-more-mixed-messages-about-https.html
And where Chrome declines to upgrade, it names the reason:
Mixed Content: The page at 'https://example.com/pricing/' was loaded over HTTPS,
but requested an insecure element 'http://203.0.113.10/logo.png'.
This request was not upgraded to HTTPS because its URL's host is an IP address.
Filter the console on the word Mixed to isolate them. Each message ends with a source link that jumps to the line of HTML, CSS or JavaScript that requested the file, which is the fastest way to identify what to edit.
2. Check the Issues tab and the Security panel
Chrome DevTools groups these findings in the Issues tab, which lists mixed content problems by type with the affected resources under each, and is easier to read than a long console when a page has many of them.
The Security panel shows the same information from the connection side. If you cannot see it, open the DevTools command menu with Ctrl+Shift+P or Cmd+Shift+P, type “security”, and choose Show Security. It reports whether the main origin is secure, whether the page interacted with non-secure origins, and offers a View requests in Network panel link that filters straight to the requests concerned.
3. Crawl the whole site
The console only describes the page in front of you. Mixed content is rarely confined to one page, because it usually comes from a template, a widget, or a batch of old posts, so check the site as a whole before declaring it fixed.
JitBit’s SSL Check is a free hosted crawler that walks an HTTPS site and reports insecure images, scripts and stylesheets. Any general site crawler will do the same job if you search the crawled HTML for the string http://. If you have shell access, grepping the source is often quicker than crawling:
grep -rn "http://" wp-content/themes/your-theme/ wp-content/plugins/ --include="*.php" --include="*.css" --include="*.js"
Ignore matches inside comments, XML namespaces and schema declarations such as http://www.w3.org/1999/xhtml. Those are identifiers, not requests, and they load nothing.
4. Let a report-only policy collect the URLs for you
On a large site, the most complete inventory comes from real visitors. A Content Security Policy in report-only mode changes nothing about how the page behaves, and posts a JSON report whenever the page requests something the policy would have refused. Send two headers, because the endpoint named in report-to has to be defined separately:
Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"
Content-Security-Policy-Report-Only: default-src https:; report-uri https://example.com/csp-report; report-to csp-endpoint
The first header is not optional, and leaving it out fails silently. A browser that understands report-to ignores report-uri entirely, so a policy that names an endpoint which was never defined gives that browser nowhere to deliver to, and you collect nothing at all. Keeping report-uri alongside it covers older browser versions that never implemented the newer directive.
Be aware that default-src https: reports more than mixed content, since inline scripts and styles and data: URLs violate it too. Filter the reports down to those whose blocked URL starts with http and the rest is noise you can ignore. Leave the policy running for a few days and it will surface the pages your own crawl missed, including URLs injected by scripts after the page loads.
How to fix mixed content
Every genuine fix is the same fix: change the URL so the resource is requested over HTTPS. What differs is where the URL is written.
1. Confirm the resource is available over HTTPS
Before changing anything, check that an HTTPS version exists. Copy the URL from the console into a new tab and change http:// to https://, or test it from the command line:
curl -sI https://cdn.example.net/js/widget.js
A 200 response means you can go ahead. A certificate warning in the browser, or a connection failure from curl, means the file is genuinely not available securely, and editing the URL will only trade one broken resource for another. Skip to step 5 for that case.
2. Change the URL in the source
Edit the template, stylesheet or content that contains the URL, save it, and redeploy if your workflow requires it. The change itself is usually one character:
<!-- Before -->
<img src="http://example.com/images/banner.jpg" alt="Banner">
<script src="http://cdn.example.net/js/widget.js"></script>
<!-- After -->
<img src="/images/banner.jpg" alt="Banner">
<script src="https://cdn.example.net/js/widget.js"></script>
For files on your own domain, prefer a root-relative path like the one above. It starts with a single slash, inherits whatever scheme the page is using, and cannot drift back to HTTP when you move the site between environments. For files on someone else’s domain you need the full HTTPS address.
Do not reach for protocol-relative URLs written as two slashes and no scheme. They were a workaround from the period when sites answered on both HTTP and HTTPS, they behave unpredictably when a file is opened locally, and a root-relative path or an explicit HTTPS URL covers every case they were meant to solve.
Then reload the page you found the problem on and check the console again. The message should be gone, not merely different.
3. Replace hardcoded HTTP URLs in a WordPress database
Editing files by hand stops being practical at a few dozen URLs, and on a WordPress site most of them are not in files at all. They sit in the database, inside post content, widget settings, page builder data and plugin options, written years ago when the site ran on HTTP.
Start with the site address, because a stale value here reintroduces HTTP URLs no matter what else you clean up:
wp option get home
wp option get siteurl
Both should already be HTTPS. In the admin they are the WordPress Address and Site Address fields under Settings, then General.
Then rewrite the rest. WP-CLI is the safest tool for this because it understands PHP serialized data, which a plain SQL find and replace will corrupt in page builder and widget records. Always take a database backup first and always run the dry run first:
wp search-replace 'http://example.com' 'https://example.com' --dry-run --skip-columns=guid
Read the report it prints, confirm the number of replacements looks sensible, then run it for real by dropping the flag:
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid
Two details matter. Skip the guid column, because those values are permanent identifiers for feed readers rather than links, and changing them makes every old post look new. Add --all-tables only if a plugin stores data in tables outside the standard WordPress prefix, since it widens the operation considerably.
Without shell access, the same work can be done through a plugin or a script:
- Really Simple Security, formerly Really Simple SSL, handles the redirect and site address side. Its mixed content scanner and fixer is a paid feature, and it rewrites requests as the page is served rather than correcting what is stored, so treat it as cover while you clean the database rather than as the cleanup itself.
- InterconnectIT’s Search Replace DB script does the same job as WP-CLI over FTP. It can break a site outright, so back up first, rename its folder to something unguessable, run a dry run before a live run, and delete the folder the moment you are finished. Leaving it on a public server hands anyone who finds it full control of your database.
If you are working through a full move rather than a stray URL, our guide on how to migrate a WordPress site to HTTPS covers the surrounding steps.
4. Replace hardcoded HTTP URLs on other platforms
The principle carries across. Find where absolute URLs are stored, replace the scheme, and be careful with any column that holds serialized or JSON data, because a naive string replacement changes the text without updating the length prefixes that PHP serialization records, and the value stops being readable.
Also check the places that are not really content: theme and template files, stylesheets referencing background images, JavaScript that builds URLs at runtime, email templates, and the ad, analytics and tag manager snippets pasted into the header years ago. Third-party tags are a frequent source, and they are easy to miss because they insert their URLs after the page has loaded.
5. Deal with resources that have no HTTPS version
Sometimes the file genuinely is not served over HTTPS, which is common with images hosted on external services that have been left to age. You have three options, in order of preference.
- Ask the provider to enable HTTPS, or move to a provider that already has. For a paid service this is a reasonable request in 2026.
- Host the file yourself. Download it, upload it to your own server, and point the page at the local copy, provided you have the right to do so. Hosting your own images is the better arrangement anyway, since it removes a dependency you do not control.
- Remove the reference. A file that cannot be served securely is already not reaching your visitors, so deleting the reference costs nothing and stops the console message.
6. Treat upgrade-insecure-requests as a stopgap, not a cure
The upgrade-insecure-requests Content Security Policy directive tells the browser to rewrite the site’s insecure URLs to HTTPS before any request goes out. Unlike the browser’s built-in autoupgrade, it covers blockable resources too, so it can bring back a blocked script or stylesheet.
Send it as a response header. On Apache:
Header always set Content-Security-Policy "upgrade-insecure-requests"
On Nginx:
add_header Content-Security-Policy "upgrade-insecure-requests" always;
If you cannot set headers, the same directive works from a meta tag placed in the document head:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
Now the honest part. This hides the problem rather than solving it, and it is worth being clear about why before you rely on it:
- The bad URLs are still in your content. Every one of them is still stored in your database and your templates, waiting to reappear the moment the header is dropped, a caching layer strips it, or the content is reused somewhere else.
- It silences your best diagnostic. Once requests are upgraded before they are made, the console stops naming them, so nobody finds out that new HTTP URLs are being added.
- It cannot conjure a certificate. If the target host has no working HTTPS, the upgraded request fails and the resource stays missing, exactly as before.
- It does not cover navigation to other sites. Ordinary links pointing at HTTP addresses on third-party domains are deliberately left alone, so those still send visitors over an unencrypted connection.
- It is not a substitute for HSTS. Someone arriving at your site over HTTP has already made an insecure request before any policy of yours is read. That is what HTTP Strict Transport Security is for, and the two solve different problems.
Used deliberately it has a place: switch it on to stop a site looking broken while you work through the real URLs, and keep a report-only policy running alongside so you can still see what remains. Just do not file it as done.
One related directive to avoid. block-all-mixed-content is obsolete in the specification and deprecated in browsers. It was designed to stop upgradeable content loading insecurely, and since that content is now upgraded anyway it has nothing left to do. It is also ignored whenever upgrade-insecure-requests is present, so setting both achieves nothing beyond confusing the next person to read your configuration.
What does not fix mixed content
Mixed content attracts advice borrowed from certificate errors, which are a different class of problem entirely. None of the following can change the outcome, because the browser’s decision is based purely on the scheme of the URL your own page requested.
- Clearing the browser cache or cookies. The decision is made fresh on every request, before anything is fetched. A cleared cache changes nothing, though it can make the symptom look intermittent, which sends people down the wrong path.
- Disabling extensions or trying incognito mode. Mixed content blocking is core browser behaviour, not something an extension adds, and it applies identically in a private window.
- Correcting the system clock. A wrong clock breaks certificate validation and produces a full warning page. Mixed content involves no certificate check on your page at all, since your page already loaded successfully.
- Reissuing, reinstalling or buying a different certificate. Your certificate is working. That is what put the page on HTTPS in the first place and created the conditions for the block.
- Switching browsers. Chrome, Firefox, Edge and Safari all follow the same rules about which resources are blocked and which are upgraded, so a resource that is missing in one is almost always missing in the rest. Where they differ is only in the edge cases, such as the bare IP address exception that Chromium applies and Firefox does not, and that is a difference in which broken thing you see rather than a way to avoid the problem.
- Turning the protection off in the browser. Chrome does expose an Insecure content permission under a site’s settings, set to Block by default, and switching it to Allow will make the missing resource appear. Do not do this, and never advise a visitor to. It restores exactly the situation the block exists to prevent, letting anyone on the network alter a script that runs inside your authenticated session. It also fixes nothing for anybody else, since every other visitor still sees the broken page. If you use it at all, use it for one minute to confirm a diagnosis, then set it back.
If the page is not yours, there is nothing useful to change at your end. The HTTP URLs are written into the site’s own pages, so only the site owner can correct them.
How to stop mixed content coming back
Mixed content is a recurring condition rather than a one-time incident, because every new post, plugin and marketing tag is another chance to introduce an HTTP URL.
- Leave a report-only Content Security Policy in place permanently. It costs nothing and tells you the day something new appears.
- Use root-relative paths for your own files so nothing has to be rewritten when the site moves.
- Re-check after anything that touches markup in bulk: a theme change, a page builder update, a migration, a new tag manager container.
- Look at the console when you publish. One reload after a new page goes live catches most of it.
- Scan the site periodically with the free SSL Checker to confirm the certificate and chain are healthy, and with a crawler to confirm no HTTP resources have crept back in. The two checks answer different questions and you want both.
Frequently Asked Questions
No. Your certificate is valid and your page loaded over HTTPS, which is why the browser is willing to enforce anything at all. Mixed content is a content problem: some of the URLs written into your own pages still start with http. Reissuing or reinstalling the certificate has no effect on it.
Because the browser dealt with the insecure resource quietly. It either upgraded the request to HTTPS or refused to make it, and in both cases nothing insecure was loaded, so the connection indicator is accurate in reporting a secure page. Browsers downgrade that indicator only when insecure content has actually run or been displayed, and after an upgrade none has. The evidence is in the console, not the address bar.
Passive content, now called upgradeable, is media that is displayed but cannot alter the page: images, audio and video. Browsers rewrite those requests to HTTPS automatically. Active content, now called blockable, is anything that can influence the page, such as scripts, stylesheets, iframes, fonts and fetch requests. Browsers refuse those outright, because a tampered script can take over the entire page.
Check three things. The host may not actually serve that file over HTTPS, in which case the upgraded request fails and nothing is loaded, since browsers never fall back to HTTP. The image may be delivered through srcset or a <picture> element, which counts as blockable and is not upgraded even though a plain image would be. Or the HTTP URL may be reintroduced after page load by a script or a caching layer, which the network panel will show you.
It makes the symptom disappear without repairing the cause. The directive rewrites your insecure URLs to HTTPS at request time, so the page renders correctly again, but the HTTP URLs remain in your database and templates and the console stops reporting them. It also cannot help when the target host has no HTTPS at all. Use it to buy time while you correct the URLs, not instead of correcting them.
No. Allowing insecure content lets an unencrypted script run inside an encrypted page, which is the scenario the block was built to prevent, and anyone on the network between you and that server can modify what runs. It also changes nothing for your visitors, who still see the broken page. As a diagnostic step for a moment it is defensible. As a fix it is not.
Not through a specific penalty, but through consequences that matter. Blocked stylesheets and scripts change how the page renders for crawlers as well as visitors, blocked images cannot be indexed, and broken functionality affects the behavioural and performance signals that are measured. The relationship between SSL and SEO is worth understanding here, since the fix is the same work either way.
It is the least dangerous kind, but it is not harmless. An attacker who can rewrite an image can swap a product photo, replace a diagram with something offensive, or move a button, and can track which pages a visitor is reading from the insecure requests alone. In practice the images are also simply missing, since the upgrade fails whenever the host has no HTTPS. It is the same edit as any other mixed content fix.
Not in any way you can design around. Active mixed content has been blocked by default in every major browser for over a decade, and upgrading of images, audio and video arrived in Chrome 80 and 86, Firefox 127 and Safari 18. A handful of narrow exceptions survive, such as Chromium declining to upgrade a bare IP address, but they are exceptions rather than a general tolerance for insecure resources. Guidance suggesting you should account for browsers that display insecure resources describes the web as it was before 2020.
For related problems, see our guides on the “Not Secure” browser warning and on how to switch a site from HTTP to HTTPS, or browse the full set of tutorials on fixing SSL errors.
Save 10% on SSL Certificates when ordering from SSL Dragon today!
Fast issuance, strong encryption, 99.99% browser trust, dedicated support, and 25-day money-back guarantee. Coupon code: SAVE10

