Skip to content

Taking over a client's WordPress site: a checklist to run before you change anything

Before you change anything on a WordPress site someone else built, take a backup of your own, record the state the site is in, and send the client what you found in writing. This checklist gives the command or screen for each check and what to write down.

By
WP Ministry
Published

In short

  • Update nothing and delete nothing until a backup you took yourself is off the server and has been restored once on a staging copy.
  • Record versions, plugins, themes, users and every failed check with a date. That record is the baseline you compare against later.
  • The client is the owner of record for the domain, the hosting, the licenses and the payment account. You get a login of your own on each.
  • A checksum run is clean only when it prints the Success line and nothing else.
  • Send the client the list of what is already wrong before you begin, with what your plan covers and what is a separate job.
  • Quote a clean-up first when the site shows signs of a break-in, has no backup that restores, or runs software that no longer gets security fixes.

Before you change anything on a WordPress site you have inherited, take a backup of your own, record the state the site is in, and tell the client in writing what you found. That record is your baseline: it shows what was already wrong on the day you arrived. A backup you took and restored yourself is the only one you know works.

This checklist is for an agency or a freelancer who has agreed to maintain a site someone else built, and who can already get into it. If access is the problem, start with how to take over a WordPress site when the developer has disappeared.

The commands use WP-CLI, run over SSH from the folder that holds wp-config.php. How to use WP-CLI to manage a WordPress site covers connecting. Where a dashboard screen shows the same thing, it is named. To keep any listing as a file, add --format=csv and send it to the baseline folder made in the backup step:

bash
wp plugin list --format=csv > ~/baseline/plugins.csv

Access and ownership

The client should be the owner of record on every row. You should have a login of your own, never the client's password and never the previous developer's.

AccountWho should hold itWhat you need
Domain registrationThe client, as registrant. ICANN's guidance is that an organization lists its own legal name, and that a web developer or a host as registrant is not good practice.Nothing, or a delegated login if the registrar offers one
DNSThe clientDelegated access, or the client makes the changes you send
HostingThe client's account, paid by the clientA panel user of your own, and SFTP or SSH under your own name
WordPressThe client holds an administrator account of their ownA named administrator account for each person on your side
Paid plugins and themesThe client's account at each vendorWhich account holds each license, and when it renews
Payment gatewayThe client's legal name and bank accountA team login only if your work needs one
AnalyticsThe client's Google account, as an administratorTo be added as a user, under Admin, on the access management screen
Search ConsoleThe client, as a verified ownerTo be added as a full user, under Settings, then "Users and permissions"

A shared login cannot show who did what, and it cannot be taken away from one person. In WordPress, an administrator adds you under Users, then "Add User". Where the owner of record is you or the previous developer, record it: moving an account into the client's name is the client's decision, and often a separate job.

Check one more address. "Administration Email Address", under Settings, then General, receives the recovery mode and critical error notices. WordPress's documentation says that on a site set up by a developer or an agency, it should point to an address the organization can read.

bash
wp option get admin_email

Write down: for each account, the owner of record, everyone else with a login, and the level of your own access.

Back up before you touch anything

A full backup is two things: the files and the database. Take both yourself. Whatever backups the site already has are unproven until someone restores one.

  1. Step 1: Export the database and archive the files

    These make a folder in your home folder, export the database into it, and pack every file of the site into one archive. Nothing on the site changes. Check two things first: that your home folder is not inside the folder the site is served from, where anyone who guessed a file's name could download it, and that the hosting plan has room for a second copy of the site.

    bash
    mkdir -p ~/baseline
    wp db export ~/baseline/database.sql
    tar -czf ~/baseline/files.tar.gz .
  2. Step 2: Move the copies off the server

    Download both files over SFTP and keep them in two places that are not the server. The archive holds wp-config.php and the database password in it, so treat it as you would a password.

  3. Step 3: Restore it on a staging copy

    A backup is proven by a restore. Build a private copy from the two files and open it. How to set up a WordPress staging site has the steps, and the copy becomes the place to try every later change.

  4. Step 4: Record what was there before you

    Ask the host how far back its own backups go. Look for a backup plugin, its schedule and where it sends its copies. A site with no backup off the server is the first finding on your list.

