Host Anchor
Performance Audit

Backend Performance Audit

sensible.com.au
Audit Date: 15 March 2026 Hosting: SiteGround VPS Platform: WordPress 6.9 PHP: 8.2.30
10
Issues Found
3
Critical
3
High Priority
4
Medium

Executive Summary

The site was migrated from WP Engine to SiteGround in approximately December 2025, but the migration was never properly finalized. The result is a WordPress installation carrying significant dead weight from its previous host that actively degrades performance on every single page load.

Key findings:

Server Environment

Infrastructure

HostSiteGround VPS
Serverc1131276.sgvps.net
OSLinux 6.12.73
PHP8.2.30 (ZTS, OPcache)
MySQL8.4.8

WordPress

Version6.9 (Current)
ThemeHello Theme Child
Page BuilderElementor Pro 3.35
Active Plugins24
WP Memory768 MB

Resources

Disk Used8.2 GB / 36 GB
Database Size~22 MB
Uploads173 MB
Backups (waste)6.4 GB
Object CacheBroken

Detailed Findings

1. WP Engine Code Still Running on Every Request

Critical

The site was migrated from WP Engine but 5 WP Engine must-use plugins were left behind. These load on every single page request (frontend and backend) and cannot be deactivated through the WordPress admin - they are must-use plugins that load automatically.

The following WP Engine components are still active:

Additionally, the advanced-cache.php drop-in is from WP Engine's cache system, not SiteGround's SuperCacher.

wp-content/mu-plugins/ wpe-cache-plugin/ (WP Engine cache) wpe-wp-sign-on-plugin/ (WP Engine SSO) wpengine-common/ (WP Engine platform) wpengine-security-auditor.php wpe-update-source-selector/ mu-plugin.php (WP Engine common loader) wp-content/advanced-cache.php (WP Engine drop-in) wp-content/object-cache.php (WP Engine Memcached Redux)

2. Object Cache Misconfigured - Silently Failing

Critical - Verified

WordPress has two layers of caching. The first is page caching - saving a complete copy of each page so it can be served instantly to visitors without rebuilding it from scratch. The second is object caching - storing individual pieces of data (settings, menus, widget content, query results) in fast memory so WordPress doesn't have to ask the database for the same information over and over.

Page caching is partially working. I confirmed from external testing that SiteGround's nginx proxy is caching pages for anonymous visitors:

External verification (15 March 2026): Homepage response headers: x-proxy-cache: HIT ← pages are being served from cache x-proxy-cache-info: DT:1 ← treated as dynamic content cache-control: (none) ← no browser caching instructions set x-sg-cache: (absent) ← SiteGround SuperCacher not active age: (absent) ← cache lifetime not reported

This means a regular visitor browsing the site will get a reasonably fast experience because they're receiving a pre-built copy of the page. I measured the difference:

Response time comparison (15 March 2026): Cached page (proxy HIT): 1.1 seconds TTFB Uncached page (cache bypass): 3.2 seconds TTFB ← 3x slower When the proxy cache can't serve a stored copy, the server takes over 3 seconds to build the page from scratch - because every database lookup that should be served from fast memory is hitting the database instead.

Object caching is completely broken. It is configured to use WP Engine's Memcached Redux plugin, pointing to a memory socket that only exists on WP Engine's servers - not on SiteGround.

// wp-config.php (found on server) $memcached_servers = array( 'default' => array( 0 => 'unix:///tmp/memcached.sock', // WP Engine socket - does not exist on SiteGround ), ); // wp-content/object-cache.php // Plugin Name: Memcached Redux (WP Engine's version)

In plain terms: every time WordPress needs a piece of stored data, it tries to grab it from a fast-memory location that doesn't exist, silently fails, and then falls back to querying the database directly. This happens hundreds of times per page load, especially on a site like this one that relies heavily on plugins to build its pages and filter its content.

Who this affects most:

The page cache is masking this problem for casual visitors, but the backend is doing far more work than it should be on every request that isn't served from the proxy cache.

