Blog

WordPress 7.2’s WP_SECRETS_KEY warning, what it means and how to fix it

red padlock on black computer keyboard

If you have tried a WordPress 7.2 beta, Site Health now shows a new recommended improvement under Security: “Secrets API is using a fallback key”. Open it and you get “No dedicated WP_SECRETS_KEY constant is defined. Secrets are encrypted using a key derived from existing salts, which works but is not the recommended configuration.” Every site that updates to 7.2 without the constant will see it, and 7.2 is due on December 8.

The short answer: nothing is broken, and the fix is one line in wp-config.php. But in one case that line locks you out of stored credentials, and the command the warning names is not in the standard WP-CLI. Here is what I would check first.

Key takeaways

  • Nothing is broken, and the fix is one line in wp-config.php. The warning only means WordPress 7.2 is encrypting stored secrets with a key derived from LOGGED_IN_KEY and LOGGED_IN_SALT because no dedicated WP_SECRETS_KEY exists. arvindkumar.ca
  • The fallback ties your stored secrets to your salts, so regenerating the salts makes stored secrets undecryptable. A dedicated key lets you rotate salts without touching stored credentials. arvindkumar.ca
  • Before adding the key, check for the _wp_secrets_root_key option using Site Health Info, WP-CLI or the options table. If it does not exist, nothing is stored and you are clear to add the key. arvindkumar.ca
  • To add the key, generate 32 random bytes, base64 encode them and define the result as WP_SECRETS_KEY in wp-config.php. Keep a copy in your password manager, because the key lives only in that file. arvindkumar.ca
  • The wp secret generate-key command is not in standard WP-CLI, because it lives in the Secrets API feature plugin. The PHP or OpenSSL one-liner gives the same result. arvindkumar.ca
  • If secrets are already stored, leave the fallback in place until you can rotate properly. Adding WP_SECRETS_KEY now would make every stored secret unreadable, and you should not regenerate salts until you have a dedicated key. arvindkumar.ca
  • Secrets only decrypt where the same key exists, so decide the key for each staging copy. The same key brings live credentials across, while a different key means nothing copied from production decrypts there. arvindkumar.ca

Summarised from this post, formatted for answer engines.

What does the WP_SECRETS_KEY warning mean?

It means WordPress is encrypting stored secrets with a key built from your LOGGED_IN_KEY and LOGGED_IN_SALT, because you have not given it one of its own. WordPress 7.2 adds a Secrets API: functions such as wp_set_secret() and wp_get_secret() that let plugins store API keys encrypted instead of in plain options. Encryption is always on, so it needs a key.

Reading the 7.2 source, the key is chosen in this order:

  1. WP_SECRETS_KEY, if it is a base64 string that decodes to exactly 32 bytes. This is the recommended form, and Site Health marks it good.
  2. WP_SECRETS_KEY in any other shape, which is hashed and still works, but Site Health then says “Secrets API key should be regenerated”.
  3. No WP_SECRETS_KEY at all: a hash of LOGGED_IN_KEY and LOGGED_IN_SALT. That is the fallback the warning is about.

The backticks around wp secret generate-key in the Site Health text are printed literally. They are not part of the command.

This warning may not last. On October 30, Eric Mann opened a patch for review that removes the fallback. Without a usable WP_SECRETS_KEY, a site could not store secrets at all, and Site Health would show a generated line to add to wp-config.php. It is not committed yet. Either way, the advice is the same: add the key while nothing is stored.

Is my site at risk if I ignore the warning?

Not today, but the fallback ties your stored secrets to your salts, and salts are something people change. If you regenerate the salts after a security scare, or a security plugin does it for you, the key derived from them changes too. Anything stored through the Secrets API can then no longer be decrypted, and Site Health raises a critical “Some secrets cannot be decrypted” result. Its own text is blunt: “There is no way to recover the original value. The credential must be re-entered.”

That line is right for a salt change, where the old key is gone for good. It is misleading in one case. If the secrets stopped decrypting because you added WP_SECRETS_KEY after they were stored, remove the constant and they all read again. Eric Mann, who wrote the Secrets API, confirmed this on Trac #66380 on October 26, and noted that re-entering does not even work in that case because writes are blocked too. The patch in review rewords the message.

A dedicated key separates those two jobs, so you can rotate salts without touching stored credentials. That will matter more soon: the AI team’s weekly summary on Make WordPress talks about connecting the Secrets API to WordPress connectors so AI provider API keys get encrypted storage. I covered keeping those AI features under control in how to limit WordPress AI features by role.

How do I check whether my site already stores secrets?

Check for the root key option, because that is what a new WP_SECRETS_KEY would make unreadable. The API keeps one wrapped root key in an option called _wp_secrets_root_key, created the first time something stores a secret. Core itself stores no secrets in 7.2, so on most sites it does not exist yet.

  1. Open Tools, Site Health, Info and expand the “Secrets API” section. Look at “Key source” and “Site secrets”. If Site secrets shows 0, nothing is stored yet.
  2. Confirm with WP-CLI: run wp option get _wp_secrets_root_key (on multisite, wp site option get _wp_secrets_root_key). If WP-CLI reports that the option does not exist, you are clear to add the key.
  3. Without WP-CLI, search the options table in phpMyAdmin or your host’s database tool for an option_name of _wp_secrets_root_key. No row means no root key.

