Question

Secure Database Passwords in PHP: Environment Variables and Secret Management

phpdatabasesecurity

Question

A PHP application needs database credentials to create a connection. If the application uses one database account with the minimum required permissions, PHP must still access that username and password somewhere. What is the best way to store and secure the database password? Is placing it directly in PHP source code a safe approach?

Short Answer

You will learn why database credentials must be available to an application, why they should not be committed in source code, and how to provide them safely through environment variables, protected deployment configuration, or a secret manager.

Concept

A database password is a secret: a value that grants access to a protected system. A PHP application cannot connect to a database without receiving its credentials somehow, so the goal is not to make the password disappear. The goal is to keep it out of places where it can be accidentally exposed.

Putting a password directly in application source code creates several risks:

  • It can be committed to Git and copied into repository history.
  • It may be visible to every developer who can read the code.
  • It can accidentally appear in backups, code snippets, logs, or error reports.
  • Changing the password requires changing and redeploying source code.

A common approach is to inject credentials at runtime through environment variables. In more mature deployments, a platform configuration system or a dedicated secret manager supplies those values.

Security also depends on the database account itself. Give the application's database user only the permissions it truly needs. For example, a reporting application may need SELECT access but should not have permission to delete tables or create new users.

Mental Model

Think of your PHP code as a recipe and the database password as a key to a locked kitchen.

The recipe may say, “Use the kitchen key,” but printing the key's combination in every copy of the recipe is unsafe. Instead, keep the key in a controlled key cabinet and give it to the cook only while they are working.

  • Source code is the recipe.
  • Environment variables or a secret manager are the key cabinet.
  • The running PHP process is the cook receiving the key when needed.
  • Least-privilege database permissions limit which rooms the key can open.
Take Quiz

Syntax and Examples

In PHP, read configuration from environment variables with getenv() or $_ENV. Validate that required values exist before attempting a connection.

<?php
$host = getenv('DB_HOST');
$port = getenv('DB_PORT') ?: '3306';
$name = getenv('DB_NAME');
$user = getenv('DB_USER');
$password = getenv('DB_PASSWORD');

if (!$host || !$name || !$user || $password === false) {
    throw new RuntimeException('Database configuration is missing.');
}

$dsn = "mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4";