3. WP-Cron Has Not Run Since December 2025

High - Verified

Every scheduled cron event on the site is overdue. The earliest overdue event dates back to 18 December 2025 - nearly 3 months of accumulated, unprocessed scheduled tasks.

I confirmed this externally. When I requested the cron handler directly, it took 7 seconds to respond - a normal response should be near-instant. The long response time indicates the system is attempting to chew through a massive backlog of overdue tasks every time it's triggered:

External verification (15 March 2026): URL: https://sensible.com.au/wp-cron.php Response: HTTP 200 Time: 7.08 seconds ← should be under 1 second A healthy wp-cron.php responds almost immediately. A 7-second response means the system is trying to process months of queued tasks in a single hit.

Root cause identified: The WP Engine mu-plugin (wpengine-common/plugin.php) hooks into every cron request using a filter called cron_request. This filter injects WP Engine's basic authentication headers into the internal HTTP request that WordPress uses to trigger scheduled tasks. On SiteGround, those credentials are meaningless - the cron request either fails or gets rejected because the server doesn't recognize the authentication. In plain terms: every time WordPress tries to run a background task, the old WP Engine code steps in and adds the wrong "password" to the request, causing it to fail silently.

Root cause (from wpengine-common/plugin.php): add_filter('cron_request', 'wpe_cron_request'); → This filter adds WP Engine basic auth headers to every cron loopback request → SiteGround does not use these credentials → The cron HTTP request fails silently, so no scheduled tasks ever execute DISABLE_WP_CRON is set to false in wp-config.php, so cron is not intentionally disabled - it's being broken by the WP Engine filter.

This confirms WP-Cron is broken because of the WP Engine code identified in Finding 1. These two findings are directly linked.

I also attempted to trigger cron manually during the audit by requesting wp-cron.php directly. Even after that request (which took 7 seconds), every scheduled event remained stuck at "now" (overdue) - the system was unable to process a single task. The cron queue is not just behind, it is completely jammed.

Server-side inspection confirmed 28+ overdue scheduled events, the earliest dating back to the migration:

hook next_run_gmt status updraft_backup_resume 2025-12-18 04:19:49 OVERDUE wp_privacy_delete_old_export 2025-12-18 04:25:20 OVERDUE WPEngine_SecurityAuditor 2025-12-18 04:37:48 OVERDUE wp_version_check 2025-12-18 12:25:16 OVERDUE wp_update_plugins 2025-12-18 12:55:16 OVERDUE delete_expired_transients 2025-12-19 02:06:19 OVERDUE updraft_backup 2025-12-19 02:31:25 OVERDUE updraft_backup_database 2025-12-19 02:31:25 OVERDUE wpseo_reindex 2025-12-19 02:06:19 OVERDUE ...all 28+ events showing "now" (overdue)

Consequences of broken cron:

4. 6.4 GB of Backup Data Publicly Accessible on the Internet

Critical - Verified

A full copy of the website's backup files is sitting inside the publicly accessible part of the server. Think of it like leaving a copy of your office keys and filing cabinet in an unlocked car in the parking lot - it's not visible from the street, but anyone who walks up to the car can open the door.

I verified this during the audit. By visiting the URL below, I confirmed that the server responds with a valid page (HTTP 200), proving the file is reachable from the open internet:

Verified URL: https://sensible.com.au/backup_2025/wp-config.php Response: HTTP 200 (file exists and is reachable) Server: nginx

More critically, I discovered that the backup is not just a collection of static files - it is a fully functional second copy of the WordPress installation. The backup's login page is live and accessible to anyone on the internet:

Verified URL: https://sensible.com.au/backup_2025/wp-login.php Response: HTTP 200 - renders a fully functional WordPress login page Page title: "Log In - Sensible Business Solutions - WordPress" Also confirmed publicly downloadable: /backup_2025/license.txt HTTP 200 (19,903 bytes - WordPress license file)

The backup uses a different database name than the live site, but the same database user and password. This means the backup's database credentials have access to both databases. A compromised backup login could be leveraged to reach the live site's data through the shared database user.