Without SSH, make a full copy with the host's backup tool or a backup plugin, and download it.

Write down: the date and time of your backup, the size of each file, where the copies are kept, the date of the test restore and what you checked on it.

The baseline audit

Each check ends with what to record. None of them fixes anything.

WordPress, PHP and the database

bash
wp core version --extra
wp core check-update
wp db query "SELECT VERSION();"
wp cli info

The first three print the WordPress version, any newer release (or Success: WordPress is at the latest version.), and the database server's version.

Read wp cli info with care. It describes WP-CLI's own environment: its "PHP version" line is the PHP on the command line, and its "MySQL binary" line is the client program, not the database server. For the PHP that serves visitors, open Tools, then Site Health, then the Info tab, and read "PHP version" under Server. The end-of-life checker shows whether each version still gets security fixes.

Write down: the three versions, whether a newer WordPress is waiting, and whether the PHP version is still supported.

Site Health

On the Status tab, WordPress sorts its own tests into critical issues, recommended improvements and passed tests. Copy every critical issue and every recommendation as worded.

On the Info tab, select "Copy site info to clipboard" and paste the result into your document. It holds the versions, the active and inactive plugins and themes, the server and database details, and whether WordPress can write to its folders: most of a baseline in one step, with no command line. The site health check adds what a page shows from outside.

Plugins and themes

bash
wp plugin list --fields=name,status,update,version,update_version,auto_update,wporg_status
wp theme list

Each row says whether the plugin is active, whether an update is waiting, and whether automatic updates are on. In the theme list, a status of parent means the active child theme depends on that theme. Never delete it.

wporg_status says whether WordPress.org still lists the plugin. An answer of active can be relied on. Check every other answer by hand. WP-CLI looks a plugin up by its folder name, and it reports closed whenever a second lookup, of WordPress.org's code repository, returns anything other than "not found". When that lookup is refused, a custom plugin that was never on WordPress.org is reported as closed too.

For each plugin that is not active, open https://wordpress.org/plugins/ followed by its folder name:

  • Closed. The page says the plugin has been closed and is not available for download. The reasons WordPress.org gives include the author's request, a guideline violation and a security issue. No update will arrive for it.
  • Left behind. The page carries a notice that begins "This plugin hasn't been tested with the latest 3 major releases of WordPress."
  • No page at all. The plugin is paid or custom. Find out who sells it, whose account holds the license and whether that license still receives updates.

Write down: the full list, and separately: updates waiting, closed and untested plugins, and each paid plugin with its vendor, license holder and renewal date.

Must-use plugins and drop-ins

bash
wp plugin list --status=must-use
wp plugin list --status=dropin
ls -la wp-content/mu-plugins

Must-use plugins live in wp-content/mu-plugins. They cannot be switched off from the dashboard, only removed as files, and they show no update notices. Drop-ins are single files in wp-content that replace a part of WordPress. Finding one is not a fault: advanced-cache.php and object-cache.php are how caching is wired in. But WordPress's documentation warns that a compromised must-use plugin may be harder to detect than a regular one.

On the Plugins screen, the links "Must-Use" and "Drop-ins" above the table show the same lists. The ls shows subfolders, which the lists leave out.

Write down: every file, what it does and who put it there. Open each one.

Custom code and the child theme

Look in four places: a child theme, a plugin with no page on WordPress.org, the must-use folder, and a snippets plugin that keeps code in the database.

A child theme keeps its changes apart from the parent, so the parent can be updated without losing them. If the active theme has no child and was edited directly, the next theme update overwrites the edits. WP-CLI has no checksum command for themes. To see whether a theme from WordPress.org was edited, download the same version and compare the two folders.

Write down: where each piece of custom code lives, who wrote it, and whether a copy exists outside the site, such as a repository. If none does, your backup is the only copy.

Users, roles and registration

bash
wp user list --fields=ID,user_login,user_email,roles,user_registered
wp option get users_can_register
wp option get default_role

