
A broken padlock, a "Not Secure" label, or a shield icon in the address bar means the page itself loaded over a valid HTTPS connection, but at least one resource on that page — an image, a script, a stylesheet, an iframe — was fetched over plain http://. The browser encrypted the main document and then, mid-page, handed a request to an insecure channel anyway. That is mixed content, and browsers flag it because an attacker on the network path can tamper with that one insecure resource even though everything else on the page is protected.
On WordPress specifically, this almost never means the SSL certificate is broken. The certificate is usually fine. What is actually happening is one of two things: hardcoded http:// URLs baked into the wp_posts and wp_options tables from before the site had SSL at all, or a theme/plugin file that hardcodes an absolute http:// URL instead of a protocol-relative or relative path. Chrome and Firefox both actively block mixed active content (scripts, iframes, stylesheets) outright, while mixed passive content (images, video, audio) is usually just flagged with a warning and still loads — which is why some sites look fine but still show the broken padlock.
Open DevTools (F12 or Ctrl+Shift+I), click the Console tab, and reload the page — the browser lists every insecure request it blocked or flagged, with the exact URL attached. A typical line reads: "Mixed Content: The page at https://example.com/ was loaded over HTTPS, but requested an insecure image http://example.com/wp-content/uploads/2019/03/hero.jpg. This content should also be served over HTTPS." That URL is not a guess — it is the literal string stored somewhere in your site, and it tells you exactly what to search for.
Do this on a handful of pages, not just the homepage — old blog posts, product pages built with a page builder, and widgets are the most common offenders since they often stored an absolute URL back when the site had no SSL. Pay attention to whether the flagged domain matches your own site or a third-party asset host; a hardcoded http:// call to a CDN or an old image host needs a different fix (update the plugin setting or CDN URL) than a call back to your own wp-content/uploads directory, which points to the database fix in the next section. If every flagged URL points back to your own domain, that is a strong signal the fix lives entirely in wp_posts and wp_options, not in third-party configuration.
Use WP-CLI's search-replace command, not a raw SQL query, because WordPress stores serialized PHP arrays inside post content, widget settings, and theme options — a plain text find-and-replace on those fields changes the string length without updating the serialized length prefix, which silently corrupts the data. WP-CLI's search-replace is serialization-aware: it deserializes the value, replaces the string inside it, and re-serializes it correctly, so nested arrays and objects survive the replacement intact.
Always run with --dry-run first — it shows exactly how many rows in each table would change without writing anything, so you can sanity-check the count before committing. The --skip-columns=guid flag matters: the guid column is meant to be a permanent, unchanging identifier for each post per the WordPress codex, and rewriting it can affect RSS feed readers and import/export tooling that key off the original guid value. If you manage the site through CloudStick, the dashboard's built-in SSH Terminal lets you run this exact wp-cli command directly against the site without opening a separate SSH client or configuring a key pair first.
Never run a raw MySQL UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://', 'https://') style query to fix this. Page builder content, widget settings, and any serialized PHP array stores its string lengths as part of the serialized format — a raw REPLACE changes the string but not the recorded length, so PHP fails to unserialize the value afterward. The result is broken widgets, missing page builder content, or a blank homepage, and it is often not obvious until well after the change. WP-CLI's search-replace exists specifically to avoid this failure mode.
If mixed content warnings persist after the database replacement, check whether wp-config.php hardcodes WP_HOME or WP_SITEURL with an http:// value — these two constants, when defined, override whatever is stored in the wp_options table and force WordPress to build every URL on the page using that literal string, regardless of what search-replace already fixed.
If either constant is present, edit it to use https:// and save the file — no database change is needed for those two values since they live in the config file, not the database. Once the database and wp-config.php both point to https, add HTTPS enforcement as defense-in-depth so that any stray http:// link a user follows, or any old bookmark, gets redirected instead of silently loading insecure content again. On Apache, add a redirect rule to .htaccess above the WordPress block; on Nginx, add a server block redirect from port 80 to the https vhost. CloudStick issues Free SSL with automatic renewal for every site by default, and because the certificate itself never lapses, half of the usual mixed-content causes — an expired cert forcing a temporary HTTP fallback, or a renewal script failing silently — simply do not happen on a CloudStick-managed server.
The short version: run the DevTools console check on your key pages, fix hardcoded http:// URLs in the database with wp search-replace --skip-columns=guid (dry run first, then live), confirm WP_HOME and WP_SITEURL in wp-config.php use https, and add a site-wide HTTP-to-HTTPS redirect as a safety net. That order matters — fixing the redirect first without fixing the underlying URLs just means every page redirects correctly but still fetches insecure sub-resources once loaded.
Going forward, avoid pasting absolute http:// URLs into new content, theme options, or widget settings even on staging — habits that seem harmless on a test domain get exported into production imports and content migrations later, reintroducing the exact problem you just fixed. Run a quick search-replace dry run after any content migration, domain change, or theme restore as a standard check rather than waiting for a user to report a broken padlock.

