# Protecting Credentials and .env Files Properly

> Protect .env files: correct web root, blocking in the web server, tight file permissions, no secrets in Git and credential rotation after a security incident.

Source: https://phpsos.de/en/blog/env-dateien-schuetzen/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** You protect a .env file by not placing it in the publicly reachable directory in the first place: the web root points to a subfolder such as `public/`, and the configuration sits one level above. In addition, the web server blocks all files starting with a dot, the file is readable only by the owner and the PHP process, and it never belongs in the Git repository. After a break-in, all credentials in it are considered known and are replaced.

## The File with the Keys to the House

Modern PHP projects like to store their secrets in a file called `.env`: database password, access to the mail server, API keys for payment and shipping services, the key with which the application signs cookies. Frameworks such as Laravel and Symfony use this pattern, and many custom developments have adopted it.

The idea is good: configuration and code are separate, and each environment gets its own values. It becomes dangerous when the file ends up in the wrong directory. If it is in the web root and the web server does not block it, a single request in the browser is enough to read all access credentials at once.

There are also variants with other names: `.env.local`, `.env.production`, `config.local.php` or files with credentials for storage services. What is described here for `.env` applies to all files that contain secrets.

Such files are constantly searched for by automated scanners. That is not a specialty of individual attackers but background noise on the internet, like the mass scans for unpatched PHP versions.

## Where the .env File Should Be

The most effective measure is structural: the web server may serve only the folder that is really meant to be public. In Laravel and Symfony, that is the `public/` folder. Everything else, that is, code, configuration, logs and `.env`, sits above it and cannot be addressed over HTTP at all.

```text
/srv/www/shop/
├── .env              ← not reachable
├── config/
├── src/
├── vendor/
└── public/           ← the only web root
    ├── index.php
    └── assets/
```

In practice, the web root nevertheless often points to the project directory itself, for example because a hosting panel dictates it or because a path was not adjusted during a move. So check in the web server configuration or in the panel where `root` or `DocumentRoot` points.

## Protecting the .env File: Web Server Block as a Second Line

If the web root cannot be changed, or you want additional protection, block hidden files in the web server. The `.well-known` folder must remain reachable, because it is used, among other things, for issuing certificates.

```nginx
# Block all files and folders starting with a dot, except .well-known
location ~ /\.(?!well-known/) {
    deny all;
    return 404;
}
```

```apache
# Block files starting with a dot
<FilesMatch "^\.">
    Require all denied
</FilesMatch>
```

A response with 404 instead of 403 does not reveal that the file exists. Check from outside whether the block works:

```bash
# Expected: 403 or 404, never 200
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env.example
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env.bak
```

The last two lines are no coincidence: copies such as `.env.bak` or `.env.old` arise during maintenance work and are often forgotten. More on such leftovers can be found in [Backup Files in the Web Root](https://phpsos.de/en/blog/backup-dateien-im-webroot/).

## Permissions, Git and Handling Copies

The principle of least privilege also applies outside the web root. A proven split:

1. **Owner** is the deploy user, not the PHP process.
2. **Group** is the group of the PHP-FPM pool, which may only read; permissions `640`.
3. **No write access** for the PHP process. An application does not need to be able to change its own credentials.
4. **No real values in the repository:** Only `.env.example` with placeholders is checked in; the real file is listed in `.gitignore`.
5. **Separate values per environment:** Development, staging and production use different credentials so that a leak in a test environment does not open up production.

```bash
chown deploy:shop-php /srv/www/shop/.env
chmod 640 /srv/www/shop/.env
# Check whether the file was ever committed
git log --all --oneline -- .env
```

Also watch out for error pages. In debug mode, frameworks sometimes display environment variables and configuration values; on a production system, this mode must be switched off. Check this after every deployment, because a wrong value in the `.env` itself can switch it back on.

If the last command produces matches, the credentials are in the Git history and count as exposed, even if the file is missing today. Why this is particularly delicate if the `.git` folder sits on the server is described in [Public .git Folder: An Underestimated Data Leak](https://phpsos.de/en/blog/git-ordner-oeffentlich/). The guide [File Permissions on the Web Server](https://phpsos.de/en/ratgeber/dateirechte-webserver/) explains the fundamentals of permissions.

> **Important:**
> Test files with `phpinfo()` display environment variables. Anyone who passes secrets as environment variables gives them away via a forgotten test file. Background is in [phpinfo() Files: Why They Must Disappear](https://phpsos.de/en/blog/phpinfo-dateien-entfernen/).

## After a Break-In: Rotating Credentials

No block protects against an attacker who is already executing code on the server. A PHP process must be able to read its configuration, so injected code in the same process can do so too. After PHP2Shell, the following therefore applies: if a break-in cannot be ruled out, all credentials on the server are to be treated as known.

An orderly rotation covers database passwords, mail and SMTP credentials, API keys of external services, signing keys of the application and credentials for storage services. Order and timing should be planned: anyone who replaces first and then cleans the server may be handing the new values to the attacker along with it.

You limit the damage of a leak beforehand by allowing each secret to do only as much as necessary. The website's database user does not need rights on other databases. With many providers, API keys of external services can be restricted to individual functions or sender addresses, for example creating shipments but not initiating payouts. And each application gets its own credentials instead of several websites sharing the same mail account or key. A leak then affects a clearly delimited area, and the rotation remains manageable.

In container environments, the `.env` file belongs neither in the image nor in a publicly reachable registry. Exclude it via `.dockerignore` and pass secrets only at runtime through the platform's mechanisms.

In the long term, it is worth looking at central secret stores, as OWASP recommends: secrets in one place, with an access log, regular rotation and the shortest possible validity.

## How does PHP SOS help?

- We check from outside whether configuration files, copies and hidden files of your websites are retrievable.
- We rearrange the web root, web server blocks and file permissions so that secrets are no longer reachable over HTTP.
- We check repositories and deployments for committed credentials.
- After an incident, we plan and accompany the rotation of all affected credentials in the right order.

This work is part of our [server hardening for PHP web servers](https://phpsos.de/en/leistungen/server-haertung/).


## Frequently asked questions

**Is a .env file in the web root automatically public?**  
Not automatically, but often. Whether it can be retrieved depends on the web server configuration. Many default configurations protect only .htaccess files, not other files starting with a dot. A test with curl brings clarity.

**Are environment variables more secure than a .env file?**  
Not as a rule. OWASP points out that environment variables are readable by all processes of the user and can show up in logs or memory dumps. They do avoid a file in the web root, though. Better are central secret stores, where the environment supports them.

**What should I do if the .env file was retrievable?**  
Treat all credentials it contains as known and replace them: database, mail, APIs, payment services, application keys. Then check the access logs to see whether the file was retrieved, and involve the data protection officer if personal data is concerned.

**Does the .env file belong in the Git repository?**  
No. At most a template without real values, such as .env.example, belongs in the repository. The real file is excluded via .gitignore and provided separately on the server.

**Can phpinfo() reveal credentials?**  
Yes. phpinfo() displays, among other things, environment variables and server variables. If secrets are stored there, they are output as well. Test files with phpinfo() therefore never belong on a reachable system.

## Sources

- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
- [Nginx documentation](https://nginx.org/en/docs/)
- [Apache HTTP Server: documentation](https://httpd.apache.org/docs/)
