Post

From “Not Secure” to Checkout Recovery: An SSL Case Study on a WooCommerce Store After a CDN Switch

Client: Independent home-goods brand, Austin, TX · WooCommerce on a managed WordPress host · ~$14k/mo in transactions.
Symptom window: 27 hours before we were engaged.
Resolution time: 18 hours from first message to green padlock and normal checkout throughput.

The SOS Message

The owner reached out on WhatsApp at 11:47 PM local time. Her store had displayed a red “Not Secure” warning in Chrome for over a day. Firefox showed SEC_ERROR_UNKNOWN_ISSUER. Stripe was silently declining checkout attempts because the payment iframe refused to render inside a page it considered untrusted. The site had processed just three orders in 24 hours — on a normal Tuesday she would have closed 28 to 35.

The trigger was clear from the message thread: her developer had migrated DNS to a new CDN two nights earlier to reduce image bandwidth costs. Everything looked fine for six hours, then browsers started rejecting the certificate as the CDN’s edge cache expired and re-fetched the origin.

Initial Diagnosis (First 20 Minutes)

Before touching anything on the site, I ran an external certificate audit from three independent locations. This matters: browser warnings are only useful if you can reproduce them from a clean, cache-free environment.

openssl s_client -connect shop.example.com:443 -servername shop.example.com -showcerts < /dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -dates
curl -vI https://shop.example.com/ 2>&1 | grep -Ei "SSL|subject|issuer|verify|HTTP/"
nmap --script ssl-cert -p 443 shop.example.com

Three findings surfaced immediately:

  • The leaf certificate presented by the CDN edge was valid for *.example.com, but the intermediate certificate was missing from the chain. Chrome carries a cached intermediate for Let’s Encrypt R3 on many desktops, which is why some users reported “it works for me” — that’s a classic red herring in SSL debugging.
  • curl reported SSL certificate problem: unable to get local issuer certificate from every fresh vantage point.
  • The origin server was still serving the old certificate (issued through the previous host’s AutoSSL), which had actually expired 9 days earlier — nobody noticed because the CDN had been terminating TLS.

Root Cause: Three Failures Stacked

  1. Broken chain of trust at the new CDN. The engineer had uploaded the leaf certificate and private key, but not the intermediate bundle. Modern browsers require the full chain unless the intermediate is preloaded, and mobile clients rarely have it.
  2. Expired origin certificate. With TLS termination at the CDN, nobody was monitoring the origin’s cert. When we later inspected origin traffic to rule out MITM, the origin’s own cert failed validation.
  3. Mixed content on checkout. Once the certificate warning appeared, Chrome upgraded its “mixed content” enforcement on this domain. A third-party analytics script and two hero images were still loaded over http://. The Stripe iframe refused to initialize, so no card form ever appeared.

The 4-Step Fix

1. Rebuild the certificate chain at the edge

We downloaded the correct intermediate from Let’s Encrypt’s current chain page and concatenated it in the correct order:

cat leaf.pem intermediate-R3.pem > fullchain.pem
openssl verify -CAfile isrgrootx1.pem -untrusted intermediate-R3.pem leaf.pem
# expected: leaf.pem: OK

Then re-uploaded fullchain.pem to the CDN’s TLS panel and forced an edge re-deploy. Verification from SSL Labs went from grade F to A within 4 minutes.

2. Reissue and re-install the origin certificate

Even though the CDN terminates TLS, the origin must present a valid certificate for CDN-to-origin fetches. We issued a fresh Let’s Encrypt cert via the host’s cPanel AutoSSL, then flipped the CDN’s origin protocol setting from “HTTP only” to “HTTPS with certificate validation” to prevent the same failure from being invisible next time.

3. Clean up mixed content across the store

Every checkout meltdown deserves a database-level fix, not a plugin band-aid. We ran a scoped search-replace using WP-CLI on a fresh backup:

wp search-replace 'http://shop.example.com' 'https://shop.example.com' --all-tables --precise --skip-columns=guid --dry-run
# review count, then run for real
wp search-replace 'http://shop.example.com' 'https://shop.example.com' --all-tables --precise --skip-columns=guid

Then we added a temporary Content-Security-Policy-Report-Only header to catch any lingering insecure requests without blocking traffic, and reviewed the report endpoint for six hours before switching to enforcement.

4. HSTS with a conservative rollout

Once every subresource resolved cleanly, we turned on HTTP Strict Transport Security — but started with a short max-age. Aggressively preloading HSTS before you have proven cert-renewal automation is how stores end up locked out of their own admin panel later.

Strict-Transport-Security: max-age=86400; includeSubDomains
# revisit in 7 days, extend to 15552000, then consider preload

What the First Customer Saw

Within 22 minutes of the chain fix, the padlock was green in Chrome, Firefox, Safari, and Edge from every vantage point we tested. We placed a $1 test order using a real card and confirmed the Stripe iframe rendered and the transaction cleared. Then we asked the owner to try from her personal phone on a mobile network — the truest test, because mobile Safari does the least caching. It worked on the first try.

The 7-Day Impact

  • Day 1 after fix: 24 completed orders (vs. 3 the day before).
  • Day 2–3: 61 orders, +12% vs. the same days the previous week — some of the recovery was pent-up customers finally getting through.
  • Cart abandonment rate: dropped from 79% (during the outage) back to a baseline of 41%.
  • Search Console: no impression drop; Google had not yet reflected the outage in ranking data, and coverage errors resolved within 48 hours of re-verification.
  • Stripe support ticket the owner had opened was closed by us with a one-line reply explaining the resolved chain issue.

What This Owner Did Right — and Wrong

Right: She kept a full backup taken 6 hours before the CDN switch. That let us test the search-replace safely on a clone before touching production data.

Wrong: Cert monitoring was only pointed at the domain, not at the origin IP. When TLS is terminated at a CDN, you need two monitors — edge and origin — because a silent origin expiry will bite you the moment your CDN configuration changes.

Prevention Checklist (Save This)

  • Always upload the full chain (leaf + intermediate) to any TLS termination point. Never assume the browser will fill it in.
  • Verify with openssl s_client -showcerts from a machine that has never visited the site, not from your daily browser.
  • Monitor both edge and origin certificates independently — UptimeRobot, Better Uptime, or a cron’d openssl script on a separate host all work.
  • Before any CDN or DNS change on a live store: take a database snapshot, note current SSL Labs grade, and screen-record a full checkout so you have a rollback baseline.
  • Roll out HSTS with short max-age first. Preload only after 30 days of clean renewals.
  • Run a scoped WP-CLI search-replace to catch http:// URLs in serialized post meta and widget options — a plugin sweep will miss those.

When to Call for Help Instead of Reading Another Tutorial

SSL problems are among the fastest-escalating failures a WordPress store can hit. Every hour the padlock is red is an hour of direct revenue loss and a compounding trust hit that shows up weeks later in repeat-purchase rate. If you are staring at a certificate warning on a store that is actively taking orders, the correct move is to stop reading and get a real human to diagnose it — not to run a plugin that promises to "fix everything."

If your WordPress or WooCommerce site is showing SSL errors right now, message me on WhatsApp for a free 10-minute diagnosis. I typically confirm the exact failure within that window and give you a fixed-price quote before any work starts — no-fix, no-fee.

Leave a Reply

Your email address will not be published. Required fields are marked *