Skip to content

RB-007 — WordPress admin lockout (security plugin / lost admin) — recover via Dokploy

Use this runbook when: nobody can log in to the wp-admin of one of the Dokploy-hosted WordPress sites — tneconnect.app, eidos-global.com, or projecteidos.com — but the public site still loads, and you hold Dokploy / container backend access. Typical triggers: a security plugin (Wordfence et al.) is locking out logins, a brute-force / 2FA / breached-password block has fired, the login URL was hidden, or the only admin accounts belong to a third party you can no longer reach.

id: RB-007
title: WordPress admin lockout — recover via Dokploy container backend
severity: high
estimated_duration: 10–20 minutes with Dokploy access
servers_affected: [E2]                       # all three WP sites run on E2 via Dokploy
apps_affected: ["TnE Connect WordPress", "PE WordPress", "EIDOS Global WordPress"]
related_kis: [KI-048, KI-004, KI-038]
related_incidents: [INC-2026-06-04-tneconnect-wp-lockout]
related_runbooks: []

Trigger / detection

All of the following are true:

  • The public site loads (the front-end renders) — so this is a login-layer problem, not a host/container outage. If the whole site is down, this is not the right runbook (check E2 / Dokploy / Traefik first).
  • No one can log in at https://<site>/wp-login.php — neither you nor the developers. Symptoms vary by cause:
  • "You have been locked out" / brute-force block message → a security plugin's lockout.
  • A previously-working password is suddenly rejected → security plugin's breached-password check, or the DB was replaced.
  • A 2FA prompt no one can satisfy → security plugin's login-security / MFA enforcement.
  • wp-login.php 404s → a hidden-login plugin changed the path.
  • White screen on /wp-admin → a plugin fatal error.

If the public site itself is down, stop — that's an E2/Dokploy/Traefik problem, not this runbook.

Severity

High. These are public, brand-facing marketing sites and lead-capture funnels (.app and the company domains). You retain full control of the host, so there is no data-loss risk from the lockout itself — but every minute locked out is a minute you cannot publish, fix, or respond. The deeper exposure this runbook exists to close is captured in KI-048: for these sites Eidos historically had no owned admin account at all — admin access depended entirely on the third-party dev agency.

Key principle

A WordPress security plugin only guards the login form. It does not guard the container filesystem or the database underneath. With Dokploy backend access you are never actually locked out — you go in below the plugin, disable it on disk, and mint yourself an admin. None of the steps below touch the public site or its content.

Required access

  • Dokploy access to the WordPress app (today: platform.projecteidos.com), OR SSH to E2 (145.241.230.130) with docker rights.
  • The ability to open a container shell / terminal for the WordPress container (Dokploy → the app → Terminal, or docker exec -it <container> bash on E2).
  • No WordPress credentials are required — that is the whole point.

Preconditions

Item Notes
Public site is up Confirms login-layer issue, not host outage
Container is running docker ps shows the WordPress container for the site
You can write inside the container You are root in the container shell (the Dokploy terminal lands you as root)

Recovery steps

Worked example: the 2026-06-04 tneconnect.app lockout (incident). The cause there was Wordfence; substitute the actual plugin folder name for your case.

Step 1 — Open a shell in the WordPress container

Dokploy → the WordPress app → Terminal (or on E2: sudo docker ps to find the container, then sudo docker exec -it <id> bash). You should land as root.

Step 2 — Find the WordPress web root

cd /var/www/html 2>/dev/null || cd "$(dirname "$(find / -name wp-config.php 2>/dev/null | head -1)")"
pwd
ls -la wp-content/plugins

The plugin folder listing is your diagnosis. Look for security plugins: wordfence, wordfence-login-security, all-in-one-wp-security, wps-hide-login, two-factor, limit-login-attempts-reloaded, ithemes-security / better-wp-security, sucuri-scanner.

Step 3 — Try WP-CLI first (often the fastest path)

wp --info --allow-root

If WP-CLI is present, the whole recovery is two commands — deactivate the offender and create yourself an admin:

wp plugin deactivate wordfence wordfence-login-security wps-hide-login --allow-root
wp user create eidosadmin you@projecteidos.com --role=administrator --user_pass='STRONG-PASSWORD' --allow-root

