Mixed Content Errors: Why They Happen and How to Fix Them
Practice4 minInnovialabs editorial team
In short
Mixed content means an HTTPS page loads scripts, styles or images over plain HTTP; find them in DevTools, rewrite the URLs to HTTPS in code and in the database, and add upgrade-insecure-requests as a safety net.
What mixed content is
Mixed content happens when a page is served over HTTPS but some of its resources — scripts, stylesheets, images, fonts, iframes, API calls — are still requested over plain http://. The page itself is encrypted, but those requests are not, so an attacker on the same network could read or replace them.
Browsers react in two ways:
- Active content (scripts, stylesheets, iframes,
fetch/XHR requests) is blocked. This is why a site can suddenly lose its styles or a widget stops working after moving to HTTPS. - Passive content (images, audio, video) is usually upgraded to HTTPS automatically by modern browsers. If the resource is not available over HTTPS, it simply does not load, and the padlock may show a warning.
The fix is always the same: every resource must be requested over HTTPS.
Why it appears after moving to HTTPS
- Hardcoded URLs in templates, CSS (
background: url(http://...)) and JavaScript. - CMS databases store absolute links inside posts, settings and widgets. WordPress, for example, saves
http://image URLs directly in post content. - Wrong site URL in config — the app builds links from a base URL that still starts with
http://. - A reverse proxy or load balancer terminates TLS, so the app sees a plain HTTP request and generates
http://links. It needs to trust theX-Forwarded-Protoheader. - Third-party embeds — old counters, widgets and CDNs that were added years ago.
How to find insecure resources
- Open the page, then DevTools → Console. Each blocked or upgraded request is reported as a "Mixed Content" warning with the exact URL.
- Check the Network tab: look at the scheme of every request, including ones triggered after clicks and scrolls.
- Search the codebase:
grep -rn "http://" --include=*.{html,css,js,php,tsx,twig} .
- Search the database export for your own domain with
http://. - For large sites, crawl all pages with a site crawler or collect reports with a report-only Content Security Policy: the browser will report violations without blocking anything.
Content-Security-Policy-Report-Only: default-src https: data: blob: 'unsafe-inline' 'unsafe-eval'; report-uri /csp-report
How to fix it in code
- Replace
http://withhttps://for every resource that supports it. - For your own files, use root-relative paths (
/assets/app.css) — they automatically inherit the page scheme. - Move the base URL into an environment variable and set it to
https://. - Behind a proxy, enable trusted proxy headers in your framework so it knows the original request was HTTPS.
- If a third-party resource has no HTTPS version, replace it or host it yourself. There is no safe way to keep it.
How to fix it in a CMS database
Do not run a plain SQL REPLACE on WordPress tables: some values are serialized PHP arrays that store string lengths, and a blind replace breaks them. Use a tool that understands serialization, such as WP-CLI:
wp db export backup.sql
wp search-replace 'http://example.com' 'https://example.com' --all-tables --dry-run
wp search-replace 'http://example.com' 'https://example.com' --all-tables
Always make a backup first and look at the dry-run output. Then update the site URL settings and clear all caches (page cache, CDN, object cache), otherwise old HTML keeps being served.
upgrade-insecure-requests as a safety net
The CSP directive upgrade-insecure-requests tells the browser to rewrite every http:// subresource request on the page to https:// before sending it. Add it as a response header:
add_header Content-Security-Policy "upgrade-insecure-requests" always;
Or as a meta tag in <head>:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
Keep its limits in mind:
- It does not make a resource available over HTTPS. If the server has no HTTPS version, the request fails.
- It does not upgrade links to other sites that users click.
- It hides problems instead of fixing them, so treat it as insurance, not as the fix.
After cleaning up, add HSTS so browsers always use HTTPS for your domain.