# Mixed content: finding and fixing http:// leftovers after HTTPS

> Moved to HTTPS and lost the padlock, a stylesheet or a slider? Find every http:// resource the page still loads, fix it at the source in the database and theme, and add a safety net for the rest.

Updated 2026-09-25 · Security · HTML version: https://getreport.app/guides/mixed-content-after-https

The certificate is installed, the address bar says `https://`, and yet the padlock is missing, the layout looks unstyled on one page, or the image slider has stopped. The usual cause is mixed content: a page served over HTTPS that still asks for some of its files over plain `http://`. The addresses sit in old posts, theme settings and stylesheets, written before the move. This guide shows how to list them, where they hide, and how to fix them at the source so they do not come back.

## Quick answer

- Run the [SSL / TLS checker](https://getreport.app/tools/ssl-check) and open the mixed content finding. It lists every `http://` script, stylesheet, frame, image and media file in the page source.
- Open the page with DevTools → Console for what the source does not show (CSS `url()`, `srcset`, script-loaded files).
- Replace `http://your-domain` with `https://your-domain` in the database (WordPress: `wp search-replace`), then in theme files and plugin settings.
- For third-party files, switch to their `https://` address, or host a copy.
- Add `Content-Security-Policy: upgrade-insecure-requests` as a safety net, not as the fix.

## Why mixed content matters

Browsers split mixed content into two kinds, and treat them very differently.

**Active content** can change the page: scripts, stylesheets, iframes, fonts and requests made from JavaScript (`fetch`, XHR). A network attacker who can rewrite an `http://` script controls the whole page, so browsers block these outright on an HTTPS page. That is where the visible damage comes from: a blocked stylesheet leaves the page unstyled, a blocked script kills the menu or the slider, a blocked iframe leaves a blank box where the map or the payment form should be.

**Passive content** is images, audio and video. The worst an attacker can do is swap the picture. Chrome and Firefox now try the `https://` version of these automatically and show nothing if that fails. When an `http://` image does load, the browser stops showing the page as fully secure.

Forms are a third case. A form on an HTTPS page whose `action` points to `http://` makes Chrome warn the visitor before submitting that the information is not secure. On a contact or checkout form, that warning alone loses the submission.

The broken layout and the missing padlock are what every visitor sees, and they are usually the first thing people notice after a move to HTTPS.

## How getReport checks it

> **Free tool:** [SSL / TLS checker](https://getreport.app/tools/ssl-check): Certificate expiry and chain, TLS versions, HTTPS enforcement, HSTS and mixed content for any site — the checks that stand between your visitors and a browser warning.

The tool fetches the page, reads the HTML as the server sent it and collects every `http://` address in the attributes that load something:

- **Active**: `<script src>`, `<link rel="stylesheet" href>`, `<iframe src>`, `<object data>`, `<embed src>`.
- **Passive**: `<img src>`, `<video src>`, `<audio src>`, `<source src>`, `<video poster>`, and the `og:image` meta tag.

> **Check: No resources load over http://.** Browsers block http:// scripts and stylesheets on an HTTPS page, which breaks layout or features, and upgrade http:// images and media to https, showing them broken when that fails. It usually appears after a move to HTTPS while old absolute URLs stay in the content.
>
> 1. Search and replace http:// with https:// in content, theme and configuration (WordPress: a search-replace plugin covers the database).
> 2. Add Content-Security-Policy: upgrade-insecure-requests as a safety net.

![The SSL and TLS check result on a page served over plain HTTP: the site is served over HTTP, not HTTPS, as the failing finding, the missing HSTS header as a warning, and the score ring; on an HTTPS page with leftovers the mixed content finding appears in the same list with the http:// addresses](https://getreport.app/guides/img/mixed-content-after-https/finding.webp "The mixed content finding only appears on HTTPS pages; on an HTTP page the check is skipped, and the first fix is the redirect.")

The finding fails whenever the list is not empty, with one exception: when the only `http://` address is the `og:image`, it is a warning. Browsers do not load that image on the page, but social networks may fail to show it in link previews.

What it does not see: `srcset` candidates, `url()` inside stylesheets and `style` attributes, files requested by JavaScript after load, and form `action` addresses. For those, use DevTools (step 1).

The same tool checks the surrounding setup, because mixed content is usually one symptom of an incomplete move:

> **Check: The site is served over HTTPS.** Browsers label HTTP pages "Not secure" and Google uses HTTPS as a small ranking signal. Anyone on the network can read or alter what visitors see.
>
> 1. Get a certificate (Let's Encrypt is free) and enable HTTPS on your host.
> 2. Redirect every http:// URL to https:// with a 301.

> **Check: http:// redirects to https://.** Visitors who type your address without https, or follow an old link, land on the unencrypted page. Every one of those visits can be read or altered on the network.
>
> 1. Redirect every http:// URL to the same path on https:// with a 301.
> 2. nginx: return 301 https://$host$request_uri; Apache: RewriteRule in .htaccess; most hosts and Cloudflare have a "Always use HTTPS" switch.

> **Check: Strict-Transport-Security header is set.** HSTS tells browsers to always use HTTPS for your site, so after the first visit a typed address or an old http:// link never starts on an unencrypted connection. Only the preload list covers the very first visit.
>
> 1. Send the header: Strict-Transport-Security: max-age=31536000; includeSubDomains.
> 2. Start with a shorter max-age (e.g. 86400) if you are not sure every subdomain supports HTTPS.

## Step by step

### 1. Get the complete list

Start with the finding: it names each address. Then open the page in Chrome with DevTools → Console and reload. Every mixed request produces a message that starts with "Mixed Content:" and says whether it was blocked or automatically upgraded. The Security panel summarises the page's state (it sits under "More tools" if it is not in the tab bar).

Check a sample of page types, not only the home page: an old blog post, a product, the cart and checkout, a landing page built with a page builder. Old content is where most leftovers live.

### 2. Find where each address lives

| Where it hides | Typical example | Fix in |
| --- | --- | --- |
| Post and page content | `<img src="http://example.com/wp-content/uploads/…">` | The database |
| `srcset` in content | Responsive image candidates | The database |
| Theme options and Customizer | Logo, header image, background image | The theme settings screen |
| Stylesheets | `background: url(http://example.com/img/bg.jpg)` | The CSS file |
| Inline styles | `style="background-image:url(http://…)"` | The database or page builder |
| Page builder data | Sections stored as JSON | The builder's own tool |
| CDN or cache plugin settings | CDN address entered as `http://cdn.example.com` | The plugin settings |
| Theme templates | `<script src="http://…">` in `header.php` | The theme files |
| Third-party embeds | An old widget or map loaded from `http://` | The embed code |

### 3. Fix the database with a search-replace

Most leftovers are absolute URLs in content. Replace your own old address with the new one in one pass. On WordPress with WP-CLI, from the site root:

```bash
wp db export before-https.sql
wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --dry-run
wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid
wp search-replace 'http://www.example.com' 'https://www.example.com' --all-tables --skip-columns=guid
wp cache flush
```

The dry run shows how many replacements each table would get; read it before running the real one. WP-CLI handles serialized PHP data safely, which a raw SQL `REPLACE()` does not. `--skip-columns=guid` leaves the post GUIDs alone, as WordPress recommends: they are identifiers, not links. Without shell access, a search-replace plugin does the same from the dashboard; take a backup first.

Replace only your own domain. Blindly changing every `http://` breaks links to sites that do not support HTTPS.

### 4. Fix files and settings by hand

Search the theme for hard-coded addresses:

```bash
grep -rn "http://" wp-content/themes/your-theme --include=*.php --include=*.css --include=*.js
```

Change each one to `https://`, or to a path such as `/wp-content/themes/your-theme/img/bg.jpg` when it points at your own site. Then go through the settings screens that store addresses: the Customizer, theme options, the CDN field in the cache plugin, slider and gallery plugins.

Do not switch to protocol-relative URLs (`//example.com/file.js`). They were a workaround for sites that served both HTTP and HTTPS. On an HTTPS-only site, write `https://`.

### 5. Replace third-party resources that have no HTTPS

Try the `https://` version of the address in a browser. Almost every service supports it today. If one does not, host a copy of the file yourself (for fonts, images, a small script whose licence allows it) or remove the embed.

### 6. Add the safety net

```nginx
add_header Content-Security-Policy "upgrade-insecure-requests" always;
```

With this directive the browser rewrites every `http://` subresource to `https://` before requesting it, so a leftover you missed still loads, as long as the other server supports HTTPS. It does not fix the source, and getReport's check reads the source, so the finding stays until the addresses are changed. If you already have a Content-Security-Policy, add `upgrade-insecure-requests` to it rather than sending a second header; the [Content-Security-Policy rollout guide](https://getreport.app/guides/content-security-policy-rollout) covers the rest of that header.

You may see `block-all-mixed-content` in older guides. It is deprecated: browsers now block active mixed content by default, so it adds nothing.

## Platform notes

### WordPress

- **Settings → General**: both WordPress Address (URL) and Site Address (URL) must start with `https://`. If they are defined in `wp-config.php` as `WP_HOME` and `WP_SITEURL`, change them there; the fields are then greyed out.
- **Really Simple SSL** (now called Really Simple Security) includes a mixed content fixer that rewrites `http://` to `https://` in the HTML as each page is sent. It works as a patch while you clean up, but it hides the problem rather than removing it, and getReport sees the rewritten page, so the finding can pass while the database still holds the old addresses.
- **Elementor** stores page data as JSON, where slashes are escaped (`http:\/\/example.com`), so a plain search-replace misses it. Use Elementor → Tools → Replace URL, then regenerate its CSS from the same screen.
- **WooCommerce**: check the checkout page separately. Payment forms often load in an iframe, which is active content, so one `http://` address there stops payments rather than just losing an image. Product images imported years ago are also frequent leftovers.

### Shopify

Shopify serves every store and its files over HTTPS. Leftovers come from `http://` addresses pasted into rich text, theme Liquid files or custom HTML sections. Search the theme code (Online Store → Themes → Edit code) and the affected content for `http://`.

### Static sites / custom

Search the source before building: `grep -rn "http://example.com" src/ content/`. In templates, build asset URLs from one base address setting so a future move changes one line.

## Verify

- Re-run the SSL / TLS checker. The finding reads "No resources load over http://".
- From a terminal, list any remaining `http://` addresses in the HTML:

```bash
curl -s https://example.com/some-old-post/ | grep -Eo '(src|srcset|href|poster|action)="http://[^"]*"' | sort -u
```

Ignore `href` on ordinary `<a>` links: a link to an `http://` page is not mixed content. `href` on a `<link rel="stylesheet">` is.

- DevTools → Console on the key pages shows no "Mixed Content" messages, and the padlock is back.

For the rest of the move (certificate, redirect, HSTS), see [SSL/TLS checks that matter](https://getreport.app/guides/ssl-tls-checks-that-matter) and the [mixed content learn page](https://getreport.app/learn/mixed-content).

## Common mistakes

- **Relying on the fixer plugin or `upgrade-insecure-requests` alone.** The site looks fixed, but a plugin update or a CSP change brings the warnings back. Change the stored addresses.
- **Replacing only `http://example.com`.** Content written with `http://www.example.com` stays broken. Run the search-replace for each variant.
- **Forgetting the CSS files.** The HTML is clean, but a background image or font in the stylesheet still loads over HTTP. The Console lists it; the page source does not.
- **Running the search-replace without a backup or dry run.** A mistyped domain rewrites thousands of rows. Export the database first and read the dry-run counts.
- **Checking before the caches are cleared.** The database is fixed, but the page cache or CDN still serves the old HTML, so the finding lists the same addresses. Purge the cache plugin and the CDN, then re-run. Once the list is empty, lock the move in with [HSTS, enabled safely](https://getreport.app/guides/hsts-safely-and-the-preload-list).