Then skip to Step 7. If wp: command not found (common — most WP images don't ship WP-CLI), continue with the filesystem method below; it needs no extra tooling and no internet.

Step 4 — Force-deactivate the security plugin (filesystem, fully reversible)

WordPress auto-deactivates any plugin whose folder it cannot find. Renaming the folder is therefore a clean, reversible off-switch — it does not touch the database:

mv wp-content/plugins/wordfence wp-content/plugins/wordfence.off
# repeat for any other security/login plugin folder you identified

Then check for a firewall auto-prepend that some security plugins (Wordfence "Extended Protection") install in the web root:

ls -la wordfence-waf.php .user.ini .htaccess 2>/dev/null
cat .user.ini 2>/dev/null
  • wordfence-waf.php is guarded — once the plugin folder is renamed it silently no-ops, so renaming the folder alone is safe and will not white-screen the site.
  • Only if the site front-end does white-screen after this: comment out the auto_prepend_file = '.../wordfence-waf.php' line in .user.ini (or .htaccess / php.ini).

Step 5 — Inventory the admin accounts

You are in a PHP container, so use PHP directly (no WP-CLI or MySQL client needed). The file form avoids all shell-quoting trouble:

cat > /tmp/listadmins.php <<'PHP'
<?php
require '/var/www/html/wp-load.php';
foreach (get_users(array('role' => 'administrator')) as $u) {
    echo $u->ID . ' | ' . $u->user_login . ' | ' . $u->user_email . ' | ' . $u->user_registered . "\n";
}
PHP
php /tmp/listadmins.php

Read the output:

  • Your account is listed → the DB is intact; just reset its password (Step 6, option B) or create a fresh one (option A).
  • Only third-party / unfamiliar accounts, or every registered date is today → the DB may have been replaced during a "fresh install"; create your own admin (option A) and treat the existing accounts with suspicion.

Step 6 — Create an owned admin (option A) or reset a password (option B)

Option A — create a new Eidos-owned administrator (preferred). Independent of any third-party account:

cat > /tmp/mkadmin.php <<'PHP'
<?php
require '/var/www/html/wp-load.php';
$login = 'eidosadmin';                       // choose
$email = 'you@projecteidos.com';             // your email
$pass  = 'ChangeThisToAStrongPassword!';     // choose a strong password
$uid = wp_insert_user(array(
    'user_login' => $login,
    'user_email' => $email,
    'user_pass'  => $pass,
    'role'       => 'administrator',
));
echo is_wp_error($uid) ? ('ERROR: ' . $uid->get_error_message() . "\n")
                       : ('Created administrator ' . $login . ' (ID ' . $uid . ")\n");
PHP
# edit the three values, then:
php /tmp/mkadmin.php
rm /tmp/mkadmin.php

Option B — reset an existing account's password (uses WP's own hashing):

php -r 'require "/var/www/html/wp-load.php"; $u = get_user_by("login", "ADMIN_LOGIN"); wp_set_password("STRONG-PASSWORD", $u->ID); echo "password reset for ".$u->user_login."\n";'

DB fallback (only if PHP CLI is missing): go to the database container's terminal and run UPDATE wp_users SET user_pass = MD5('STRONG-PASSWORD') WHERE user_login = 'ADMIN_LOGIN'; (WordPress accepts the legacy MD5 hash and rehashes to phpass on next login). Confirm the table prefix first with SHOW TABLES; — it may not be wp_.

Step 7 — Log in and verify

Go to https://<site>/wp-login.php and log in with the new credentials. With the security plugin disabled there should be no lockout, no 2FA prompt, and no breached-password rejection.

Step 8 — Re-enable security the right way (do not skip)

Leaving Wordfence off indefinitely is not the end state. Once you are safely in:

  1. Confirm your owned admin works and is not the one that gets locked out.
  2. Re-enable the plugin by renaming the folder back (mv wordfence.off wordfence) only after you have, from inside wp-admin:
  3. Allow-listed your own IP(s) in the firewall.
  4. Turned off the breached-password lockout (or briefed everyone to use unique strong passwords and a manager).
  5. Enrolled 2FA for the owned admin first, before enforcing it for all admins.
  6. Keep the .off folder until you have verified login survives a re-enable; it is your instant escape hatch.

Verification (post-recovery)

  • https://<site>/wp-login.php logs in with the Eidos-owned admin account
  • The public site still renders correctly (front page + one content page)
  • wp-admin dashboard loads with no PHP fatal / white screen
  • The owned admin account survives a security-plugin re-enable (Step 8) — i.e. it is allow-listed / 2FA-enrolled and is not locked out
  • A full site backup has been taken and pushed off-host (see Post-incident)
  • The admin + DB credentials are in Vault

Rollback / abort

Every action here is reversible:

  • Re-enable a force-deactivated plugin: mv wp-content/plugins/<plugin>.off wp-content/plugins/<plugin>.
  • A newly-created admin can be deleted from wp-admin → Users.
  • No content, theme, or database row outside wp_users/wp_usermeta is modified by this runbook.

If the public site white-screens at any point, the most likely cause is a dangling security-plugin auto-prepend (Step 4) — restore by either renaming the plugin folder back or commenting the auto_prepend_file line.

Post-incident

  1. Take a full backup and ship it off-host. The cleanest is the All-in-One WP Migration export (.wpress) uploaded to the PECommon bucket (EIDOSDev1) under tneconnect.app/allin1_bkp/ (or the equivalent prefix for the other sites). This is what was done on 2026-06-04 — see backups.md and the incident.
  2. Store credentials in Vault. Admin login, DB / wp-config.php values, SFTP, plugin licence keys → kv_pe/tneconnect.app (see the per-app doc's "Credentials in Vault" table).
  3. File the incident at infra/incidents/YYYY-MM-DD-<site>-wordpress-lockout.md.
  4. Ensure an Eidos-owned admin persists on every Dokploy WordPress site — that is the standing fix for KI-048.