The Users screen shows the same list. The client should be able to put a person's name to every administrator. Mark shared logins, accounts of people who have left, and any account nobody can identify. For that last kind, read what to do about an unknown admin user before going further.

The two options are "Anyone can register" and "New User Default Role" on the General settings screen. A 1 with a default role above subscriber means anyone can register and be given that role.

Write down: every administrator with a name beside it, the accounts to be reviewed, and both settings.

Application passwords

An application password lets a program sign in to the site's REST API or XML-RPC as a user. It cannot be used on the login screen, so nobody sees it in daily use, and one made for an old deployment script or a phone app is easy to forget.

bash
for id in $(wp user list --field=ID); do echo "User $id"; wp user application-password list "$id" --fields=name,created,last_used,last_ip; done

WP-CLI prints created and last_used as Unix timestamps, such as 1791420391. On Linux, date -d @1791420391 turns one into a date. On a site with thousands of customer accounts, add --role=administrator to the inner wp user list first.

In the dashboard, the list is on each user's profile under "Application Passwords", with the columns "Last Used" and "Last IP" and a "Revoke" link.

Write down: each one, its owner, when it was last used, and whether anyone knows what it is for.

Checksums

bash
wp core verify-checksums --include-root
wp plugin verify-checksums --all

These compare WordPress and each plugin with the releases on WordPress.org. A clean run prints one line for each command and nothing else:

text
Success: WordPress installation verifies against checksums.
Success: Verified 8 of 8 plugins.

Anything else on the screen is a finding, even when the last line still says Success.

What it printsWhat it means
Warning: File should not exist: and a pathA file in WordPress's folders that is not part of the release. An added file alone still ends in Success. With --include-root the list includes ordinary extras in the site's top folder, such as a search engine's verification file. Each one needs an explanation.
Warning: File doesn't verify against checksum: and a pathA file of WordPress itself has been changed. The run ends in Error.
A table with Checksum does not match or File was addedA plugin's file differs from its release, or a file was added to its folder.
Could not retrieve the checksums for version, then skippingThe plugin has no release on WordPress.org at that version, so nothing was compared. Paid and custom plugins are not checked by this command.

Do not repair anything yet. If a changed or added file cannot be explained, stop and work through how to check whether your site has been hacked.

Write down: both last lines, every warning, and the explanation for each or the words "not explained".

Scheduled events

bash
wp cron event list
wp cron test

The list shows every scheduled task with its next run. Look for hooks that belong to a plugin no longer installed, and for run times far in the past, which mean scheduled tasks are not running.

wp cron test asks the site to start its scheduler, as any visit does, and answers Success: WP-Cron spawning is working as expected. It reports an error when DISABLE_WP_CRON is set, which is not a fault if the server calls the scheduler itself.

Write down: the number of events, any hook you cannot place, and the answer to the test.

Autoloaded options

Autoloaded options are the settings WordPress loads on every page. Site Health tests their total size. A pass reads "Autoloaded options are acceptable". Above 800,000 bytes it raises "Autoloaded options could affect performance" as a critical issue.

bash
wp db query "SELECT COUNT(*) AS options, ROUND(SUM(LENGTH(option_value)) / 1024) AS kilobytes FROM $(wp db prefix)options WHERE autoload IN ('yes', 'on', 'auto', 'auto-on');"
bash
wp db query "SELECT option_name, LENGTH(option_value) AS bytes FROM $(wp db prefix)options WHERE autoload IN ('yes', 'on', 'auto', 'auto-on') ORDER BY bytes DESC LIMIT 10;"

The first query gives the total and the second the ten largest. Both count the four values WordPress has autoloaded since version 6.6. WP-CLI's own shortcut, wp option list --autoload=on --format=total_bytes, counts only on and yes, so it reads low on a current site.

Write down: the total, and the largest options with the plugin each belongs to. Change none of them now.

The error log

bash
wp config get WP_DEBUG_LOG
tail -n 200 wp-content/debug.log

