How to stop .env, .git and backup files from being downloaded
Bots try /.env and /.git/config on every website, every day. If your web server hands them out, they get your database password, your API keys or all of your code. Two short rules stop this.
Before you start
- You can log in with
sudoand edit files withnanoor another editor. - The examples use
example.com. Use your own domain. - Files at risk:
.env(Laravel, Symfony and many others), the.gitfolder, copies likewp-config.php.bakorconfig.php~, database dumps (.sql) and log files, whenever they sit inside the website's folder.
1. Check your site
Run these from your own computer:
curl -s https://example.com/.env | head -5
curl -s https://example.com/.git/HEAD
curl -s https://example.com/wp-config.php.bak | head -5
Look at what comes back, not only the status code. Many sites (WordPress, Laravel) answer every unknown address with the home page and the status 200. That is fine. It's a leak if you see:
- lines like
DB_PASSWORD=orAPP_KEY=from.env, ref: refs/heads/main(ormaster) from.git/HEAD,- PHP code such as
<?phpfrom a backup file.
Even if all three look safe, add the rules below. Someone may copy a file there tomorrow.
2. Keep secret files out of the web folder
The safest file is one the web server can't reach at all:
- Laravel and Symfony: point the site at the
publicfolder, not the project folder. Then.envand.gitsit one level above it. See our Laravel guide. - WordPress: you may move
wp-config.phpone folder up, above the folder withwp-includes. WordPress finds it there on its own. - Backups and database dumps: never keep them in the website's folder. Use a folder like
/var/backupsand copy them off the server. See our backup guide.
Then add the rules below anyway, as a safety net.
3. Block the files on Nginx
Create /etc/nginx/snippets/block-private-files.conf:
# Hidden files and folders such as .env and .git, but not .well-known (Let's Encrypt)
location ~ /\.(?!well-known/) {
deny all;
}
# Backups, database dumps and logs
location ~* (\.(sql|bak|old|orig|save|swp|log)|~)$ {
deny all;
}
Then add this line in each site's server { } block in /etc/nginx/sites-available/:
include snippets/block-private-files.conf;
Put it above the location ~ \.php$ block. Nginx uses the first of these rules that matches. If the PHP block comes first, a hidden file like .cache.php still runs. Hidden PHP files are a common hiding place for code left behind by attackers.
Test and reload:
sudo nginx -t && sudo systemctl reload nginx
Nginx doesn't use .htaccess files. Without this rule it serves them as plain text to anyone who asks. The rule blocks them too.
4. Block the files on Apache
Apache already hides .htaccess and .htpasswd, but nothing else. Create /etc/apache2/conf-available/block-private-files.conf:
# Hidden files and folders such as .env and .git, but not .well-known (Let's Encrypt)
RedirectMatch 404 /\.(?!well-known/)
# Backups, database dumps and logs
RedirectMatch 404 (?i)(\.(sql|bak|old|orig|save|swp|log)|~)$
This answers "not found", as if the files weren't there. It works for every site on the server, also with WordPress's .htaccess rules. Turn it on, test and reload:
sudo a2enconf block-private-files
sudo apache2ctl configtest && sudo systemctl reload apache2
5. Check again
Run the commands from step 1 again. You should now get 403 Forbidden (Nginx) or 404 Not Found (Apache) instead of the file. To see only the status code:
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.env
Your pages, images and downloads still work as before. The .well-known folder stays open, so Let's Encrypt can still renew your certificate. To be sure, run sudo certbot renew --dry-run.
6. Was anything downloaded already?
Your web server's log shows every request. This finds the ones that got a file back (status 200):
sudo zgrep -E '"GET /[^ ]*(\.env|\.git/|\.sql|\.bak)[^ ]* HTTP/[0-9.]+" 200 ' /var/log/nginx/access.log*
On Apache, use /var/log/apache2/access.log*. If your site has its own log file, search that one. Requests that got 403 or 404 are normal bot noise and can be ignored. A WordPress or Laravel site may also show 200 for the home page (see step 1), so check the size column too. A real .env download is small, a home page much bigger.
Logs only go back a few weeks. If you find a real download, or the file was open for longer than your logs go back, treat it as stolen:
- Change every password and key in the file: database, email, payment and other API keys. Then update the file and restart PHP.
- Laravel's
APP_KEY: a new key logs everyone out and can't read data encrypted with the old one. Put the old key inAPP_PREVIOUS_KEYSwhile data is moved over, as the Laravel docs explain. - A leaked
.gitfolder means your full code and its history are out. Change every password that was ever saved in the repository, even if it was deleted later.
If something goes wrong
- Certbot can't renew: make sure you copied
(?!well-known/)exactly. It keeps the.well-knownfolder open. - Your site offers
.logor.sqlfiles on purpose: remove that word from the list in the second rule. - Hidden files still download on Nginx: the
includeline is in the wrongserverblock, or below the PHP block. Check each site's file. - Apache says
Invalid command 'RedirectMatch': the alias module is off. Runsudo a2enmod alias.
Official sources
- Old, backup and unreferenced files (OWASP testing guide)
- location (Nginx docs): how Nginx picks the matching rule
- RedirectMatch (Apache docs)
- Deployment (Laravel docs), which uses the same Nginx rule, and Encryption (Laravel docs) for
APP_PREVIOUS_KEYS - Hardening WordPress (WordPress docs), which covers moving
wp-config.php - Removing sensitive data from a repository (GitHub docs)
Next steps: add security headers, and work through the security checklist for a new PHP server.
ServerNest is being built to block hidden files such as .env and .git on every site it sets up, and to test the web server settings before every reload. Join early access to try it first.