A prospect emails me. “Hey, looking for someone to maintain our WordPress website. Can you give us a price?”
The temptation is to answer. To say yes, $X per month, here’s what’s included. That’s what most operators do, and it’s how most operators get burned.
I don’t quote until I’ve looked. Twelve years of inheriting other people’s WordPress websites taught me that the visible part of the iceberg — what the client describes in the email — has almost no relationship to what I’ll actually be maintaining. The website that “just needs the occasional update” turns out to have 73 active plugins, three abandoned themes in wp-content/themes, an autoload table the size of a small album, and a 2019 PHP version the host won’t upgrade.
What follows is the twelve-item pre-quote check I run on every WordPress maintenance prospect before I send a number. It takes 60-90 minutes if I have admin access, 20-30 if I’m reading from screenshots they sent. By the end of it I know three things: whether I want the work, what it’ll cost me to do well, and whether the price they’re imagining is anywhere near the price the website actually deserves.
The setup
Before I check anything else, I ask for two pieces of access. WordPress admin login with administrator role, ideally on a non-shared account they create for me. And hosting account read access — cPanel, SiteGround user, WP Engine portal, whatever the host gives.
If they refuse hosting access, I’m already cautious. Half of what I want to check lives at the host layer — PHP version, mail configuration, server-level caching, file system state. An operator who guards hosting credentials from the maintainer they’re hiring is either inexperienced or hiding something. Both are red flags for the relationship, not just the technical work.
If they refuse admin access entirely and want me to quote from screenshots, I quote 2× what I’d otherwise charge. I’m pricing the risk I can’t see.
The twelve checks
1. WordPress version, PHP version, and the gap between them.
First thing I look at. WordPress core version (current or one minor version back is fine), PHP version (8.2+ is comfortable, 8.1 is the floor for active support as of 2026, 8.0 is end-of-life and I treat it as a project, not a maintenance item), and the gap.
Websites running WP 6.4 on PHP 7.4 tell me the operator stopped paying attention three years ago. Websites running WP 6.9 on PHP 8.3 tell me someone competent was here recently. The gap predicts the rest of the audit.
2. Active plugin count and what’s in the list.
Open wp-admin/plugins.php. Count active plugins. Read the names.
Fifteen to twenty active is normal. Thirty is high but not alarming if the website does real work. Forty-plus is a problem — every additional plugin compounds maintenance surface area, update conflicts, and security exposure. I once inherited a website with 73 active plugins. The previous operator had been billing the client for plugin updates monthly without ever consolidating or removing duplicates.
What I look for in the list: two cache plugins (always a problem), two security plugins (same), three “we tried different page builders” plugins still active, plugins from authors who haven’t shipped an update in two years. Each one is a story I’ll be billing to unpack.
3. Inactive plugins and abandoned themes.
Same screen, switch tab. How many inactive plugins are sitting in wp-content/plugins? More than five and the operator has been “saving them for later” since 2020. They’re attack surface — disabled doesn’t mean uninstalled — and they signal someone who doesn’t prune.
Then wp-admin/themes.php. How many themes are installed beyond the active one and a child theme parent? Twenty20, Twenty21, Twenty22, Twenty23 all sitting unused — same story. Cleanup work, billable.
4. The autoload size on wp_options.
This is the single most predictive technical check I run. I drop into wp-cli (or the equivalent admin tool) and ask:
SELECT SUM(LENGTH(option_value)) FROM wp_options WHERE autoload = 'yes';
Under 1 MB is healthy. 1-3 MB is normal for an established website. 4+ MB and every single page load is paying a tax — every request fetches that data into PHP memory before doing anything else. I’ve inherited websites with 12 MB of autoloaded options because the previous operator never cleaned up after deactivating Jetpack, WooCommerce extensions, or page builders.
This is also the number that tells me whether the previous maintenance was real. Operators who actually maintained the website keep autoload trim. Operators who clicked “Update All” once a month and called it maintenance leave autoload to grow forever.
I wrote about this in more depth in my piece on triaging a slow WordPress site.
5. Where the database lives, and what it weighs.
phpMyAdmin or the host’s equivalent. Total database size. Largest tables.
wp_posts over 100 MB usually means post revision bloat (see #6). wp_options over 50 MB means transient garbage or autoload bloat. wp_postmeta over 200 MB on a non-WooCommerce website means a plugin is writing data it never cleans up. WooCommerce websites with old order data: expect wp_actionscheduler_* tables to be enormous.
Database size on its own doesn’t price the work. But the ratio of size to traffic tells me how much performance debt I’m taking on.
6. Post revisions, transients, and “garbage that accumulates.”
Two quick queries:
SELECT COUNT(*) FROM wp_posts WHERE post_type = 'revision';
SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '\_transient\_%';
Under 500 revisions and a few hundred transients: fine. Over 5,000 revisions or 10,000 transients: nobody’s been minding the store. Both have well-known fixes — define( 'WP_POST_REVISIONS', 5 ), a transient pruner, an object cache layer — and the absence of these fixes tells me the previous operator wasn’t doing real maintenance.
7. Caching layer — what’s there and what’s actually working.
There’s a difference between installed and actually doing work. I check three things:
Is an object cache active? wp_using_ext_object_cache() returns true if so. The wp-admin dashboard’s Site Health screen also surfaces this. Most shared hosts ship no object cache; websites on Kinsta/WP Engine usually do.
Is there a page caching plugin active (WP Rocket, W3 Total Cache, LiteSpeed, etc.)? If yes, view a front-end page as a logged-out user and check the page source for cache markers — most plugins inject a <!-- Cached --> comment near the bottom. If the comment isn’t there, the cache plugin is installed but not delivering.
Is the host doing server-level caching above WordPress (Nginx FastCGI, Varnish, Cloudflare APO)? Some hosts have this on by default; some don’t.
A website where all three are missing is a website I’ll be optimizing before I can call it maintained.
8. Cron health.
WP-Cron in WordPress is famously unreliable on low-traffic websites — it only fires when someone visits. So scheduled tasks — backups, plugin updates, email digests, transient cleanup — may simply not be running.
I check wp cron event list (via wp-cli) and look at the “next run” timestamps. If something’s “overdue by 14 days,” WP-Cron has been broken since two weeks ago. Then I check whether the website has been wired to real server cron via the host (cPanel, control panel, or a constant in wp-config).
If WP-Cron is broken and there’s no server-level cron replacement, I know that whatever the client thinks is running on schedule (backups especially) probably isn’t.
9. Email deliverability — is wp_mail() actually delivering?
Easy test: trigger a password reset for myself, log out, see if the email arrives within 30 seconds. If yes, check the From address. If it’s wordpress@theirdomain.com and the email is in spam, that’s a deliverability problem waiting to break something important (recovery emails, WooCommerce order receipts, contact form submissions).
If the email never arrives at all, the host has disabled PHP’s mail() function entirely — increasingly common on shared hosting — and the website needs an SMTP plugin (WP Mail SMTP, FluentSMTP) wired to a real mailbox or service before any transactional email works.
This check has saved me from inheriting clients whose contact form submissions had been silently dropping into the void for months. I always make sure they know.
10. Backup status — and proof, not promises.
I ask three questions: where do backups go, when did the last successful one run, and have we ever restored from one.
The first answer is usually “UpdraftPlus” or “the host does it.” The second is usually “I’m not sure, let me check.” The third is almost always “no.”
I’m not looking for a perfect answer. I’m looking for whether the client actually knows. An operator-aware client says “BackupBuddy, weekly to Dropbox, I restored from one in February when we tested the staging environment.” A client who doesn’t know is paying for backups they’ve never verified — and the moment I take over maintenance, I’m the one who’ll be on the phone when restoration fails.
11. The wp-config.php audit.
I don’t trust what’s in the admin UI — I look at wp-config.php directly. Specifically:
Are the authentication salts the WordPress defaults (the literal 'put your unique phrase here' strings)? If yes, every session token on the website is forgeable by anyone who knows the defaults. Critical fix.
Is WP_DEBUG set to true in production? If yes, errors are being logged or displayed where attackers can read them.
Is DISALLOW_FILE_EDIT set? If not, anyone with admin access (via theft, weak password, or compromised plugin) can edit theme files directly through the WordPress admin — instant remote code execution.
Are the database credentials in the file, hard-coded? Of course they are; that’s standard. But are the file permissions tight (640 or stricter)? Often not.
Every one of these is a 60-second fix and a real security improvement.
12. The host, and how they answer their support tickets.
Last check, and the one I do least technically. Who is the host? Do I know them? If yes, what do I know about them — are they responsive, do they handle WordPress-specific issues competently, do they have a track record of dropping support tickets on the floor?
For unknown hosts, I open a test support ticket. “I noticed memory_limit is set to 256M; can you confirm this can be raised to 512M if needed?” Something trivial. If they answer in under four hours with a real answer, the host is a partner. If they answer in 72 hours with a templated response, the host will be my problem every time something needs server-level attention.
A maintenance contract on a website at a bad host is a maintenance contract on the host as well. Price accordingly.
What I do with all this
After 60-90 minutes, I have a picture. I categorize what I’ve found into three buckets.
Routine maintenance work — the stuff that’s actually monthly: core updates, plugin updates, backup verification, uptime monitoring. Bills at my standard hourly rate spread across the month.
Cleanup project work — the stuff that should have happened before I arrived: removing inactive plugins, trimming autoload, fixing default salts, configuring SMTP. Bills as a one-time engagement at the start of the contract. The client should know they’re paying for the previous operator’s deferred work.
Risk to escalate — the stuff that’s not maintenance, it’s intervention: PHP version upgrades that the host doesn’t support, plugin consolidations that require schema work, backup restoration testing that’s never been done. I quote these as projects with their own scope.
I tell the client what’s in each bucket. I quote each bucket separately. I never bundle cleanup into the maintenance rate, because if I do, the client believes I’m overcharging for maintenance — when actually I’m under-charging for cleanup that should be its own line item.
About a third of prospects walk away when they see the numbers. That’s fine. The ones who don’t walk away are the ones who understand they’re paying for the work the website actually needs, not the price they imagined before the audit.
The short version
You can’t quote what you haven’t seen. You can’t see by looking at the front of the website. The twelve things above tell you what the back of the website actually is, which tells you what the maintenance contract should actually cost.
The free Triage plugin I built runs most of these checks automatically — autoload size, plugin/theme inventory, cron health, salts, post revisions, the rest — and produces an AI-prioritized verdict on top. Twelve years of pre-quote checks, packaged for one-click use. Useful for the first 60 of those 90 minutes.
For the remaining 30 minutes — the host evaluation, the backup verification, the wp-config audit — there’s no plugin. There’s pattern recognition. That’s the work.
— Saber