WordPress keeps a log of its own only when WP_DEBUG_LOG is set. The first command prints 1 when it is on, a path when the log goes to a file elsewhere, and an error that the constant is not defined when it is off. Switched on, the log is wp-content/debug.log, and Site Health raises "Your site is set to log errors to a potentially public file". WordPress's documentation does not recommend its debugging tools on a live site, so a log left on is itself a finding. Otherwise PHP's errors go to a log the host keeps.

Write down: the errors that repeat, the file each one names, and the date of the oldest.

Forms and email

Send a real message through every form, from an address outside the client's domain, and confirm it arrives. A form can show its thank-you message while the email goes nowhere.

Write down: each form, where it sends, and whether the message arrived.

A store

WooCommerce's documentation says to test payments on a staging site only, and that many payment gateways have a sandbox or test mode. So place the test order on your staging copy, with the gateway in test mode, and delete the order afterward. WooCommerce gives a test order no special status.

On the live store, read. Open WooCommerce, then Orders, and look at the last few weeks. "Failed" means a payment failed or was declined. "Pending payment" means an order arrived and no payment was made. A run of either is a fault you inherited.

Write down: the payment methods switched on, the date of the last paid order, and how many recent orders failed.

Security posture

The baseline already holds most of this. Four questions remain.

  • Does every administrator have a second step at login? WordPress's hardening guide calls two-step authentication a good idea. Record who has it. How to set up two-factor authentication covers adding it.
  • Which accounts should go? Take the list of old and shared accounts to the client. Removing a person is their decision.
  • Are file permissions loose? The hardening guide gives 755 for folders, 644 for files, and 400 or 440 for wp-config.php. The two commands below list the files and the folders that any account on the server can write to. The file permissions guide explains what to set.
  • Does anything look like a past break-in? An administrator nobody knows, a checksum warning nobody can explain, a must-use plugin nobody installed. If a sign holds up, stop the takeover and tell the client the same day.
bash
find . -type f -perm -o+w
find . -type d -perm -o+w

For a full review, with each plugin looked up for known vulnerabilities, follow how to audit a WordPress site for security weaknesses.

Performance and search baseline

Record how fast the site is and how it stands in search today, so that later changes can be compared.

  • Run PageSpeed Insights on named pages. Use the home page and one page of each kind that matters: a product, a post, the contact page. It reports mobile and desktop. Its field data comes from real Chrome users over the previous 28 days, and falls back to figures for the whole site when a page has too little. Its lab data comes from a simulated load and varies between runs.
  • Record the three Core Web Vitals from the field data. Google's good range is 2.5 seconds or less for Largest Contentful Paint, 200 milliseconds or less for Interaction to Next Paint, and 0.1 or less for Cumulative Layout Shift.
  • Export the Page indexing report from Search Console. It counts the pages that are indexed and not indexed, with a table of reasons. Google says not to expect every address to be indexed, only the canonical pages, so the count is a baseline and not a score.
  • Export the Performance report. By default it shows clicks and impressions for the past three months.
  • Check that the site is not hiding from search engines. The command below prints 0 when "Discourage search engines from indexing this site" is ticked under Settings, then Reading. On a live site that goes to the top of the list.
bash
wp option get blog_public

Write down: the date, each address tested, the device, the figures, and the two exports.

Write down what is already wrong

Turn the baseline into a list of findings. For each one, give what you saw, where, on what date, and what it risks, in a sentence the client can follow. Then put it in one of three groups.

GroupWhat belongs in itExamples
Before the plan startsAnything that makes the site unsafe to take responsibility forNo backup that restores, signs of a break-in, a failing checkout, PHP that no longer gets security fixes
Covered by the planRoutine work your plan already includesUpdates that are waiting, unused plugins and themes
A separate jobWork with its own scope and its own quoteReplacing a closed plugin, moving edits out of a parent theme, a license the client has to buy

Send the list before you begin, and ask the client to reply that they have read it. What you owe the client is set by your agreement with them, and this page is not legal advice. The care plan proposal template has wording for what a plan includes and what it does not.

The handover note

