WordPress Admin or Client Account Compromised: How to Contain It
Someone logged in as one of your users. Lock the account, kill its sessions and application passwords, secure the email behind it, then review what it changed.
Seeing “Sorry, you are not allowed to access this page”, lost your admin role, your password, your 2FA device or tripped a security lockout? Get back in from the server, safely.
You cannot get into your own WordPress dashboard. Maybe you get a bare page saying Sorry, you are not allowed to access this page. Maybe the password reset email never arrives. Maybe your phone with the two-factor app is gone, or a security plugin decided you were an attacker and locked you out. Or the login page itself returns a 403 error.
Each of these blocks a different layer, and each has a clean way around it from the server, without guessing passwords and without breaking anything. This guide shows you which layer is stopping you and how to get back in. If you can log in but keep landing back on the login form, that is a cookie problem and our login loop guide covers it.
Getting into wp-admin takes three separate checks, and a lockout means one of them is failing:
| Layer | What it checks | What failure looks like |
|---|---|---|
| Network | Whether your IP address can reach the login page at all (host firewall, Cloudflare, .htaccess rules, a security plugin firewall) | 403 Forbidden, a block page, or a timeout before the login form appears |
| Authentication | Who you are: password, two-factor code, lockout counters | Login form errors, a lockout notice, a 2FA prompt you cannot answer |
| Authorization | What you may do: your role and its capabilities | You log in, then see “Sorry, you are not allowed to access this page.” |
Roles are worth understanding, because they explain the most confusing lockouts. WordPress stores your role in the wp_usermeta table under a key named after the database table prefix plus capabilities, for example wp_capabilities. The value is a PHP serialized array such as a:1:{s:13:"administrator";b:1;}. The definitions of what each role may do live in the options table under a key such as wp_user_roles. If either record goes missing, gets corrupted, or carries the wrong prefix, WordPress still lets you log in but treats you as a user with no permissions.
You need SSH or your hosting file manager, and for the database fixes, WP-CLI or phpMyAdmin from your hosting panel. Export the database before changing users or options: wp db export over SSH, or Export in phpMyAdmin.
How to run the commands in this guide. Server commands run in an SSH terminal from your site folder, the one that contains wp-config.php. WP-CLI is called wp on most hosts and wp-cli on some. SQL runs in phpMyAdmin’s SQL tab or through wp db query. curl runs on your own computer: built in on macOS and Linux, and available as curl.exe in Windows PowerShell or WSL.
One warning first. If you did not change anything and suddenly lost admin rights, or you find administrator accounts you do not recognize, treat it as a possible compromise. Get back in using the steps below, then follow our hacked site guide before doing anything else.
curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/wp-login.php
curl -sS -I https://example.com/wp-login.php
Run these on your own computer, replacing example.com with your domain. The first prints just the HTTP status code; the second shows the response headers. Then run the first command again from a different network, such as a laptop tethered to your phone’s mobile data.
cf-ray header means the response came from Cloudflare; a server header naming your web server suggests the block is on the host.A missing reset email is a mail delivery problem, not a WordPress password problem, so skip email entirely and set the password from the server.
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp user reset-password USERNAME --skip-email --show-password
Run these over SSH. The first lists every administrator so you can confirm the right account (and spot any you do not recognize). In the second, replace USERNAME with a login name, email address or ID from that list. --skip-email stops WordPress from emailing the user, and --show-password prints the new random password on screen, so it never sits in your shell history. Good output includes the new password and “Success: Password reset for 1 user.” Log in with it, then change it to one from your password manager on your profile page.
Open phpMyAdmin, select the site’s database (its name is DB_NAME in wp-config.php), open the users table (wp_users or your prefix plus users), and edit your row. In the user_pass field, choose the MD5 function from the dropdown and type a temporary password. WordPress still accepts an old-style MD5 hash for compatibility and replaces it with a strong hash. Change the password from your profile straight after logging in, and remove the temporary one from wherever you noted it.
Fix site email afterward, since resets and unlock links depend on it; an SMTP plugin fixes most cases.
First rule out the harmless version: if the address bar shows a specific screen such as admin.php?page=SOMETHING, that screen may belong to a plugin that is no longer active. Go to plain /wp-admin/. If the dashboard loads, your access is fine. Likewise, theme and plugin editors and the Add Plugin screen return this message when DISALLOW_FILE_EDIT or DISALLOW_FILE_MODS is set in wp-config.php, which is intentional on many managed sites.
If even /wp-admin/ refuses you, check your role:
wp db prefix
wp user get USERNAME --field=roles
wp user meta get USERNAME PREFIXcapabilities
wp role list
Run these over SSH. wp db prefix prints the table prefix WordPress is using, such as wp_; use it in place of PREFIX in the third command, so it becomes for example wp_capabilities. Replace USERNAME with your login. The second command prints your roles; good output is administrator, bad output is blank. The third prints the raw capabilities record; good output is array ( 'administrator' => true, ), bad output is an error that the key does not exist. wp role list should list the standard roles (Administrator, Editor, Author, Contributor, Subscriber); an empty list means the role definitions themselves are broken.
wp user set-role USERNAME administrator
wp role reset administrator
Run these over SSH. set-role replaces the user’s roles with administrator and writes the capabilities record with the correct prefix and serialization. Good output: “Success: Added USERNAME (USER_ID) to https://example.com as administrator.” wp role reset administrator restores the default capabilities of the administrator role, only needed when wp role list was empty or the role had been edited by a role editor plugin. It does not touch other roles unless you name them or add --all.
If the account itself is damaged, create a fresh administrator instead:
wp user create NEW_USERNAME NEW_EMAIL --role=administrator
Run this over SSH. NEW_USERNAME is a login name that does not already exist (avoid “admin”), and NEW_EMAIL is an address not used by another account. Without --user_pass, WP-CLI generates a strong password and prints it. Good output: “Success: Created user USER_ID.” followed by the password.
This is the classic cause when a site was moved and everyone suddenly lost access. If someone changed $table_prefix in wp-config.php (for example from wp_ to abc_) and renamed the tables, the data inside still uses the old prefix: wp_capabilities and wp_user_level in usermeta, and wp_user_roles in options. WordPress now looks for abc_capabilities, finds nothing, and every user has no role.
wp db query "SELECT meta_key, COUNT(*) FROM NEW_PREFIXusermeta WHERE meta_key LIKE '%capabilities' GROUP BY meta_key;"
wp db query "SELECT option_name FROM NEW_PREFIXoptions WHERE option_name LIKE '%user_roles';"
Run these over SSH. Replace NEW_PREFIX with the prefix from wp db prefix. Good output: keys that start with that same prefix. Bad output: keys starting with a different prefix, which is your OLD_PREFIX. To fix it, rename exactly the prefixed keys WordPress core uses:
UPDATE NEW_PREFIXusermeta SET meta_key = CONCAT('NEW_PREFIX', SUBSTRING(meta_key, LENGTH('OLD_PREFIX') + 1))
WHERE meta_key IN ('OLD_PREFIXcapabilities', 'OLD_PREFIXuser_level', 'OLD_PREFIXuser-settings', 'OLD_PREFIXuser-settings-time', 'OLD_PREFIXdashboard_quick_press_last_post_id');
UPDATE NEW_PREFIXoptions SET option_name = 'NEW_PREFIXuser_roles' WHERE option_name = 'OLD_PREFIXuser_roles';
Run this in phpMyAdmin’s SQL tab, or pass each statement to wp db query. Replace every NEW_PREFIX and OLD_PREFIX with the real prefixes, including the trailing underscore. The first statement swaps the old prefix for the new one on the five per-user keys core creates, and leaves every other key alone. The second renames the role definitions. Good output: a count of affected rows, then a working login. On multisite, per-site keys such as OLD_PREFIX2_capabilities need the same treatment, and some plugins store their own prefixed keys; check with the SELECT queries above.
If you edit wp_capabilities by hand in phpMyAdmin, the number before each string must equal its length exactly. In a:1:{s:13:"administrator";b:1;}, s:13 means a 13-character string, and “administrator” has 13 letters. A typo or a stray space makes PHP unable to read the value, and WordPress silently treats you as having no role. This is why wp user set-role or wp user meta update USERNAME PREFIXcapabilities '{"administrator":true}' --format=json is safer: WP-CLI builds the serialized value for you. In that second command, --format=json tells WP-CLI to read the value as JSON and store it as a proper PHP array.
Try the gentle options first: the backup or recovery codes you saved when you set up 2FA, or another administrator resetting two-factor for your account from your user profile or the plugin’s settings.
If codes are rejected rather than lost, check the server clock with date -u over SSH. Authenticator app codes depend on time, and a server clock that is off by more than a minute or so makes every code fail. A wrong server clock is a host issue.
If none of that works, two-factor is enforced by a plugin, so deactivating that plugin removes the second step:
wp plugin list --fields=name,status,title | grep -iE "two|2fa|factor|wordfence|solid|security"
wp plugin deactivate PLUGIN_SLUG
Run these over SSH. The first filters the plugin list for common two-factor and security plugin names; -i ignores case and -E lets the pipe mean “or”. Replace PLUGIN_SLUG with the name in the first column. Without WP-CLI, rename the plugin’s folder over SFTP, for example from wp-content/plugins/PLUGIN_SLUG to wp-content/plugins/PLUGIN_SLUG-off; WordPress deactivates a plugin whose folder it cannot find.
Log in, then reactivate the plugin right away (or rename the folder back and activate it on the Plugins screen), and reset 2FA for your account in its settings. With Wordfence, two-factor lives under Wordfence, Login Security, where you can deactivate it for your user and set it up again with your new device. Save the new recovery codes somewhere other than your phone.
Waiting out the lockout period works, but these are faster.
The Wordfence lockout page usually offers to email an unlock link to the site’s administrator address; use that if email works. If you are blocked by its firewall rather than its login protection, Wordfence documents the constant define( 'WFWAF_ENABLED', false );. With protection level “Basic WordPress Protection” it goes in wp-config.php just below the WP_DEBUG line; with “Extended Protection” it goes in wordfence-waf.php in your site folder, before the line that starts with if. That disables only the firewall. Otherwise, rename the wp-content/plugins/wordfence folder to log in, then rename it back and remove your IP from the block list under Wordfence, Firewall, Blocking.
wp option delete limit_login_lockouts
Run this over SSH. The plugin stores current lockouts in this option; deleting it clears all of them at once. Good output: “Success: Deleted ‘limit_login_lockouts’ option.” Renaming the plugin folder also works. Afterward, add your IP or username to its safelist.
Solid documents a temporary emergency switch: add define( 'ITSEC_DISABLE_MODULES', true ); to wp-config.php above the “That’s all, stop editing” line. It turns off all Solid modules, including lockouts and the hidden login URL, so you can log in. Release your lockout from the plugin’s settings and delete the line as soon as you are done, because it removes all of Solid’s protection. Solid also ships WP-CLI commands under wp itsec; run wp help itsec to see what your version offers. Its Magic Links feature, if your version has it, emails a login link that bypasses a lockout.
Plugins that move wp-login.php make the old address return 404. Deactivate the plugin (WP-CLI or folder rename) and log in at the normal /wp-login.php.
If your IP gets a 403 before WordPress even loads, the block lives in front of it. Check, in this order:
Require ip, Deny from or Allow from lines guarding wp-login.php or wp-admin, as shown below.grep -nE "Require|Deny from|Allow from|Order " .htaccess wp-admin/.htaccess 2>/dev/null
Run this over SSH in the site folder. It prints access-control lines from the main .htaccess and any inside wp-admin; 2>/dev/null hides the error if the second file does not exist. Good output: no lines, or rules that include your current IP. Bad output: a Require ip with an old address. Edit the address, or comment the block out with # to test.
If you have phpMyAdmin but no SSH, no WP-CLI and no working account, you can create an administrator directly. Do this only after a database export, and only when the steps above are impossible.
INSERT INTO PREFIXusers (user_login, user_pass, user_nicename, user_email, user_registered, user_status, display_name)
VALUES ('NEW_USERNAME', MD5('TEMP_PASSWORD'), 'NEW_USERNAME', 'NEW_EMAIL', UTC_TIMESTAMP(), 0, 'NEW_USERNAME');
SET @uid = LAST_INSERT_ID();
INSERT INTO PREFIXusermeta (user_id, meta_key, meta_value) VALUES
(@uid, 'PREFIXcapabilities', 'a:1:{s:13:"administrator";b:1;}'),
(@uid, 'PREFIXuser_level', '10');
Run this in phpMyAdmin’s SQL tab, all at once so LAST_INSERT_ID() refers to the new row. Replace every PREFIX with your table prefix from $table_prefix in wp-config.php, including inside the two meta key names. NEW_USERNAME, NEW_EMAIL and TEMP_PASSWORD are your choices. The capabilities value is the serialized administrator role, and user_level 10 is the legacy administrator level some plugins still read. Good output: “1 row inserted” for the user and “2 rows inserted” for the meta. Log in, set a strong password on your profile at once, and delete any old broken account only once you are sure it holds nothing you need.
On multisite, network-wide power comes from the super admin list, stored as the site_admins network option, not from a role. Each site in the network also has its own capabilities key, such as wp_2_capabilities for site 2.
wp super-admin list
wp super-admin add USERNAME
wp --url=SITE_URL user set-role USERNAME administrator
Run these over SSH on the network. The first lists super admins; the second grants super admin to USERNAME. In the third, SITE_URL is the address of one site from wp site list, and --url makes WP-CLI write the role for that site only. Good output for each is a “Success” line.
Once you are back in, audit who else can get in. List administrators with their registration dates (Step 2’s command), check for users with unexpected roles, and review application passwords on each admin profile, since an application password still works after a normal password reset. Rotate the keys and salts with wp config shuffle-salts if you suspect anyone else had a session; it logs everyone out. Unknown administrators or unfamiliar application passwords mean you should switch to the hacked site playbook immediately.
Open a ticket when the block is at the server firewall, when you have no SSH or database access, or when the server clock is wrong. Send:
curl -sS -I on wp-login.php, and any block page text or Ray ID.The Emergency Room has a short triage checklist, and our diagnostic tools show what the login page returns from outside your network.
Did this fix it?
Still stuck? Start again from what you can see in the Emergency Room.
Still stuck?
Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.
Get the free checklistsKeep reading
Search the field manual
Or jump straight to a symptom
Press / to search from any page, Esc to close