DEV Community

Sandeep Singh
Sandeep Singh

Posted on

HTTPS and Mixed Content on WordPress After Installing SSL

You installed the SSL certificate. Your host says it's active. You visit the site and... the padlock is missing, or the browser warns that parts of the page aren't secure, or some images have vanished, or the slider stopped working. Welcome to mixed content, the most common problem after moving a WordPress site to HTTPS.

It matters beyond looks. Browsers block some insecure resources outright, so features break. Visitors see warnings at exactly the moment you want them to trust you (contact forms, checkouts, sign-ups). And Google's page experience guidance asks whether "your pages are served in a secure fashion" (Understanding page experience).

This guide covers switching WordPress to HTTPS properly, finding every source of mixed content, and making sure it doesn't creep back.

What mixed content is, and why some of it breaks your site

Mixed content means an HTTPS page loads a sub-resource (image, script, stylesheet, iframe, font) over plain HTTP. MDN explains that browsers now split it into two categories (MDN: Mixed content):

  • Upgradable content: browsers "should automatically upgrade requests" from HTTP to HTTPS. This includes <img> with a src attribute, CSS images like background-image, and audio and video. If the file exists at the HTTPS address it loads; if not, you get a missing image.
  • Blockable content: everything else, including scripts, stylesheets, iframes and fonts, which browsers block.

Two details catch WordPress sites out:

  • Images loaded through srcset or <picture> are not in the upgradable list, according to MDN. WordPress uses srcset for responsive images, so old http:// image URLs in srcset can break even when the plain src would have been upgraded.
  • Resources requested from an IP address rather than a hostname aren't upgraded either.

So mixed content produces three kinds of symptoms: a missing padlock or warning, missing images, and broken features where a script, stylesheet or embed was blocked.

Step 1: Make sure HTTPS itself works