The handover note is the baseline in a form the client can read and keep. It should contain:

  • the site's address, the date of the baseline and who made it;
  • each account, its owner of record and your level of access;
  • your backup: when it was taken, where it is held, when it was restored and what was checked;
  • the backups that existed before you, or that there were none;
  • the WordPress, PHP and database versions, and whether each is still supported;
  • the plugin and theme list as an attachment, with updates waiting, closed and untested plugins, and paid licenses with holder and renewal date;
  • must-use plugins, drop-ins and custom code, with where each lives;
  • the administrators by name, the accounts nobody could identify, and the application passwords;
  • the result of both checksum runs, with every warning;
  • Site Health's critical issues and recommendations, as worded;
  • what was sent through each form and whether it arrived, and on a store, what the orders show;
  • the speed and search figures, with their dates;
  • the findings, each in its group, with what the plan covers and what is quoted separately;
  • what you did not check, such as the code inside a paid plugin;
  • what you changed during the audit, which should be nothing beyond your own login.

The first month

  1. Step 1: Set up your own backups and monitoring

    Put backups on a schedule you control, sent off the server, and restore one. How to schedule automatic WordPress backups has the method. Add a check that tells you when the site stops answering.

  2. Step 2: Deal with the first group of findings

    Do the work the client agreed to, and record each fix against its finding.

  3. Step 3: Update on staging, then on the live site

    Apply one update at a time on the staging copy and look at the site after each. How to safely update WordPress gives the order and what to do when one breaks something. Repeat on the live site only what passed.

  4. Step 4: Remove what is unused

    Delete inactive plugins and themes once the client has confirmed nothing depends on them. Site Health raises them as "You should remove inactive plugins" and "You should remove inactive themes", and its advice is to keep the active theme, its parent and the default theme.

  5. Step 5: Close old access

    Remove or lower the accounts the client agreed to, revoke application passwords nobody claimed, and switch on the second login step for every administrator.

  6. Step 6: Document the site

    Write one page: the accounts and their owners, the hosting details, where custom code lives, the forms and where they send, the licenses and their renewal dates, how to restore, and anything odd you learned.

  7. Step 7: Send the first report against the baseline

    The monthly maintenance report template is the shape, and the first one can show each figure beside the baseline. From then on, the maintenance checklist is the routine.

If you would rather not carry that routine yourself: White label is our Essential care plan, delivered under your agency's brand. It is for agencies with three or more client sites.

When to say no, or quote a clean-up first

A care plan assumes a site that is sound on the day it starts. Do not put a site on a plan as it is when:

  • there are signs of a break-in. That is a clean-up, then a plan. The hacked site runbook covers the first hour;
  • no backup restores, and your own would not restore on staging either;
  • WordPress or PHP no longer gets security fixes, and the staging copy breaks when either is updated;
  • the site depends on a closed plugin, or on a paid one whose license the client will not renew;
  • files of WordPress itself or a parent theme were edited, and nobody knows what was changed or why;
  • the checkout on a store is failing now;
  • the client will not become the owner of record for the domain and the hosting, or will give you only a shared login;
  • the client will not acknowledge the findings in writing.

For most of these the answer is a clean-up quoted as its own job, with its own scope and an end, and a plan that starts when it is done. For the last two, it is no.

Common questions

Can I do this without SSH or WP-CLI?

Most of it. Site Health's Info tab copies the versions, plugins, themes and server details in one step. The Plugins and Users screens show the rest, and each user's profile lists their application passwords. The checksum comparison has no screen in WordPress, so ask the host to run it or record it as not checked.

Should I update everything on the first day?

No. Updating before the backup and the baseline removes your way back and your record of what was there. Back up, restore the backup once on staging, write the baseline, then update on staging before the live site.

The previous developer still has an administrator account. Do I delete it?

Not on your own decision. Record it, tell the client, and act on their written answer. When you delete a user, WordPress asks what to do with the content that user owns. Choose "Attribute all content to another user.", because the other choice deletes it.

What if the client says a problem started after I took over?

That is what the dated baseline and their reply to it are for. A finding listed in the note you sent before you began was there before you. Without the note, it is your word against theirs.

More on this subject

White label for your agency

White label is from $22 a month per site. The Essential plan under your agency's brand, with branding included. Three sites or more. Tell us how many sites you look after.