Title: Secured WP
Author: wpsecuredcom
Published: <strong>August 1, 2021</strong>
Last modified: September 28, 2026

---

Search plugins

![](https://ps.w.org/secured-wp/assets/banner-772x250.jpg?rev=2576070)

![](https://ps.w.org/secured-wp/assets/icon.svg?rev=2576072)

# Secured WP

 By [wpsecuredcom](https://profiles.wordpress.org/wpsecuredcom/)

[Download](https://downloads.wordpress.org/plugin/secured-wp.2.5.1.zip)

[Live Preview](https://srd.wordpress.org/plugins/secured-wp/?preview=1)

 * [Details](https://srd.wordpress.org/plugins/secured-wp/#description)
 * [Reviews](https://srd.wordpress.org/plugins/secured-wp/#reviews)
 *  [Installation](https://srd.wordpress.org/plugins/secured-wp/#installation)
 * [Development](https://srd.wordpress.org/plugins/secured-wp/#developers)

 [Support](https://wordpress.org/support/plugin/secured-wp/)

## Description

Adds layer of security for your WordPress site. Adds custom login page slug, enables
2FA, removes security issues. Adds remember device, counts login attempts and lock
usernames if the password is wrong. Out of band e-mail is also supported – instead
of entering codes, your user can use simple login link from within their e-mail 
client.

**WooCommerce**

WooCommerce is also supported for 2FA, just enable the plugin and all your customers
will be asked to enable two-factor authentication.

List with currently supported features:

 1. **Login redirection** – redirects the default wp-login.php to a slug of your choice
 2. **Login attempts** – counts the unsuccessful attempts, and locks user if there 
    are too many
 3. **2FA settings** – gives the ability to use two factor authentication and Out Of
    Band email link
 4. **Remember devices** – current device could be remembered for given amount of days
    and user wont be asked for the 2FA code on it before that (the password is still
    required)
 5. **Removes XML-RPC** from your WordPress site
 6. Custom shortcode ([wps_custom_settings]) can be used to give the users without 
    access to the dashboard to setup the 2FA
 7. **Backup codes** – ten single-use codes per user, the way back in when the phone
    is lost
 8. **Signed-in sessions** – see everywhere an account is signed in, and log sessions
    out one by one

**Login Redirection**

You can change the default wp-login.php to slug of your choice. That will prevent
most common hacker attacks and will harden your WordPress installation. You can 
redirect the original wp-login.php to the slug of your choice.

**2FA login**

Enable two-factor authentication for your WordPress site, and to enforce your website
users, or some of them to use 2FA. Next time user logins s/he will be asked to enable
the 2FA using their favorite application. Once the process is completed, every time
the user logs, s/he will be asked to provide the 2FA code.

Which second step users get after the password:

 * TOTP on – a code from the authenticator app. With Out of band e-mail also on,
   the email link is offered as an alternative.
 * TOTP off, Out of band e-mail on – a one-time sign-in link sent to the user’s 
   email address.
 * both off – no second step; the password alone signs users in, even with 2FA enabled.
   Passkeys do not change that: they are a separate way to sign in, not a step after
   the password.

An emailed link is a weaker second step than TOTP: whoever controls the mailbox 
can also reset the password.

Setting up the authenticator (at the first login after 2FA is enabled, or on the
profile page) shows a QR code, an “Open in authenticator app” button and the setup
key with a Copy button. On a phone the QR code cannot be scanned from its own screen:
the button opens the `otpauth://` link, which iOS and Android hand to the installed
authenticator or password app (Google Authenticator, Microsoft Authenticator, 1Password,
iOS Passwords, …); the setup key can also be copied and pasted into the app. The
code field is marked as a one-time code, so iOS / macOS Passwords and password managers
that hold the secret offer the current code at later logins.

On the profile page everything Secured WP adds – remembered devices, the authenticator
and passkeys – is grouped in one “Secured WP” panel; the same panel is used by the`[
wps_custom_settings]` shortcode and on WooCommerce’s My Account page.

**Login Attempts**

This gives you the ability to prevent brute force attacks if the hacker knows the
username and tries to guess the password. With this enabled, after the given amount
of tries that username is locked for the given amount of time – from the address
the tries came from. Somebody guessing from one place cannot lock the real user 
out everywhere. If the account fails ten times the allowed amount from many addresses,
it is locked everywhere, except on devices the user asked to remember. Behind a 
reverse proxy or CDN, use the `wpsec_client_ip` filter to supply the visitor’s real
address, otherwise every visitor shares the proxy’s address. Attempts are counted
before the password or code is checked, with counters that cannot be outrun by sending
many requests at once. An account locked by an administrator cannot be used with
application passwords either.

**Remember device setting**

With that, user can use given device for the given amount of days without being 
asked for the 2FA code. The password is always required – a remembered device never
signs anybody in on its own. The devices can be removed or checked from the default
user settings page; changing the password or resetting the authenticator forgets
them all.
 That setting is based on current setting (global) for the current moment,
which means that when the day value (in settings) is changed globally, that wont
reflect the already set cookies and user devices. Example: If you set that to 10
days and there is a user which decide to use Remember Device functionality, when
you change that value to 15 days, that wont increase the time for that user. Same
applies for decreasing the value.

**Encryption key for 2FA secrets**

Authenticator secrets are stored encrypted (XChaCha20-Poly1305), so a copy of the
database alone – a leaked backup, an SQL injection – does not let anyone generate
your users’ codes. That only holds if the key is kept somewhere other than the database.
The plugin takes the key from the first of these that exists:

 1. The `WPSEC_ENCRYPTION_KEY` environment variable. Best choice when your host lets
    you set environment variables (Docker, most managed hosts, or `fastcgi_param WPSEC_ENCRYPTION_KEY"...";`
    in nginx). It always takes priority.
 2. The `WPSEC_ENCRYPTION_KEY` constant in wp-config.php: `define( 'WPSEC_ENCRYPTION_KEY','...');`
 3. A key stored in the database – only as a fallback. It keeps working when the WordPress
    salts are changed, but anyone holding a copy of the whole database also holds this
    key.

The key must be at least 32 random characters, for example the output of `openssl
rand -hex 32`. A shorter value is ignored and reported as an error; users then cannot
set up an authenticator until it is fixed.

When none of these exists, the plugin creates a key itself – on activation, the 
first time the plugin settings page is opened, or the first time one is needed –
and tries to add it to the top of wp-config.php, right after the opening `<?php`(
after a `declare()` line, if the file starts with one):

    ```
    // BEGIN Secured WP - encryption key for the stored 2FA secrets. Do not change or remove it; see the Secured WP readme.
    defined( 'WPSEC_ENCRYPTION_KEY' ) || define( 'WPSEC_ENCRYPTION_KEY', '<64 hex characters>' );
    // END Secured WP
    ```

The top of the file is used because every wp-config.php starts with `<?php`, while
the “That’s all, stop editing!” line differs between hosts or is missing. The plugin
only writes when wp-config.php is writable and does not mention WPSEC_ENCRYPTION_KEY
yet, never when the file declares a namespace, and only after PHP itself has parsed
the result. It writes in place (owner, permissions and symlinks are kept) and puts
the original back if the result does not read back exactly. To stop the plugin from
ever editing wp-config.php, add `add_filter( 'wpsec_write_wp_config', '__return_false');`
to a must-use plugin.

When wp-config.php cannot be written, the key is stored in the database, and the
plugin settings page and Tools > Site Health say so. To move it out of the database,
do one of these:

 * Make wp-config.php writable, click “Move the key to wp-config.php” on the plugin
   settings page (or run `wp wpsec move-key`), then make the file read-only again.
   The same key is written to the top of the file and deleted from the database,
   so nothing needs re-encrypting.
 * Or copy the key yourself. After confirming your identity, the settings page shows
   the exact block to paste (`wp wpsec move-key` prints it too when it cannot write).
   Put it at the top of wp-config.php, right after `<?php` – anywhere before the
   line that loads wp-settings.php works – or, where the host manages wp-config.
   php, in the file the host tells you to use for your own settings.
 * Or set the `WPSEC_ENCRYPTION_KEY` environment variable to the value between the
   quotes in that block.

As soon as the plugin finds the same key in wp-config.php or the environment, it
uses it from there – it takes precedence over the database – and deletes the database
copy (the next time an administrator opens the admin, or on `wp wpsec key-status`).

If your deployments replace wp-config.php (git, CI, or a host that manages the file),
the block the plugin wrote would be removed on the next deploy. Copy it into the
wp-config.php you deploy, or use the environment variable. If the key goes missing
while users still have authenticators encrypted with it, the plugin does not create
a new key over it: the settings page, the Users screens and Site Health report how
many authenticators cannot be read, until the key is restored or those authenticators
are reset.

Changing or adding a key: every stored value records which key encrypted it. The
plugin reads with any key it still has, and re-encrypts with the current one (the
first in the list above) whenever a secret is read.

 * Moving from the database key or the wp-config.php constant to the environment
   variable: just set the environment variable. The old key stays where it is and
   is still used to read.
 * Changing the value of the environment variable or the constant: put the old value
   into `WPSEC_ENCRYPTION_KEY_PREVIOUS` (environment variable or constant) and the
   new value into `WPSEC_ENCRYPTION_KEY`. The previous key is only ever used to 
   read.

To move everything at once, run `wp wpsec reseal`. Once it reports nothing unreadable,
the old key can be removed: delete `WPSEC_ENCRYPTION_KEY_PREVIOUS` or the old constant,
and `wp wpsec reseal --drop-database-key` deletes the database fallback key.

Never remove or change the only key that encrypted the stored secrets. Those users’
authenticators cannot be read any more: they are told so at login, can use the emailed
link or a passkey if enabled, and must have their authenticator reset. Salt changes
do not affect the key.

Secrets stored by earlier versions (encrypted with a key derived from the WordPress
salts) are read and re-encrypted with the new key automatically. If the salts were
changed before the upgrade, those secrets cannot be read and must be reset.

**Backup codes**

Right after setting up the authenticator, each user gets ten backup codes (XXXXX-
XXXXX), shown once, with Copy, Download and Print. Each code signs in once instead
of an authenticator code – at login (“Lost your phone? Use a backup code instead”)
and when confirming identity for security changes. So a user who lost their phone
can sign in, confirm their identity with another code, reset their authenticator
on their profile and set it up on the new phone, without an administrator. Codes
also work when the stored authenticator cannot be read because its encryption key
was lost.

The profile has its own Backup codes section next to the authenticator: how many
are left and when they were made, always visible, with “Make backup codes” (it asks
the user to confirm their identity first when needed; a new set replaces the old
one). Users who set up the authenticator before backup codes existed get a first
set, once, at their next login, and every user with two-factor login sees an admin
notice when they have no codes or 2 or fewer left – dismissible until that changes.
Administrators see a user’s count on their profile, never the codes, and cannot 
make them for someone else.

Codes are stored as password hashes, independent of the encryption key and the salts,
and each can be used exactly once, even by simultaneous requests. Resetting the 
authenticator retires the codes; the next setup comes with a new set. For support,`
wp wpsec backup-codes <user> --generate` prints a fresh set to hand over.

**Signed-in sessions**

The Secured WP panel on the profile lists every place the account is signed in right
now – browser, IP address, when it signed in and when the session expires – marks“
this browser”, and logs sessions out one by one or all others at once. Administrators
can do the same for users they may edit (“Log out everywhere”). Logging out single
sessions needs WordPress’s own session storage; if a plugin replaces it, only “log
out all other sessions” is offered.

**Resetting a user’s authenticator**

A reset removes the authenticator and its backup codes and forgets the user’s remembered
devices; they set up a new one (with new backup codes) at their next login. It is
needed when a phone is lost, or when an authenticator cannot be read (its encryption
key was lost). The Users screen shows “UNREADABLE – reset required” in the Secured
WP Status column for those.

 * Users screen: “Reset authenticator” under the user’s name, or select users and
   choose “Reset authenticator” from Bulk actions. Administrators are asked to confirm
   their identity first.
 * Multisite: the authenticator belongs to the user across the whole network, and
   WordPress lets only network administrators edit other users. The reset is offered
   on Network Admin > Users, and on a site’s Users screen only to network administrators–
   site administrators do not get it.
 * Profile page: another administrator can reset it there too.
 * The user themselves, with a backup code: sign in with one, confirm identity with
   another, then “Reset authenticator” on the profile.
 * WP-CLI: `wp wpsec reset-authenticator <user>...`, or `wp wpsec reset-authenticator--
   unreadable` for every authenticator that cannot be read.

**WP-CLI**

All commands are under `wp wpsec`; `wp help wpsec <command>` shows the built-in 
help. They act on the whole installation – on multisite the key and the users’ authenticators
are network-wide, so `--url` makes no difference. Anyone who can run WP-CLI already
controls the site, so the commands do not ask for identity confirmation the way 
the admin screens do.

    ```
    wp wpsec key-status
    ```

Shows where the encryption key comes from (environment variable, wp-config.php constant,
or database), how many keys are available for reading, how many authenticators are
stored (and how many are still in the pre-2.5 format), and warns about a misconfigured
key, a key kept in the database, or authenticators encrypted with a key that is 
no longer available. If the database still holds a copy of a key that is now also
set in wp-config.php or the environment, the database copy is deleted and the command
says so.

    ```
    wp wpsec move-key
    ```

Moves the key stored in the database to the top of wp-config.php, between the “BEGIN
Secured WP” and “END Secured WP” lines, then deletes the database copy. It is the
same key, so nothing needs re-encrypting. wp-config.php must be writable while the
command runs; it can be made read-only again afterwards. If the file cannot be written,
the command …

## Screenshots

[⌊Main screen of the plugin, settings tabs and information about the settings⌉⌊Main
screen of the plugin, settings tabs and information about the settings⌉[

Main screen of the plugin, settings tabs and information about the settings

[⌊2FA login screen, user does not have enabled 2FA yet⌉⌊2FA login screen, user does
not have enabled 2FA yet⌉[

2FA login screen, user does not have enabled 2FA yet

[⌊E-mail with the Out of Band link⌉⌊E-mail with the Out of Band link⌉[

E-mail with the Out of Band link

[⌊WooCommerce My Profile page⌉⌊WooCommerce My Profile page⌉[

WooCommerce My Profile page

[⌊Extends default Users menu bulk actions⌉⌊Extends default Users menu bulk actions⌉[

Extends default Users menu bulk actions

[⌊Extends default Users list by adding new filter drop-down from where you can easily
filer users by user role⌉⌊Extends default Users list by adding new filter drop-down
from where you can easily filer users by user role⌉[

Extends default Users list by adding new filter drop-down from where you can easily
filer users by user role

[⌊Adds new column to the standard Users menu - you can see the status of every user,
and sort the Locked and Logged in users⌉⌊Adds new column to the standard Users menu-
you can see the status of every user, and sort the Locked and Logged in users⌉[

Adds new column to the standard Users menu – you can see the status of every user,
and sort the Locked and Logged in users

## Installation

Manual Installation

 1. Download the “secured-wp.zip” file with the plugin to a location of your choice
 2. Upload “secured-wp.zip” by going yo plugins -> Upload plugin and then select the
    plugin location from step one
 3. Activate the plugin through the \”Plugins\” menu in WordPress.

Install from within WordPress

 1. Go to Plugins -> Add new
 2. Search for “Secured WP”
 3. Install and activate the plugin through the “Plugins” menu in WordPress.

## FAQ

### Can I disable some of the modules

Every single module can be enabled/disabled from its settings tab.

### Can I exclude some user

Yes – go to users page – select users by pressing the check box next to the username,
and from the drop down menu select the action you want to perform and click Apply.

## Reviews

![](https://secure.gravatar.com/avatar/e36d4faeb072d1dfab7f26ab8e5f4a8b3f83b11412a1711e7277b46a58d2fbe4?
s=60&d=retro&r=g)

### 󠀁[Solved a major headache](https://wordpress.org/support/topic/solved-a-major-headache/)󠁿

 [sitetrail](https://profiles.wordpress.org/sitetrail/) July 9, 2026

This plugin solved a major security headache for us. We could not pay Godaddy all
their sneaky upsells for old servers and kept getting the older accounts hacked.
SecureWP solved a major problem in keeping sites safe. Their restore service is 
also fast and effective.

 [ Read all 1 review ](https://wordpress.org/support/plugin/secured-wp/reviews/)

## Contributors & Developers

“Secured WP” is open source software. The following people have contributed to this
plugin.

Contributors

 *   [ wpsecuredcom ](https://profiles.wordpress.org/wpsecuredcom/)

[Translate “Secured WP” into your language.](https://translate.wordpress.org/projects/wp-plugins/secured-wp)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/secured-wp/), check
out the [SVN repository](https://plugins.svn.wordpress.org/secured-wp/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/secured-wp/) by 
[RSS](https://plugins.trac.wordpress.org/log/secured-wp/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 2.5.0

Security review: one-time TOTP consumption across enrollment/login/confirmation,
160-bit secrets for new enrollments, atomic passkey request limits and challenge
consumption, atomic profile-enrollment limits, fail-closed database errors and authenticator
persistence, escaped lockout emails, proper XML-RPC 2FA errors, safe byte-buffer
deserialization, and CLI-only diagnostic scripts.
 Performance: one indexed query
per counter read; expired-counter cleanup runs on hourly WP-Cron instead of random
login requests. Visit the main-site admin once after deployment to schedule cleanup.
Existing authenticator secrets remain valid. A previously accepted code cannot be
reused: wait for the next code before authenticating again.

New: backup codes – ten single-use codes per user, shown once after setting up the
authenticator, accepted at login and for identity confirmation, replaceable from
the profile; self-service recovery after a lost phone or a lost encryption key. `
wp wpsec backup-codes`.
 New: signed-in sessions on the profile – browser, IP, sign-
in and expiry times, “this browser”, log out one session or all others. Security:
passing a user to the plugin’s User helpers no longer silently makes it the “current”
user, and an empty or unknown user resolves to nobody rather than to the logged-
in user. Stored settings are type-checked on every read. “Delete data on uninstall”
now also removes passkey credentials, remembered devices and old attempt counters.
Removed: unused code and third-party libraries (Bacon QR Code, DASPRiD Enum, ParagonIE,
PSR, Spomky, Symfony), the legacy users-list class, the unused JIT asset compilers,
the unused password validator and the settings Export/Import tab that had no handler;
the plugin no longer defines a global trigger_deprecation() function. Namespace 
WPSEC\Mosules\Views renamed to WPSEC\Controllers\Modules\Views. Security: authenticator
secrets are stored only after a code from them verifies; a secret shown during a
login that was not finished is never used. Security: an enrolled authenticator secret
is no longer shown on the profile page, to the user or to administrators – reset
and enroll again instead. Security: Remember devices skips only the 2FA code; it
no longer signs the browser in without a password. Security: login attempts are 
locked per source address, with an account-wide backstop that remembered devices
get past; the counter no longer re-locks right after a lock ends; administrator 
emails are sent once per lock period. Security: the custom login slug no longer 
leaks through /wp-admin/options.php (CVE-2021-24917). Security: with only Out of
band e-mail enabled, users now get the emailed sign-in link as the second step. 
Before, the password alone signed them in. Security: login, code and identity-confirmation
limits are counted atomically and before checking, so simultaneous requests can 
no longer each get the full allowance (before, a burst of parallel code guesses 
was limited only by the number of PHP workers). Security: an administrator’s lock
also stops application passwords. Security: authenticator secrets are encrypted 
with a key kept outside the database (environment variable or wp-config.php, database
only as a fallback) instead of one derived from the salts. Changing the salts no
longer breaks every login; an unreadable secret is reported instead of crashing 
the login page. The key is written at the top of wp-config.php (not above a “stop
editing” line, which some hosts do not have); a key that had to be stored in the
database can be moved with a button or `wp wpsec move-key`, or copied by hand, after
which the database copy is deleted. No new key is created over a lost one. WP-CLI
commands: `wp wpsec key-status`, `move-key`, `reseal`, `reset-authenticator`. Security:“
Reset authenticator” on the Users screens (row and bulk action, network Users screen
on multisite); the status column flags authenticators that cannot be read. Bulk 
actions ignore user IDs that are not real users (a 0 used to act on the administrator
running it). Fixed: the setup QR code did not scan – the format information was 
written to the wrong cells, and a link too long for the code was silently cut short.
Codes now match a reference encoder bit for bit. The built-in encoder (no third-
party library) now covers all QR versions 1-40 (up to 2331 bytes, previously 213),
so long and non-Latin site names get a working code; dense codes get a “Show a larger
QR code” link so a phone can scan them; the QR markup is 6-7 times smaller. If a
QR code still cannot be drawn, the page says so and offers the “Open in authenticator
app” button and the setup key instead of failing. Authenticator setup: “Open in 
authenticator app” button (otpauth:// link) and a Copy button for the setup key;
the code field supports one-time-code autofill; the authenticator app now shows 
the account name next to the site name. Profile page: Secured WP’s options are grouped
in one branded panel (also in the shortcode and WooCommerce My Account). Security:
a WPSEC_ENCRYPTION_KEY line commented out in wp-config.php is no longer used; wp-
config.php is not written when the disk is nearly full. Fixed: a module setting 
stored as something other than true/false (a hand edit, an import) no longer causes
a fatal error on every page.

#### 2.4.0

Update to WP 7.1

#### 2.3.2

Small update – added option to remove styles for gutenberg (core WordPress)

#### 2.3.1

Small update – added option to remove styles for classical themes (core WordPress)

#### 2.3.0

Maintenance update. Tested up to WP 6.9. Added jquery scripts removal logic.

#### 2.2.4

blueprint live preview fixes.

#### 2.2.3

Bug fixes related to login attempts.

#### 2.2.2

Updated TOTP library.

#### 2.2.1

PHP 8 fixes.

#### 2.2.0

Updated libs and fixed deprecations.

#### 2.1.1

Small bug fixes with redirection

#### 2.1.0

Removed all jQuery dependency when custom page (or post) with shortcode is used 
for user’s settings manipulation. Fixed lots of bugs

#### 2.0.3

 * Missing class fix, uninstall script fix

#### 2.0.2

 * Added missing constants file

#### 2.0.1

 * Fixed bugs and problems, added blueprint.json

#### 2.0.0

 * Most of the plugin has been rewritten

#### 1.0.0

 * Initial release.

## Meta

 *  Version **2.5.1**
 *  Last updated **1 day ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.0 or higher **
 *  Tested up to **7.1.2**
 *  PHP version ** 8.1 or higher **
 *  Language
 * [English (US)](https://wordpress.org/plugins/secured-wp/)
 * Tags
 * [2-factor authentication](https://srd.wordpress.org/plugins/tags/2-factor-authentication/)
   [2FA](https://srd.wordpress.org/plugins/tags/2fa/)[login redirect](https://srd.wordpress.org/plugins/tags/login-redirect/)
 *  [Advanced View](https://srd.wordpress.org/plugins/secured-wp/advanced/)

## Ratings

 5 out of 5 stars.

 *  [  1 5-star review     ](https://wordpress.org/support/plugin/secured-wp/reviews/?filter=5)
 *  [  0 4-star reviews     ](https://wordpress.org/support/plugin/secured-wp/reviews/?filter=4)
 *  [  0 3-star reviews     ](https://wordpress.org/support/plugin/secured-wp/reviews/?filter=3)
 *  [  0 2-star reviews     ](https://wordpress.org/support/plugin/secured-wp/reviews/?filter=2)
 *  [  0 1-star reviews     ](https://wordpress.org/support/plugin/secured-wp/reviews/?filter=1)

[Your review](https://wordpress.org/support/plugin/secured-wp/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/secured-wp/reviews/)

## Contributors

 *   [ wpsecuredcom ](https://profiles.wordpress.org/wpsecuredcom/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/secured-wp/)