WordPress Secrets Management: Vault, Doppler, and Env-Based Config

Hardcoding API keys and database passwords in wp-config.php — even with restrictive file permissions — creates a single point of secret compromise: anyone with read access to the file system has all credentials. A secrets manager (HashiCorp Vault, Doppler, AWS Secrets Manager, or 1Password Secrets Automation) rotates, audits, and injects secrets at runtime, so your repository and filesystem never contain production credentials.

Problem: WordPress wp-config.php stores API keys, database credentials, and payment gateway secrets in plain text — these leak into version control, server backups, and staging environments where they should not be accessible.

Solution: Use an external secrets manager: integrate with HashiCorp Vault via the Vault PHP SDK, fetch secrets at runtime with $client->read('secret/wordpress/stripe'), and cache them in a short-lived transient. For simpler setups, use Doppler or AWS Secrets Manager with a PHP SDK and store only the authentication token in wp-config.php, not the secrets themselves. Never commit secrets to Git.


The code below shows three patterns: environment variable injection from Doppler via a wp-config.php that reads from $_ENV, a HashiCorp Vault AppRole fetch in a WP-CLI command, and a wp-config.php pattern that pulls secrets from AWS Secrets Manager at bootstrap.


 wp_json_encode( [ 'role_id' => $role_id, 'secret_id' => $secret_id ] ),
        'headers' => [ 'Content-Type' => 'application/json' ],
        'timeout' => 5,
    ] );
    if ( is_wp_error( $login ) ) {
        wp_die( 'Vault authentication failed.' );
    }
    $token = json_decode( wp_remote_retrieve_body( $login ), true )['auth']['client_token'] ?? '';

    // Read secret
    $secret_response = wp_remote_get( "$vault_addr/v1/secret/data/$path", [
        'headers' => [ 'X-Vault-Token' => $token ],
        'timeout' => 5,
    ] );
    $value = json_decode( wp_remote_retrieve_body( $secret_response ), true )['data']['data'][ $field ] ?? '';

    wp_cache_set( $cache_key, $value, 'vault', 300 );  // cache 5 minutes
    return (string) $value;
}

// Usage in wp-config.php (before WordPress loads):
// define( 'DB_PASSWORD', wp_vault_get_secret( 'wordpress/prod', 'db_password' ) );


# ── Doppler: inject secrets as env vars at process start ─────────────────
# Install Doppler CLI
brew install dopplerhq/cli/doppler

# Authenticate
doppler login
doppler setup  # link to your Doppler project and config

# Run WP-CLI with Doppler-injected secrets
doppler run -- wp core install --url=https://example.com ...

# Run PHP-FPM with Doppler secrets (systemd service override)
# /etc/systemd/system/php8.2-fpm.service.d/doppler.conf:
# [Service]
# ExecStart=
# ExecStart=/usr/bin/doppler run -- /usr/sbin/php-fpm8.2 --nodaemonize


NOTE: Fetching secrets from Vault or AWS Secrets Manager in wp-config.php on every request adds 5–50ms of network latency — always cache the result in PHP's apcu_store() (within a single FPM process lifecycle) or in the WordPress object cache with a short TTL, and ensure your secrets manager endpoint is in the same VPC/datacenter as the WordPress server to minimise latency.