Why this is a critical security issue:

I scanned the backup's plugin list against the WPScan vulnerability database. Multiple plugins in the backup have known, unpatched vulnerabilities:

Plugin (in backup) Backup Version Vulnerability Severity
Yoast SEO 25.8 Contributor+ Stored XSS (fixed in 26.9) 5.9 Medium
WP Security Audit Log 5.5.4 Authenticated Stored XSS (fixed in 5.6.0) 6.4 Medium
Simple History 5.20.0 Admin+ Info Exposure (fixed in 5.8.2) 4.9 Medium
Code Snippets 3.9.2 CSRF to cloud snippet actions (fixed in 3.9.5) 4.3 Medium

The backup copy is also running 19 plugins with pending updates that will never install, since the backup has no working cron and is not maintained. Every future vulnerability disclosure for any of these 46 plugins adds another exploitable entry point through this backup.

In total, 6.46 GB of backup and migration debris is spread across the server:

public_html/backup_2025/ 5.7 GB Full WordPress copy (live login page, shared DB credentials) wp-content/updraft/ 743 MB Old UpdraftPlus backup archives wp-content/mysql.sql 13 MB Complete database dump sitting in web root wp-content/bkp_mu-plugins/ 1.7 MB Backup copy of mu-plugins wp-content/*-old (8 directories) 48 KB Empty migration leftovers wp-content/config-3cd9a55-nitropack 8 KB Stale NitroPack configuration wp-content/advanced-scripts/ 4 KB Empty migration directory wp-content/drop-ins/ 4 KB Empty migration directory ------ Total waste: ~6.46 GB

The mysql.sql file is a complete database dump - it contains every user account, email address, form submission, and site setting. It is currently blocked by nginx (HTTP 403), but like the wp-config.php issue, any change to server configuration could expose it. A 13 MB database dump has no reason to exist in the web root.

5. PHP Errors Firing on Every Page Load

High

The PHP error log is 932 KB and growing. Two recurring issues were identified:

A) Undefined variable in theme (every page view):

PHP Warning: Undefined variable $icon in .../themes/hello-theme-child-master/functions.php on line 101 Frequency: Every single page load (frontend + admin) Impact: Warning logged to disk, minor CPU overhead per request

B) Fatal error in JetSmartFilters (AJAX filtering):

PHP Fatal error: Uncaught Error: Call to a member function is_data() on array in .../plugins/jet-smart-filters/includes/render.php on line 336 Stack trace: Jet_Smart_Filters_Render->ajax_apply_filters('') Jet_Smart_Filters_Referrer_Manager->setup_front_referrer() Impact: AJAX filter requests crash, search/filter functionality broken

C) Elementor Pro featured image warning:

PHP Warning: Trying to access array offset on value of type bool in .../plugins/elementor-pro/modules/dynamic-tags/tags/post-featured-image.php:39 Impact: Triggers on posts missing featured images

6. Plugin Overload - 24 Active Plugins

Medium

The site is running 24 active plugins plus 7 must-use plugins (5 of which are WP Engine remnants). Several plugins should not be permanently active:

Plugin Status Issue
WP File Manager Remove Immediately This plugin provides a web-based file browser with full server access. It was the target of a mass exploitation campaign in 2020 (CVE-2020-25213, CVSS 10.0 - unauthenticated remote code execution) that compromised hundreds of thousands of WordPress sites. Verified: plugin is installed and reachable at /wp-content/plugins/wp-file-manager/ (HTTP 200). The 2020 exploit endpoint is currently blocked (HTTP 403), but the plugin's purpose - giving browser-based access to all files on the server - makes it a high-value target for attackers.
WPE Site Migration Deactivate Migration is complete. No reason to keep active.
Better Search Replace Deactivate Database tool - activate only when performing search/replace operations.
WordPress Importer Deactivate One-time import tool. No need to keep active.
Duplicate Page Review Lightweight but assess if still needed.
UpdraftPlus Review Redundant if SiteGround's daily backups are enabled.
Wordfence Keep WAF is actively protecting the site (verified: blocking xmlrpc and author enumeration). Stale .htaccess rules are dead weight but not harmful.
HubSpot (leadin) Review Makes external API calls. Confirm if CRM integration is actively used.
WP Sheet Editor (4 plugins) Review Admin-only bulk editing suite. Confirmed not loading on the frontend.

7. Wordfence WAF Configuration Mismatch

Medium

Wordfence's Web Application Firewall is configured via .htaccess using auto_prepend_file directives targeting mod_php5, mod_php7, and mod_php. SiteGround uses PHP-FPM, not mod_php.

# .htaccess - Wordfence WAF <IfModule mod_php5.c> php_value auto_prepend_file '.../wordfence-waf.php' </IfModule> <IfModule mod_php7.c> php_value auto_prepend_file '.../wordfence-waf.php' </IfModule> <IfModule mod_php.c> php_value auto_prepend_file '.../wordfence-waf.php' </IfModule>

These .htaccess rules are dead weight from the old hosting environment and are parsed on every request without doing anything. The good news is that Wordfence is actively protecting the site through its .user.ini fallback method, which is the correct approach for PHP-FPM. I verified this externally:

External WAF behavior check (15 March 2026): xmlrpc.php: Connection reset (blocked) ← Wordfence is blocking this ?author=1: HTTP 404 (blocked) ← Author enumeration prevented .htaccess WAF: Targeting mod_php (dead code) ← Not how it's actually loading Wordfence appears to be loading via .user.ini fallback, so the WAF is active - but the .htaccess rules are unnecessary dead weight on every request.

8. Contact Forms Sending From Dead WP Engine Email Address (51 Pages Affected)

High

Every form on the site is configured to send email notifications from email@sensiblesite.wpenginepowered.com - the old WP Engine staging domain. This domain is no longer under Sensible's control. Emails sent from a domain you don't own are likely being rejected by mail servers or landing in spam folders, which means customer inquiries submitted through the website may never be received.

Form "email_from" setting across the site: Current value: email@sensiblesite.wpenginepowered.com (WP Engine - no longer valid) Affected pages: 51 published pages Includes: Home, Contact Us, About Us, all Service pages, all Location pages (Melbourne, Sydney), Case Studies, and the global "Service Form - Inside Pages" template

This affects every page that contains a form, including the most important lead generation pages on the site. The forms themselves still appear and can be submitted, but the email notifications generated from those submissions are sent using an address on a domain Sensible no longer controls. Most email providers will reject or spam-filter these messages.

I also found that form notification copies are still being sent to ethan@yakk.com.au.

9. All Users Are Administrators - Unused Generic Account Present

Medium

Every user account on the site has full administrator access. There are no editor, author, or subscriber-level accounts. While the active accounts all appear to be legitimate, the number of full administrators and the presence of an unused generic account are worth reviewing.

User Role Last Login Status
raysweeney Administrator 12 March 2026 Client - Active
yakkbasetheme (ethan@yakk) Administrator 9 March 2026 Yakk (SEO/Dev) - Active
luke@yakk.com.au Administrator 4 February 2026 Yakk - Confirm still needed
webadmin Administrator Never Generic shared account - never used

The webadmin account uses a generic Microsoft 365 email address (webadmin.bg@SensibleBiz.onmicrosoft.com) and has never been used to log in. Generic shared accounts are a security risk because there is no accountability for actions taken under that login - if it were compromised, there would be no way to determine who was responsible. If this is intended as a "break glass" emergency account, it is unnecessary - a new administrator account can always be created via SSH using WP-CLI or directly in the database if needed.

It's also worth confirming whether both Yakk accounts need full administrator access, or whether one of them could be downgraded to an Editor role. The fewer administrator accounts on a site, the smaller the attack surface.

10. No Two-Factor Authentication, File Editing Enabled, Loose Permissions

Medium

Several security hardening measures that are considered standard practice are not in place: