Secrets Management in PHP Applications
AI generated
<?php
8.4
PHP · Security · DevOps · Credentials
Secrets Management in PHP Applications
from .env files to full vault integration

Credentials end up far too often directly in the repository, in plaintext configuration files or as a hard coded constant right in the middle of the code. Solid secrets management separates configuration from code, encrypts values at rest and turns rotation into a routine task instead of an emergency after a security incident.

18 min read .env · encryption · vault · rotation PHP 8.x · framework agnostic

1. Why secrets management is more than hiding .env

Secrets management describes the full lifecycle of credentials: generation, storage, distribution to applications, rotation and eventual revocation. In many PHP projects, secrets management is reduced to adding a .env file to .gitignore and hoping nobody commits it by accident. That is a start, but not complete secrets management, since it covers neither encryption at rest nor a rotation strategy nor centralized access control.

The difference becomes visible the moment a secret is compromised. Without thoughtful secrets management, a leaked database password often means manually going through every server, adjusting every configuration file, and hoping no copy was overlooked. With established secrets management, rotation is a single command, because there is exactly one source of truth for each secret that every application instance reads from at runtime.

2. Environment variables as the first stage of secrets management

The entry point into solid secrets management is a strict separation of code and configuration through environment variables. The .env file never contains hard coded values in the code, instead it is read at runtime by a library such as vlucas/phpdotenv and exposed through getenv() or $_ENV. Important for clean secrets management: the .env file itself never belongs in version control, only a .env.example with placeholder values documents which variables are required.

A frequently overlooked aspect of environment variables is that they can be visible through phpinfo(), other users' process listings on the same host, or debugging tools. For strict secrets management, plain environment variables are therefore not sufficient for highly sensitive values such as encryption keys, which is where the mechanisms described in the next section come in.


<?php

declare(strict_types=1);

use Dotenv\Dotenv;

require __DIR__ . '/vendor/autoload.php';

// Load .env only in local/dev — never ship it to production servers
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
$dotenv->required(['DB_PASSWORD', 'API_SECRET_KEY'])->notEmpty();

final class Config
{
    public static function dbPassword(): string
    {
        $value = $_ENV['DB_PASSWORD'] ?? null;
        if ($value === null || $value === '') {
            throw new RuntimeException('DB_PASSWORD is not configured');
        }
        return $value;
    }
}

3. Encryption at rest for sensitive configuration values

For secrets that must live in a configuration file or a database, encryption at rest is the next stage of secrets management. PHP offers modern authenticated encryption through the sodium extension, a core part of PHP since version 7.2. The plaintext is encrypted with a master key that itself does not live in the same repository, but is provided through an environment variable or a dedicated key service.

The decisive point of this approach within secrets management: the master key is never stored together with the encrypted values. If an encrypted configuration file sits in the repository but the master key exists exclusively on the production server, a repository leak has no consequences for the actual credentials. This separation is the core of every serious secrets management strategy.


<?php

declare(strict_types=1);

final class SecretBox
{
    public function __construct(private readonly string $masterKeyBase64)
    {
    }

    /**
     * Encrypt a secret value at rest using libsodium authenticated encryption.
     */
    public function encrypt(string $plaintext): string
    {
        $key = sodium_base642bin($this->masterKeyBase64, SODIUM_BASE64_VARIANT_ORIGINAL);
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);

        return base64_encode($nonce . $ciphertext);
    }

    /**
     * Decrypt a value produced by encrypt().
     */
    public function decrypt(string $encoded): string
    {
        $key = sodium_base642bin($this->masterKeyBase64, SODIUM_BASE64_VARIANT_ORIGINAL);
        $decoded = base64_decode($encoded, true);
        $nonce = substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $ciphertext = substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

        $plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
        if ($plaintext === false) {
            throw new RuntimeException('Secret decryption failed — key mismatch or tampered data');
        }
        return $plaintext;
    }
}

// Master key comes from an environment variable, never from the repository
$box = new SecretBox(getenv('SECRETS_MASTER_KEY'));
$stored = $box->encrypt('db-password-123');

4. Vault integration: fetching secrets at runtime

Beyond a certain team and infrastructure size, local encryption in secrets management is complemented by a dedicated secrets service such as HashiCorp Vault or a cloud native secrets manager. The central benefit: secrets never live in the application code or in configuration files, but are fetched via an authenticated API call at application startup and kept exclusively in memory. Access rights, audit logs and automatic expiration are handled server side by the vault itself.

For PHP applications this means in practice: a small HTTP client talks to the vault API during bootstrap, authenticates with a short lived token, for example via Kubernetes service account tokens, and fetches the required secrets. This pattern within secrets management significantly reduces the attack surface, because compromised application code alone no longer grants lasting access to the secrets, provided the token is short lived and tightly scoped.


<?php

declare(strict_types=1);

final class VaultSecretClient
{
    public function __construct(
        private readonly string $vaultAddr,
        private readonly string $token,
    ) {
    }

