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.
Table of Contents
- 1. Why secrets management is more than hiding .env
- 2. Environment variables as the first stage of secrets management
- 3. Encryption at rest for sensitive configuration values
- 4. Vault integration: fetching secrets at runtime
- 5. Rotating credentials without downtime
- 6. Keeping secrets out of logs and error messages
- 7. Secrets management in CI/CD pipelines
- 8. Common mistakes in secrets management
- 9. Secret storage locations compared
- 10. Summary
- 11. FAQ
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.