How do I add WP_SECRETS_KEY to wp-config.php?

Generate 32 random bytes, base64 encode them and define the result as a constant in wp-config.php. Do this only after the check above came back empty.

  1. Back up wp-config.php. It worked when you have a copy you can restore over SFTP or your host’s file manager.
  2. Generate the key with one of the commands below. Either prints a single line of 44 characters ending in =.
  3. Add the define line above the “That’s all, stop editing!” comment, next to your salts. Use single quotes and paste the key exactly.
  4. Save the key in your password manager as well. It lives only in wp-config.php, so a database backup restored without it cannot decrypt anything.
  5. Reload Tools, Site Health. The result should now read “Secrets API is using a dedicated key”, and Key source on the Info tab should say WP_SECRETS_KEY (base64-encoded 32 bytes).

To generate the key with PHP:

php -r 'echo base64_encode( random_bytes( 32 ) ), PHP_EOL;'

Or with OpenSSL, which most Linux and macOS machines have:

openssl rand -base64 32

Then the line in wp-config.php:

define( 'WP_SECRETS_KEY', 'paste-your-44-character-key-here' );

Do not reuse a salt or type a phrase yourself. Anything that is not a clean base64 encoding of 32 bytes lands in the “should be regenerated” case.

Why does wp secret generate-key not work?

Because the wp secret commands are not in WordPress or the standard WP-CLI bundle at the time of writing. They live in the Secrets API feature plugin, whose documentation says nothing in its CLI folder is proposed for core.

Core knows. Trac #66380, opened on October 25 after testing Beta 1, has a patch in review that drops the command from the Site Health text, adds a critical “Secrets API cannot unlock the stored secrets” result when the key cannot unwrap the root key, and only recommends a dedicated key before any secret is stored. Eric Mann, who wrote the Secrets API, took that patch over on October 30, and it now builds on the change described below. It did not make Beta 2 or Beta 3, so until it lands the Site Health text will keep naming a command you do not have.

You lose nothing with the PHP or OpenSSL command. In the feature plugin’s source, generate-key just prints base64_encode( random_bytes( 32 ) ), the same as the PHP one-liner above. If you only have WP-CLI and no shell PHP, wp eval 'echo base64_encode( random_bytes( 32 ) ), PHP_EOL;' gives the same result.

What if secrets are already stored?

Leave the fallback in place until you can rotate properly, because adding WP_SECRETS_KEY now would make every stored secret unreadable. If you use the feature plugin’s WP-CLI commands, its 0.4.0 notes give the way out: set WP_SECRETS_KEY to a new value, set WP_SECRETS_KEY_PREVIOUS to your LOGGED_IN_KEY and LOGGED_IN_SALT joined together, run wp secret rotate, then remove WP_SECRETS_KEY_PREVIOUS. The root key was wrapped with the salt-derived key, and WordPress fails closed: it will not quietly fall back to the salts.

The API is built for this move. A second constant, WP_SECRETS_KEY_PREVIOUS, holds the old key while a rotate command re-wraps the root key under the new one, without re-encrypting any secret. Today that rotate command is also only in the feature plugin’s WP-CLI commands, so for most sites the practical plan is:

  • Keep the fallback, and do not regenerate your salts until you have a dedicated key.
  • List what is stored with wp option list --search='_wp_secret_*' --fields=option_name. Each secret sits in an option named after it, so you can see which plugins own credentials you would have to re-enter if anything goes wrong.
  • Watch the 7.2 betas, release candidates and WP-CLI before December 8, because the fallback may be removed and moving the wp secret commands into WP-CLI has been proposed.

What happens to secrets on staging sites and migrations?

They only decrypt where the same key exists, and the API has no export by design. The Make Core proposal says migrations and staging pushes mean entering values again at the destination.

So decide per staging copy. Give staging the same WP_SECRETS_KEY and live credentials come across with the database, which means staging can call a live payment or AI provider. Give it a different key and nothing copied from production decrypts there, and Site Health on staging will say so. Either way, make it a decision rather than whatever the copy script happened to do. What I check after every database push is the Secrets API section of Site Health, before anyone tests a form or an AI feature on staging.

If you are on a 7.2 beta, do the root key check today and add the constant while nothing is stored yet. It takes two minutes now, and it saves a forced re-entry of every API key the first time someone rotates your salts.

4 questions about the WordPress 7.2 WP_SECRETS_KEY warning, answered

What does "Secrets API is using a fallback key" mean in WordPress 7.2?

It means no dedicated WP_SECRETS_KEY constant is defined, so WordPress encrypts stored secrets with a key derived from your LOGGED_IN_KEY and LOGGED_IN_SALT. It works, but it is not the recommended configuration.

How do I add WP_SECRETS_KEY to wp-config.php?

Generate 32 random bytes, base64 encode them and define the 44-character result as WP_SECRETS_KEY above the "That's all, stop editing!" comment. Only do this after confirming no secrets are stored yet.

Why does the wp secret generate-key command not work?

The wp secret commands are not in WordPress or the standard WP-CLI bundle at the time of writing. They live in the Secrets API feature plugin, and a PHP or OpenSSL command produces the same key.

What happens to my stored secrets if I regenerate my WordPress salts?

If you rely on the fallback key, it changes along with the salts, so anything stored through the Secrets API can no longer be decrypted. Site Health then shows a critical "Some secrets cannot be decrypted" result.

Read next