← All case studies

Clearing 22 WordPress Vulnerability Warnings on a 2019 Site

The email from the host was a long list in red. Its security scanner had flagged 22 components on a small company’s WordPress site: WordPress core, 19 plugins and 2 themes, several with known cross-site scripting holes. A second email said the hosting account’s API key would stop working on a fixed date.

The owner wasn’t technical and didn’t want an ongoing plan. The question was simple: can this be fixed, once, and what will it cost?

If you’ve had a similar alert from Cloudways, Patchstack, Wordfence or your host, this is the order that cleared it without breaking anything.

What the alert was really saying

From the outside, the site looked fine. Pages loaded, content was intact, and nothing suggested it had been hacked. The real story was age:

  • WordPress 5.1, a 2019 release getting security backports only
  • Avada 5.8.2 with WPBakery 5.2, both from early 2019
  • About 15 plugins, all at 2019 versions
  • Two inactive premium themes still on disk (and flagged), plus duplicate sitemap, slider and page builder plugins
  • None of the four common security headers sent

So the list was long mostly because everything was old, plus a lot of dead weight. The API key email was a separate, account-level change. Nothing connected to the hosting API, so it needed no work.

Why “just click Update” would have broken it

Going from WordPress 5.1 to current, with a 2019 theme, is a big jump: a new jQuery, the block editor, and a newer PHP. Avada has its own migration path from 5.x to 7.x, and Avada 7 needs PHP 8.3.

I tested that on a copy. The old theme and plugins on PHP 8.3 returned an error on every single page. The theme, its builder plugins, WordPress and PHP had to move together, in the right order, and never on the live site first.

The approach: copy, measure, then change

  1. Backups first. A full host backup plus an offsite copy before touching anything, and a fresh one before every live change.
  2. A private staging copy on the same host, password protected.
  3. A baseline of every page. For all 54 published pages I recorded the status code, a fingerprint of the main content text, and the number of images and links. After each change I ran the same check and compared. “Looks the same” isn’t a test. Matching fingerprints are.
  4. Check what’s used before removing it. I searched page content for every shortcode each suspect plugin provides before deleting the plugin.

Clean up, then update in a safe order

On staging I removed what wasn’t in use:

  • 7 unused themes, including the two inactive premium themes from the alert
  • 6 inactive plugins: two sliders, two sitemap plugins, a cache plugin and a mega menu
  • Later, WPBakery and Contact Form 7, once I’d confirmed nothing on the site still used them

Fewer components means a smaller attack surface and less to update later, which suits an owner who doesn’t want a maintenance plan.

Then the updates, in this order: the theme and its builder plugins, WordPress core, then the remaining plugins. Two details mattered:

  • Theme registration. Avada kept rejecting the licence as an invalid purchase code. The cause was one wrong character in the code being typed in. Retyped from the licence certificate, it registered at once.
  • Order of the theme swap. Uploading Avada 7 while the old Fusion plugins were still active crashed the site. The order that works: deactivate the old Fusion plugins, upload the theme, register it, update Avada Core and Builder, then activate them.

The near miss: a plugin that looked unused

One flagged plugin, a withdrawn “shortcode any widget” plugin with a known security hole, looked unused. I’d searched page content for its usual shortcode and found nothing, so I deleted it on staging.

The page comparison caught it straight away: 351 characters and 21 links were missing from 51 of 54 pages. The left-hand services menu had vanished. The pages used the plugin through a different shortcode name, [do_widget], not the one I’d searched for.

The old plugin had no safe version, so I didn’t bring it back. I wrote a small replacement plugin, about 60 lines, that renders only the widgets the pages need, with the same markup as before. The menu came back with the same 21 links and the same classes.

Lesson: when you check whether a plugin is in use, search for what the pages actually contain, every shortcode name it registers, not just the plugin’s name.

Hardening, kept simple

  • Five security headers added: HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy. I left out a Content Security Policy, which can easily break a page builder theme.
  • File editing from the dashboard turned off.
  • Comments closed on all 54 pages. The contact page had an open comment form, which was a spam route.
  • Automatic updates turned on for 10 low-risk plugins. The theme and its builder plugins stay manual, because a page builder update deserves a check on staging first. I told the owner that plainly at handover.

Going live

With the owner’s approval of the staging copy: fresh live backup, push to live, clear caches, then the same check on the live site.

54 of 54 pages were identical to before: same text, same images, same links. The host’s scanner was run again: 0 of 22 components flagged.

Follow-up: PHP 7.4 to 8.3

A few days later the host warned about two critical PHP vulnerabilities. Official fixes covered PHP 8.2 and newer, and the site was still on PHP 7.4. The owner asked for the PHP upgrade as a separate job.

The blocker was the old accordion plugin that drew the services menu. It used PHP syntax that was removed in PHP 8, so it couldn’t run on 8.3 at all. I rebuilt the menu inside the small custom plugin: same colours, row heights, open and close behaviour, and keyboard-friendly buttons.

Before going live, I loaded every page at desktop (1280px) and phone (375px) width on both the test copy and the live site, 108 page loads each, and compared page, header, menu and footer sizes. The result: no broken images, no failed file loads, no script errors and no sideways scrolling, with measurements matching to within 1 to 3 pixels.

The live switch was a few small steps, not a database push: upload the plugin, turn off the two old menu plugins, switch PHP to 8.3, then check again. The site runs on PHP 8.3.36, the patched release.

While in there, I also set proper search titles and descriptions for the 12 main pages, and fixed 7 old internal links that still pointed to pre-WordPress .html addresses and led to 404 pages.

Results

  • 22 flagged components to 0, confirmed by the host’s own scanner
  • WordPress 5.1 to 7.1.3, Avada 5.8 to 7.16, PHP 7.4 to 8.3
  • All 54 pages unchanged after every step, checked by fingerprint, not by eye
  • 7 themes and 11 plugins removed or replaced, 5 security headers added
  • No downtime problems and no rollback needed

If your host just sent you a vulnerability alert

  1. Don’t update everything on live. Take a full backup, then make a staging copy.
  2. Check versions first. A long list on an old site usually means age, not a hack. Look for files you don’t recognise before you assume either way.
  3. Remove before you update. Inactive themes and plugins still count as attack surface. Confirm they’re unused by searching for every shortcode they provide.
  4. Update in order: theme and builder plugins, then WordPress, then the rest. Check what PHP version the new theme needs.
  5. Measure every page before and after, at desktop and phone width.
  6. Rescan after going live, and keep the backup from just before the change.

If your site has a long list of warnings and you’d rather not find out the order the hard way, book a free 20-minute call and I’ll tell you what it will take.

Stuck on a WordPress or WooCommerce problem like this?

I build and debug custom WordPress & WooCommerce functionality for agencies and product teams. Get a free 20-minute code audit.

Book a Free Call