    /**
     * Fetch a secret from Vault's KV v2 engine at request time.
     *
     * @return array<string, string>
     */
    public function read(string $path): array
    {
        $ch = curl_init(sprintf('%s/v1/secret/data/%s', $this->vaultAddr, $path));
        curl_setopt_array($ch, [
            CURLOPT_HTTPHEADER => ['X-Vault-Token: ' . $this->token],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 3,
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        /** @var array{data: array{data: array<string, string>}} $decoded */
        $decoded = json_decode($response ?: '', true, flags: JSON_THROW_ON_ERROR);
        return $decoded['data']['data'];
    }
}

$vault = new VaultSecretClient('https://vault.internal:8200', getenv('VAULT_TOKEN'));
$dbCredentials = $vault->read('database/production');

5. Rotating credentials without downtime

Rotation is the part of secrets management that gets neglected most often, because it never feels urgent without a concrete trigger such as a security incident. Yet regular rotation is exactly the mechanism that turns a secret leak from a permanent problem into a time limited window. For database credentials this means: a new user with a new password is created, the application gradually switches to the new credentials, and only afterward is the old user disabled.

For uninterrupted rotation within secrets management it is crucial that old and new credentials remain valid in parallel during a transition period. Services such as Vault support this with dynamic, time limited database credentials generated directly from the database engine, so every application instance automatically receives its own short lived credentials instead of sharing a single static password with every other instance.

6. Keeping secrets out of logs and error messages

An often overlooked channel through which secrets escape secrets management is log files and error messages. A careless var_dump() of a configuration object, an exception with the full database DSN in the stack trace message, or an API client that logs request headers including the authorization token, undermine even the most sophisticated rotation and encryption strategy.

The pragmatic approach within secrets management: a central logging library automatically masks known secret keys, for instance through a denylist of field names such as password, token or secret, whose values are replaced with ***REDACTED*** before being written to the log. Exception handlers should also never output the full exception message including potentially embedded credentials to the client, only a generic error message, while the details are logged internally only.

7. Secrets management in CI/CD pipelines

Build and deployment pipelines are another critical point in secrets management, because they almost always need access to production adjacent credentials, for example for database migrations or deployment targets. Secrets belong in the encrypted secrets store of the CI platform, never as a plaintext variable in the pipeline script or as a comment in a configuration file.

In addition, pipeline logs should mask by default whenever a variable marked as a secret appears in a command's output. For sustainable secrets management in CI/CD it also holds that every pipeline should only get access to the secrets it actually needs for its own stage, instead of blanket exposing every production secret to every build job.

8. Common mistakes in secrets management

The most common mistake is committing the .env file, usually accidentally during initial setup before .gitignore was configured correctly. Since Git history is permanent, deleting the file afterward is not enough, the entire secret must be rotated once it has been committed. A second mistake in secrets management is reusing the same secret across multiple environments, so that a leak in staging automatically compromises production as well.

A third, more subtle mistake: secrets are stored encrypted, but the decryption key sits right next to the encrypted file in the same directory or repository. This creates an illusion of security without providing real protection, because both parts are compromised together. Consistent secrets management always separates keys and encrypted values across different access paths, ideally across different systems entirely.

9. Secret storage locations compared

Depending on the maturity of the infrastructure, different storage locations within secrets management are better or worse suited. The overview below classifies the common options.

Storage location Risk Suited for Rotation
Hard coded in the code Very high Never use Requires a deployment
.env file on the server Medium Small projects, single server Manual, error prone
Encrypted config file Low to medium Mid size teams, repository sync Scriptable
Dedicated vault/secrets manager Low Growing infrastructure, compliance Automated, time limited
Dynamic DB credentials Very low High security requirements Automatic per instance

The table shows a clear trend in secrets management: the more centralized and automated storage and rotation are, the lower the residual risk in the event of a leak. A single compromised process can hardly cause lasting damage when credentials are dynamic and short lived.

Mironsoft

PHP security audits, vault integration and deployment hardening

Ready to manage credentials centrally and rotate them safely?

We analyze where secrets currently end up in your PHP application, build thoughtful secrets management with encryption and vault integration, and set up a rotation strategy that causes no outages even in a real incident.

Secrets audit

Full inventory of all credentials in code, config and CI/CD

Vault integration

Clean implementation of HashiCorp Vault or cloud secrets manager connections

Rotation strategy

Uninterrupted rotation for databases, APIs and internal services

10. Summary

Solid secrets management in PHP is a staged model: environment variables separate code and configuration, encryption at rest protects values that must still be stored, and vault integration removes secrets entirely from code and file system in favor of a central, auditable service. Rotation turns a one time leak into a time limited risk instead of a permanent problem.

In the end, secrets management pays off exactly when something goes wrong: a compromised secret can be rotated in minutes instead of days, because there is exactly one source of truth. Logs and error messages that are consistently scrubbed of secrets additionally prevent an otherwise harmless debugging session from becoming the actual vulnerability.

Secrets Management in PHP Applications — The essentials at a glance

Separating code and config

Load environment variables via .env, never hard code, never commit .env to the repository.

Encryption at rest

sodium for authenticated encryption, master key stored separately from the encrypted value.

Vault instead of a file

Central secrets service with audit log, short lived tokens and dynamic credentials.

Rotation as routine

Regular rotation instead of an emergency measure, old and new credentials valid in parallel during transition.

11. FAQ: Secrets Management in PHP Applications

1Is .env enough as complete secrets management?
No, good first step, but covers neither encryption nor rotation nor centralized access control.
2.env accidentally committed, now what?
Rotate the entire secret, since the old version remains retrievable through Git history.
3When is vault worth it over files?
As soon as multiple teams, environments or servers need centralized access control with an audit log.
4Encryption without external dependency?
sodium extension, core part of PHP since 7.2, provides authenticated encryption without Composer packages.
5How often should credentials rotate?
Common practice is 30 to 90 days for databases, shorter for dynamic, automatically generated credentials.
6How do I keep secrets out of logs?
Logging library masks known field names automatically, exception handlers never expose internal details to clients.
7Static vs dynamic, which is better?
Dynamic, per instance credentials from a vault limit the damage of a leak far more effectively.
8Treat secrets differently in CI/CD?
Yes, every pipeline stage gets only the secrets it needs, logs automatically mask variables marked as secrets.
9How do I protect the master key?
Never in the same repository as the encrypted values, use an environment variable or a separate key service instead.
10Worth it for small projects?
Yes, separating code and configuration alone prevents the most common leak cause, vault usually pays off later.