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.

If a file was already downloaded, blocking it isn't enough. Change every password and key in it (step 6). Bots don't wait.

Before you start

  • You can log in with sudo and edit files with nano or another editor.
  • The examples use example.com. Use your own domain.
  • Files at risk: .env (Laravel, Symfony and many others), the .git folder, copies like wp-config.php.bak or config.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= or APP_KEY= from .env,
  • ref: refs/heads/main (or master) from .git/HEAD,
  • PHP code such as <?php from 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 public folder, not the project folder. Then .env and .git sit one level above it. See our Laravel guide.
  • WordPress: you may move wp-config.php one folder up, above the folder with wp-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/backups and 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 in APP_PREVIOUS_KEYS while data is moved over, as the Laravel docs explain.
  • A leaked .git folder 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-known folder open.
  • Your site offers .log or .sql files on purpose: remove that word from the list in the second rule.
  • Hidden files still download on Nginx: the include line is in the wrong server block, or below the PHP block. Check each site's file.
  • Apache says Invalid command 'RedirectMatch': the alias module is off. Run sudo a2enmod alias.

Official sources

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.

← Back to the security blog