WordPress secrets have historically lived in places that any security review would flag: the WordPress options table, plugin-specific tables, individual user meta records, and (worst of all) hardcoded values in plugin and theme code. The pattern grew up because WordPress predates the modern operational consensus around environment-variable secrets management, and because the WordPress ecosystem of plugins each made its own decisions about where to store API keys, tokens, and credentials. The result, for any non-trivial WordPress site in 2026, is a sprawl of secrets in places they shouldn’t be and a tangible operational risk that a database backup, a plugin compromise, or a routine database export becomes a credential leak.
WordPress 7.0’s Connectors API and the broader credential-resolution waterfall pattern that arrived alongside it have made env-var-based secrets management the new recommended default. This piece is the practical migration checklist for moving an existing WordPress site from database-stored secrets to environment-variable-based secrets, using the WordPress 7.0 patterns where applicable and falling back to standard patterns where the Connectors API doesn’t cover specific cases.
The short version is that the migration is straightforward but tedious. The inventory step is the largest part of the work because most WordPress sites have accumulated more secrets than the operations team realizes. The execution step is usually fast once the inventory is complete. The verification step is critical because missing a secret can produce silent failures that don’t surface until much later.
Why this matters now
Several developments through 2025 and 2026 have made the env-var-secrets migration more important than it had been:
The WordPress 7.0 Connectors API introduced a standardized credential-resolution waterfall (covered in our earlier piece on the Connectors API and again in the WordPress backup security post). The waterfall reads credentials in order: environment variables, then PHP constants, then the platform secrets backend (Pantheon Secrets, WP Engine secrets manager, similar), then the encrypted database storage. The order matters because the highest-precedence source wins. Putting secrets in environment variables means they’re consulted first; database storage becomes a fallback.
The Pantheon Secrets product launched in mid-2025 and made env-var-equivalent secrets management trivial for Pantheon-hosted sites (covered in our Pantheon Secrets piece). WP Engine and Kinsta followed with their own equivalents. The hosting-platform support means env-var secrets are no longer just for self-hosted sophisticated operations; they’re available to every host.
A series of high-profile WordPress security incidents through 2025 and 2026 included several where the attack vector was credentials stored in plugin options or in the database. The pattern is consistent: attacker gets database access through some other vulnerability, exfiltrates credentials, uses the credentials to compromise the connected third-party services. The mitigation is to not have the credentials in the database in the first place.
The combination of these developments means env-var secrets are now both easier to implement and more obviously the right choice for any site that takes security seriously.
Step 1: Inventory every secret in the database
The first step is finding what you actually have. The inventory typically reveals more secrets than the operations team expected. The systematic approach:
Search wp_options for known patterns. Run a query against the options table looking for option names containing patterns like _api_key, _token, _secret, _password, _credentials. Common patterns include mailchimp_api_key, stripe_secret_key, recaptcha_secret, and dozens of plugin-specific variations.
Inspect plugin-specific tables. Many plugins create their own tables for configuration. Check the database schema for tables named after plugins and look for columns that might hold credentials.
Search user meta for connection-token storage. Plugins that handle per-user authentication (social login, OAuth integrations with third-party services) often store tokens in user meta. Search the user_meta table for token-shaped data.
Inspect post meta on relevant post types. Some plugins store credentials in post meta tied to specific configuration posts. The pattern is rare but exists.
Grep the wp-config.php file for hardcoded values. Constants like define('STRIPE_SECRET', 'sk_live_...') are technically not in the database but are functionally equivalent for migration purposes; they should move to environment variables as well.
Grep the active theme and plugin directories for hardcoded values. This is the most painful part of the inventory because well-written plugins don’t have hardcoded credentials but poorly-written ones sometimes do. Use grep for patterns like API key formats specific to the services your site integrates with.
The output of the inventory step is a list of secrets with: the secret’s name (what it is), the secret’s current location (where it lives now), the secret’s value (the actual credential), and the consuming code (which plugins or themes use this secret).
For a typical mid-size WordPress site, the inventory often turns up 20 to 50 secrets across all categories. For sites with many third-party integrations, the count can be substantially higher.
Step 2: Plan the migration
With the inventory in hand, plan the migration:
Categorize each secret. Some secrets are application secrets (API keys for services the site calls). Some are session or authentication tokens (which may not need to migrate; they regenerate naturally). Some are encryption keys (which need careful handling because changing them invalidates existing encrypted data). The category determines the migration approach.
Identify rotation candidates. Any secret that has been exposed (in a database backup, in a code commit, in shared documentation) should be rotated before migration rather than migrated as-is. This is also a good opportunity to rotate secrets that have been in place for years without rotation.
Choose the target storage for each secret. Some secrets go into the platform secrets backend (Pantheon Secrets, WP Engine secrets manager). Some go into environment variables on the deployment infrastructure. Some go into a HashiCorp Vault or AWS Secrets Manager if the site is self-hosted with that infrastructure available.
Determine the consuming code’s read path. For each secret, identify how the consuming code currently reads it. The migration’s code-change part will modify each read site to use the new pattern.
Plan the rollout sequence. Migrate non-critical secrets first to verify the patterns work. Save the most-critical or most-difficult-to-rotate secrets for last when the migration approach is proven.
Plan the rollback. If the migration of any specific secret fails (the new read path doesn’t work, the secret value didn’t transfer correctly), how do you roll back to the old location? The standard answer is "leave the database value in place until the migration is verified, then delete it" but the specifics depend on the secret.
Step 3: Set up the target storage
Before migrating, set up the target storage for the secrets:
For Pantheon Secrets (Pantheon-hosted sites), install the Pantheon Secrets plugin and verify it’s working. Test by storing a non-critical secret and confirming the code can read it through the credential resolver.
For WP Engine secrets manager (WP Engine-hosted sites), enable the secrets manager feature in the WP Engine dashboard and set up the equivalent.
For environment variables on self-hosted sites, establish the pattern for how environment variables get set. Typical patterns include systemd unit files, Docker environment configuration, Kubernetes secrets, or shell profile files for traditional Linux servers.
For HashiCorp Vault or AWS Secrets Manager, install the appropriate WordPress integration plugin and configure access. The integration registers itself with the WordPress credential resolver waterfall so reads from those backends work transparently.
The target storage should be production-ready before you migrate any actual secrets. Don’t migrate to a half-configured backend; the risk of losing secrets in the transition is too high.
Step 4: Execute the migration
For each secret in the inventory:
Copy the value to the target storage. The new location should hold the secret value before any code changes happen so the new read path works as soon as it’s added.
Update the consuming code to read from the new location. For plugins and themes you control, this is a code change that uses the credential resolver (wp_ai_get_credential('service_name', 'key_name') if going through the Connectors API pattern, or the equivalent for direct env var reads). For plugins you don’t control, this is more complex; you may need to file an issue with the plugin maintainer, write a small wrapper plugin that intercepts the read, or accept that the secret stays in the database.
Verify the read works. Test the consuming code path to confirm it gets the right credential from the new location. The most common failure mode is a typo in the new location name; verifying immediately catches these.
Mark the database value for deletion but don’t delete yet. The standard pattern is to add a comment or note in the database record indicating it’s safe to delete after the migration is fully verified, but to leave the value in place as a fallback for the verification period.
Repeat for the next secret.
The execution is tedious because each secret is its own small workflow. Doing this for 50 secrets in a single session is a several-hour task. Spreading the work over a few days with verification between batches is the more sustainable approach.
Step 5: Verify
After all secrets are migrated, verify the site is functioning correctly:
Functional verification. Click through every feature of the site that uses any of the migrated secrets. Send a test email through any plugin that uses an email API. Process a test transaction through any payment integration. Test the user login if any authentication tokens were migrated.
Log inspection. Check error logs for any "credential not found" or similar errors. Plugins that didn’t migrate cleanly often log warnings rather than failing visibly; the warnings need to be caught.
Environment parity check. Verify the migration worked across all environments. A development environment that still uses the old database secrets while staging and production use the new env-var secrets is a configuration drift waiting to cause confusion.
Backup test. Trigger a backup. Verify the backup doesn’t include the migrated secrets in their old locations (because they should now be empty). The backup should be substantially smaller for the secret-related portions if the migration was successful.
Step 6: Clean up
Once verification is complete:
Delete the database values for migrated secrets. The cleanup removes the secrets from the database entirely so the migration is actually complete. Until this step, the secrets are technically still in both places.
Remove hardcoded values from wp-config.php. Replace any hardcoded define() statements with reads from environment variables, then verify wp-config.php no longer contains any secrets.
Document the migration. Create a runbook or documentation describing where each secret now lives, how the consuming code reads it, and how to update secrets in the future. The documentation is important because the operational pattern has changed and operators need to know.
Update onboarding documentation. New developers joining the project need to know that secrets live in environment variables now, not in the database. The onboarding docs need to reflect this.
Common pitfalls
A few specific issues that tend to arise during the migration:
Stale plugin caches. Some plugins cache credential lookups in transients or in-memory variables. After migration, the cache may still have the old value (or the now-deleted value), producing failures. Flush relevant caches after migration as part of the verification step.
Conditional environment configuration. Some sites have different secrets for different environments (one Stripe API key for production, a different one for staging). The migration must preserve this; environment-variable values typically differ per environment by design, which makes this easier than the database approach.
Encryption keys. Migrating encryption keys is special because changing the key invalidates existing encrypted data. The migration pattern is to set the new key in the env var, decrypt existing data with the old key, re-encrypt with the new key, then delete the old key. This is more work than other secrets but is essential for getting the encryption story right.
Plugins that resist migration. Some plugins are written in ways that don’t easily allow the credential read to be redirected to an env var. For these, the options are: file an issue with the plugin maintainer to request env-var support, write a small wrapper plugin that intercepts and redirects the credential read, or accept that the secret stays in the database (which is the wrong answer for security but sometimes the practical answer).
Multisite considerations. Multisite installations have per-site secrets and network-wide secrets. The migration plan needs to handle both. Per-site secrets typically need per-site env var values (which the multisite host’s tooling needs to support); network-wide secrets are simpler.
After the migration
Once the migration is complete and the site is running on env-var secrets, the ongoing operational pattern shifts:
Adding new secrets uses the env-var pattern from the start rather than going through the database. New integrations that need a secret get the secret added to the env-var configuration on each environment, with no database write.
Rotating secrets is easier because the rotation is an env-var update rather than a database update. Updating a single environment variable propagates to all the code that reads it without database writes or cache invalidation concerns.
Backups are safer because the database no longer contains the most sensitive credentials. A leaked database backup is still a problem (user data, post content, post meta) but the blast radius of credential exposure is smaller.
Code reviews change because reviewers now expect to see env-var reads rather than database reads for credentials. New code that follows the old pattern should be flagged in review.
The migration is a one-time investment that pays back continuously through the lifetime of the site. The first migration is the hardest; subsequent secrets (new integrations, new third-party services) get added through the env-var pattern from the start without requiring another migration.
Frequently asked questions
How long does the migration typically take? For a small site with under 10 secrets, a few hours. For a mid-size site with 20 to 50 secrets, a few days of work spread across inventory, planning, execution, and verification. For a large enterprise site with hundreds of secrets, potentially a few weeks.
Can I do this without downtime? Yes, if you migrate one secret at a time and verify each before moving on. The database values stay in place as fallbacks until verification is complete, which prevents downtime from a botched migration.
Do I need WordPress 7.0? No. The migration pattern works on WordPress 6.x as well. WordPress 7.0’s Connectors API standardizes the credential resolver pattern, but env-var-based credential reads have been possible in WordPress for years.
What if my host doesn’t support env-var secrets management? Self-hosted sites can use the host’s standard environment variable mechanism (systemd, Docker, etc.). Managed hosts that don’t have a secrets manager typically allow environment variables through their control panel or configuration files. If your host genuinely doesn’t support env vars at all, the platform may not be appropriate for production WordPress in 2026.
How does this affect my plugin licensing? Plugin license keys are credentials. They should migrate to env vars the same way other credentials should. Plugins that expect their license key in a specific database option may need updates to read from env vars; this is the same plugin-modification work as for other credentials.
Should the database encrypted-storage fallback be deleted? Eventually yes, but only after you’re confident the migration is complete and stable. Keeping the encrypted storage as a fallback for a few weeks after migration is reasonable; keeping it indefinitely defeats the purpose of the migration.
Does this affect site backups? The backup should be smaller because it no longer contains the credentials. Test that backups are still valid and that restore works after the migration; the most-common backup-restore issue post-migration is forgetting that the restore needs to also restore the env-var configuration.
Can I migrate gradually rather than all at once? Yes, and this is often the better approach. Migrate the most-critical secrets first to address the biggest security risk, then migrate the less-critical ones over time as opportunity allows. Partial migration is meaningfully better than no migration.
What about secrets used in CI/CD pipelines? Pipeline secrets (deploy keys, API tokens for the deployment process) should be in the CI/CD platform’s secrets management (GitHub Actions secrets, GitLab CI variables, etc.), not in the WordPress database. The migration discussed here is for runtime application secrets; CI/CD secrets are a separate (and equally important) topic.
Does WordPress 7.0’s Connectors API work with non-Connectors-API plugins? The credential resolver waterfall is plugin-agnostic. Any plugin can read a credential through the resolver (using wp_ai_get_credential() or the equivalent), regardless of whether the plugin is built on the Connectors API. The resolver is the standardization layer; the consuming plugins choose how to use it.