Before touching WordPress settings:

  • Visit https://yourdomain.com directly. The certificate should be valid for both the www and non-www names you use.
  • Check the certificate's expiry and that renewal is automatic. Let's Encrypt certificates, which many hosts use, last 90 days by default and are meant to renew automatically (Let's Encrypt FAQ).
  • If you're behind Cloudflare or another proxy, check its SSL mode. A proxy in "Flexible" mode talking to the origin over HTTP while WordPress forces HTTPS is a classic cause of redirect loops.

Step 2: Switch WordPress's own URLs to HTTPS

WordPress stores two URLs in Settings → General: "WordPress Address (URL)" and "Site Address (URL)". Both need to start with https://.

Since WordPress 5.7, Site Health can do this for you. WordPress detects whether the environment supports HTTPS and, if so, shows a button in the HTTPS section of Tools → Site Health that "switches the site from HTTP to HTTPS with a single click" (Make WordPress Core: HTTPS in WordPress 5.7). The underlying function also verifies the change worked and reverts it automatically if WordPress doesn't then recognise the site as HTTPS.

The same release added on-the-fly rewriting of insecure references to your own site's URL in content. That rewriting only applies when certain conditions are met: the site uses HTTPS, the Site Address and WordPress Address use the same domain, and an internal migration flag was set because the URLs were switched on a site with existing content. It's a big help, but it rewrites output rather than fixing the stored data. It also doesn't cover every place a URL can hide: theme files, CSS, third-party embeds, and plugins that build their own output. The core announcement itself notes that advanced configurations aren't supported by the one-click migration.

To force HTTPS in the dashboard, WordPress's documentation describes setting FORCE_SSL_ADMIN in wp-config.php (WordPress: HTTPS):

define( 'FORCE_SSL_ADMIN', true );
Enter fullscreen mode Exit fullscreen mode

Step 3: Redirect all HTTP traffic to HTTPS

Every http:// URL should 301-redirect to its https:// equivalent in one hop, with no chain through http://www → https://www → https://. Many hosts offer a "force HTTPS" toggle; otherwise use a server rule. Check with:

curl -sIL http://example.com/some-page/ | grep -iE "^(HTTP|location)"
Enter fullscreen mode Exit fullscreen mode

You want exactly one 301 and then a 200. Google's site move documentation notes that an HTTP-to-HTTPS move doesn't need the Change of Address tool, but the redirects themselves are essential (Site moves with URL changes).

Step 4: Fix the stored URLs in the database

Old posts, pages, widgets, menus, custom fields and plugin settings often contain hard-coded http://yourdomain.com/... links and image URLs. Fixing them in the database makes the fix permanent rather than relying on output rewriting.

With WP-CLI (take a backup first):

wp db export before-https.sql
wp search-replace 'http://example.com' 'https://example.com' --dry-run
# check the counts, then run without --dry-run
wp search-replace 'http://example.com' 'https://example.com'
Enter fullscreen mode Exit fullscreen mode

WP-CLI's documentation notes that search-replace "intelligently handles PHP serialized data", which is why it's safer than raw SQL for WordPress options and widgets (WP-CLI: search-replace). Repeat for http://www.example.com if your old content used it. Many hosts and migration plugins offer an equivalent tool if you don't have shell access.

Step 5: Hunt down what's left

After the database fix, remaining mixed content usually comes from:

  1. Theme files: hard-coded http:// in header.php, footer.php or template parts. Search the theme folder for http://.
  2. CSS files: background-image: url(http://...) in theme or custom CSS, including the Customizer's "Additional CSS".
  3. Page builder data: some builders store CSS or URLs in their own tables or files; regenerate their CSS after the database change if they offer that option.
  4. Third-party embeds: old video, map, form or widget embed codes using http://. Replace them with the provider's current HTTPS embed code.
  5. External images: images hotlinked from other sites over HTTP. If the other site doesn't support HTTPS, host the image yourself.
  6. Plugin settings: logo URLs, tracking scripts and custom code fields in plugins.
  7. Hard-coded IP addresses: these aren't upgraded by browsers at all.

How to find them

  • Browser developer tools: open the Console on a page; browsers log mixed content warnings and blocked requests with the exact URL.
  • View source and search for http://.
  • Crawl the site: a crawler that reports insecure resources finds mixed content across all pages, not just the ones you thought to check. Screaming Frog's free version (up to 500 URLs; limits) can flag HTTP links and insecure resources found on HTTPS pages, with the page each one appears on.

WordPress-specific gotchas

A few situations trip people up even after following the steps above:

  • Greyed-out URL fields. If "WordPress Address" and "Site Address" can't be edited in Settings, they're probably defined as WP_HOME and WP_SITEURL constants in wp-config.php. Change them there instead.
  • Caching. Page caches, CDN caches and minified CSS bundles can keep serving the old http:// versions for hours or days. Clear every cache layer after the switch, including any your host runs.
  • Multisite. Each site in a network has its own URLs, and the one-click migration doesn't support every advanced configuration. Plan the change per site.
  • Email and form links. Transactional email templates and form confirmation messages often contain hard-coded links. Search plugin settings, not just posts.

Step 6: Lock it in (optional, carefully)

Two headers can stop regressions, but both need care:

  • Content-Security-Policy: upgrade-insecure-requests tells browsers to request all of the page's HTTP sub-resources over HTTPS (MDN: upgrade-insecure-requests). It's a useful safety net, but it hides problems rather than fixing them, and a resource with no HTTPS version still fails.
  • Strict-Transport-Security (HSTS) tells browsers to only use HTTPS for your domain for a period set by max-age (MDN: Strict-Transport-Security). Start with a short max-age and only extend it, or add includeSubDomains or preload, once you're sure every subdomain supports HTTPS. Mistakes here are hard to undo.

Step 7: Re-check, then keep checking

After the switch:

  • Crawl the site again; there should be no HTTP resources on HTTPS pages and no internal links to http:// URLs.
  • Check canonical tags, og:image and sitemap URLs now use https://.
  • Look at Search Console over the following weeks for crawl errors or indexing drops.

Mixed content tends to come back: a new embed pasted into a post, a plugin that stores an http:// logo URL, an old page restored from a revision. That's why a recurring check matters more than a one-time cleanup. I covered the store-owner version of this, including broken images, in Mixed Content and Missing Images: A Practical Fix Guide for Store Owners. SSL renewal is one of the yearly items in The Website Maintenance Checklist for Small Business Websites.

Disclosure: I build Uchik, which can handle the "keep checking" part. Uchik crawls your site every day and emails a report that includes HTTPS mixed content alongside broken links, missing images and redirect chains, each tied to the page and HTML element to fix. The features page lists the checks, How it works explains the crawl, and the sample report shows a mixed-content finding. It reads server-delivered HTML, so resources added only by JavaScript may be missed; the browser console is still the final check for those. You can sign up for free.

Checklist

  • [ ] Certificate valid for all hostnames; auto-renewal confirmed
  • [ ] Proxy/CDN SSL mode compatible (no Flexible-mode loops)
  • [ ] WordPress Address and Site Address on https:// (Site Health one-click or Settings)
  • [ ] FORCE_SSL_ADMIN set
  • [ ] Single-hop 301 from every HTTP URL to HTTPS
  • [ ] Database search-replace done with backup and dry run
  • [ ] Theme files, CSS, page builder data, embeds, plugin settings checked
  • [ ] No srcset or IP-address resources over HTTP
  • [ ] Canonicals, og:image, sitemap on HTTPS
  • [ ] Optional: upgrade-insecure-requests; HSTS with a short max-age first
  • [ ] Recurring crawl for mixed content and HTTP links

The certificate is the easy part. Making every image, script and link on the site follow it is what earns the padlock.

Top comments (0)