$pdo = new PDO($dsn, $user, $password, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

The credentials are not embedded in the PHP file. Before starting PHP, the server or deployment environment provides values such as:

DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=app_db
DB_USER=app_user
DB_PASSWORD=a-long-unique-secret

For local development, a .env file can be convenient when used with a library such as vlucas/phpdotenv. Treat that file as a secret: do not commit it.

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

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();

$password = $_ENV['DB_PASSWORD'] ?? null;

Add the real local file to .gitignore:

.env

You may commit a .env.example file containing variable names but no real passwords.

Step by Step Execution

Consider this connection setup:

<?php
$host = getenv('DB_HOST');
$user = getenv('DB_USER');
$password = getenv('DB_PASSWORD');

if ($host === false || $user === false || $password === false) {
    throw new RuntimeException('Required database environment variables are not set.');
}

$pdo = new PDO(
    "mysql:host={$host};dbname=app_db;charset=utf8mb4",
    $user,
    $password,
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

Execution flow:

  1. PHP asks the operating system environment for DB_HOST.
  2. PHP reads DB_USER and DB_PASSWORD the same way.
  3. If one value is absent, getenv() returns false; the guard clause stops the program with a clear configuration error.
  4. PHP builds a MySQL DSN using the host and database name.
  5. PDO sends the username and password to MySQL to authenticate.
  6. If the credentials are incorrect or access is denied, PDO throws an exception because exception mode is enabled.

The password is still in memory briefly because the application needs it to connect. The important improvement is that it is not part of the version-controlled source code.

Real World Use Cases

  • Web applications: A production server injects DB_PASSWORD into PHP-FPM, Apache, Docker, or a hosting platform's configuration.
  • Docker and Kubernetes: A container receives credentials through Docker secrets, Kubernetes Secrets, or a cloud platform integration rather than a committed config file.
  • Cloud-hosted applications: The application fetches credentials from a managed secret service, such as a cloud secret manager, using the workload's identity.
  • Command-line scripts: A scheduled PHP import job reads the same environment configuration as the web application without duplicating credentials in the script.
  • Multiple environments: Development, test, staging, and production each provide different values for the same variable names, such as DB_HOST and DB_PASSWORD.
Take Quiz

Real Codebase Usage

Real projects usually centralize database configuration in one place rather than creating connections throughout the codebase.

A small configuration function can fail early when a required secret is missing:

<?php
function requiredEnv(string $name): string
{
    $value = getenv($name);

    if ($value === false || $value === '') {
        throw new RuntimeException("Missing required environment variable: {$name}");
    }

    return $value;
}

function createPdo(): PDO
{
    $host = requiredEnv('DB_HOST');
    $database = requiredEnv('DB_NAME');
    $username = requiredEnv('DB_USER');
    $password = requiredEnv('DB_PASSWORD');

    $dsn = "mysql:host={$host};dbname={$database};charset=utf8mb4";

    return new PDO($dsn, $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);
}

Common production practices include:

  • Centralized configuration: One configuration module or framework configuration file reads settings.
  • Fail-fast validation: Missing configuration stops startup rather than causing confusing connection failures later.
  • Least privilege: Separate database users may be used for the web app, migrations, background workers, and read-only reporting.
  • Secret rotation: A secret manager or deployment configuration can update a password without editing application code.
  • Safe logs: Applications log that a connection failed, but never log the DSN if it includes credentials and never log the password.
  • TLS connections: When the database is on another machine, configure encrypted database transport as supported by the database driver and server.

Common Mistakes

Committing credentials to source control

// Do not do this.
$pdo = new PDO('mysql:host=db;dbname=app', 'app_user', 'my-password');

Even if the repository is private, credentials can spread through commit history, backups, forks, and developer machines. Use runtime configuration instead. If a secret was committed, rotate it immediately; removing the current line does not remove old Git history.

Treating Base64 encoding as encryption

// This does not secure a password.
$password = base64_decode('bXktcGFzc3dvcmQ=');

Base64 is reversible encoding, not protection. If the decryption key is stored alongside encrypted credentials, an attacker who can read the application often can read both.

Putting a secret file inside the public web directory

A file such as public/config.php or public/.env may be downloaded if the server is misconfigured. Keep non-public files outside the document root when possible, and deny direct access as a defense in depth measure.

Committing .env

A .env file is useful locally, but it is still a plaintext secret file. Add it to .gitignore and commit a sanitized .env.example instead.

Logging passwords or full connection strings

// Do not do this.
error_log("Could not connect with password: {$password}");

Logs are widely copied and retained. Log a general error, and use exception details carefully without printing secrets.

Giving the application an administrator account

Avoid connecting as MySQL root, PostgreSQL postgres, or another powerful administrative user. Create a dedicated account with only the required privileges.

Assuming environment variables are automatically safe

Environment variables keep secrets out of source code, but processes and diagnostics on the same host may be able to inspect them depending on the operating system and configuration. Restrict server access, avoid printing environments, and use a proper secret manager when your platform provides one.

Comparisons

ApproachSuitable useMain advantageMain concern
Password hard-coded in PHPAvoid for real applicationsSimple to demonstrateEasily committed, copied, and exposed
.env file outside GitLocal development and small deploymentsConvenient per-environment configurationPlaintext file must have restrictive permissions and stay out of Git
Operating-system or hosting environment variablesCommon production deploymentSeparates code from deployment secretsMust secure process/server access and avoid diagnostic leaks
Secret managerProduction systems and teamsAccess control, auditing, rotation, centralized storageRequires platform setup and application integration
Encrypted config file with key in same appUsually not useful aloneMay hide casual viewingDoes not protect against someone who can read the app and its key

Environment variables are generally a configuration delivery mechanism, while a secret manager is a system designed to store, control, and rotate secrets. They are often used together: the platform authenticates the application, then injects or retrieves the secret at runtime.

Take Quiz

Cheat Sheet

// Read a required secret.
$password = getenv('DB_PASSWORD');

if ($password === false || $password === '') {
    throw new RuntimeException('DB_PASSWORD is required.');
}
  • Keep real passwords out of PHP source code and Git.
  • Use environment variables, protected server configuration, or a secret manager.
  • Use .env only as a local/development convenience unless your deployment explicitly protects it.
  • Add .env to .gitignore; commit .env.example without values.
  • Keep secret files outside the public document root where possible.
  • Use a dedicated database user, never an administrator account.
  • Grant only needed permissions: least privilege.
  • Do not print passwords, environment dumps, or credential-bearing DSNs in logs.
  • Rotate a password immediately if it is exposed.
  • Use encrypted database connections when the app and database communicate over an untrusted network.

FAQ

Should I put my PHP database password in a config file?

A config file outside the web root can be acceptable for a small, carefully managed deployment if it has restrictive file permissions and is excluded from Git. Environment variables or a secret manager are usually easier to separate from application code.

Are environment variables encrypted?

Not necessarily. They are a way to pass configuration to a process without placing it in source code. Secure the server, deployment system, process access, and logs.

Is a .env file safe in production?

It can work, but it is a plaintext secret file and must never be publicly accessible or committed. Many production systems instead inject environment variables or use a secret manager.

What should I do if I committed a database password to Git?

Change or disable that database credential immediately. Then remove it from current code and, if appropriate, clean repository history. Rotation is essential because old commits may still be accessible.

Should PHP encrypt the database password before storing it?

Encryption only helps if the decryption key is protected separately. If both are available to the same application files, it adds little protection. Prefer external secret storage and access controls.

Should my application connect as the database root user?

No. Create a dedicated application user with only the database and permissions needed by that application.

Can I use the same database password in development and production?

No. Use separate credentials for each environment. This limits damage if a development machine or test setup is compromised.

Can I use $_ENV instead of getenv()?

Yes, but availability of $_ENV can depend on PHP configuration. getenv() directly reads environment variables and is commonly used for this purpose.

Take Quiz

Mini Project

Description

Build a small PHP database connection factory that reads MySQL settings from environment variables. This reflects a common application pattern: one well-tested place creates connections while deployment systems provide the secrets.

Goal

Create a reusable PDO connection function that fails safely when required database configuration is missing.

Requirements

Use environment variables for the database host, name, username, and password. Validate every required value before connecting. Create the connection with PDO and exception mode enabled. Do not place any real credential in the PHP source file. Use UTF-8 character encoding in the MySQL DSN.

Take Quiz

Keep learning

Related questions

View more