SERVER SECURITY GUIDE

Exposed .env and .git Files: How to Find Them, Block Them and Rotate Secrets

Some of the most damaging leaks on small servers involve no hacking at all. A .env file with database passwords or a .git directory with your source code sits in the web root, a scanner requests the right URL and the server hands it over. Sysdig's threat research team documented an operation it named EMERALDWHALE that collected more than 15,000 cloud service credentials this way, by "web scraping Git config files, Laravel .env files, and raw web data." The operators were not sophisticated; they simply asked thousands of servers for the same few paths. This guide shows how to test your own server in five minutes, close the hole in nginx or Apache, and rotate secrets that may already have been read. It complements the nginx hardening guide and the VPS security checklist.

Quick win (5 minutes)

  • Run curl -s -o /dev/null -w "%{http_code}\n" https://your-domain/.env against each site you host.
  • Repeat for /.git/config and /.git/HEAD.
  • If any returns 200, open the response body: a real file shows keys or repository settings, a generic page is a false alarm.
  • If a real file is exposed, block the path first, then rotate every secret it contained, in that order.
  • Note which sites deploy by copying a whole repository onto the server.

How these files end up public

The causes are mundane. A deploy script copies the whole repository, including .git, into the directory the web server serves. A framework keeps its configuration file in the project root, which is also the document root. A developer edits a file in place and the editor leaves a backup copy. The OWASP Web Security Testing Guide lists the general problem: "Backup archives may contain copies of all files within (or even outside) the webroot," and backup files "may disclose the source code for pages designed to execute on the server." Its remediation is to store data, log and configuration files "in directories not accessible by the web server."

The consequences depend on what the file holds. Sysdig reported that the EMERALDWHALE operators probed the /.git/config path, validated any tokens they found, and then used them to "clone private repositories, and extract cloud credentials from their source code." One run found "more than 67,000 URLs with the path /.git/config exposed." The stolen accounts were reportedly used for phishing and spam and also sold. A leaked .env usually carries a database password, an API key and an application secret, which connects directly to the exposed database problem.

Test your own server

Check with the same requests a scanner would make. Replace the domain and run these from your own machine:

for p in /.env /.git/config /.git/HEAD /.env.backup /config.php.bak; do
  printf '%s  ' "$p"
  curl -s -o /dev/null -w '%{http_code}\n' "https://your-domain$p"
done

A 404 is the answer you want. A 403 means the file exists but is blocked, which is acceptable though it confirms the path to a scanner, so prefer 404. A 200 needs a look at the body: many applications return their home page with status 200 for every unknown path, which is harmless, but if you see lines like DB_PASSWORD= or [core] with repositoryformatversion, you have a leak. Test every virtual host, including staging subdomains and old sites you forgot, because those are the ones most often left unprotected. Your logs also help: search for requests to these paths with grep -E '/\.(env|git)' /var/log/nginx/access.log to see how often scanners already try, and what status they received. The log analysis guide explains how to read them.

Block them in nginx

The rule is to deny every dot-file path except the one Let's Encrypt needs. HTTP-01 validation places a token at http://your-domain/.well-known/acme-challenge/ and, as Let's Encrypt's documentation states, "can only be done on port 80," so a blanket dot-file block would break certificate renewal unless you carve out that path. Inside your server block:

location ^~ /.well-known/acme-challenge/ {
    allow all;
}

location ~ /\.(?!well-known/) {
    return 404;
}

This relies on nginx's documented matching order: it picks the longest matching prefix location first, and "if the longest matching prefix location has the ^~ modifier, then regular expressions … are not checked." That is why the ACME path is served normally while the regex catches everything else that starts with a dot, including /.env, /.git/ and /.htpasswd. If you already serve the ACME path from a specific root, keep that setting inside the first block. Add the block to every server and test with sudo nginx -t before reloading. If you use Let's Encrypt with shorter certificate lifetimes, run a renewal dry run afterwards (sudo certbot renew --dry-run) to confirm nothing broke.

To also block common leftovers, add a rule for backup extensions such as location ~* \.(bak|old|orig|swp|sql)$ { return 404; }. Do not add extensions such as zip or gz if you legitimately serve those downloads.

Block them in Apache

Apache's mod_alias documents RedirectMatch [status] regex URL, and states that for a status outside 300 to 399 the URL argument "must be omitted," so a 404 needs no target. In the server or virtual host configuration:

RedirectMatch 404 "/\.(?!well-known/)"

That returns 404 for any path containing a dot-directory or dot-file except /.well-known/, which Let's Encrypt validation uses. Reload with sudo apachectl configtest && sudo systemctl reload apache2. If you use .htaccess files in an application, remember that an AllowOverride setting decides whether they are honoured, and that rules in the main configuration are easier to audit than scattered per-directory files.

Want to know what your server exposes?

A free, read-only server check looks for publicly reachable files and services and reports them without changing anything.

Get your free server check Talk to a security engineer

Fix the cause: keep secrets out of the web root

Blocking the URL is a safety net. The durable fix is to ensure the files are not under the web root at all.

  • Serve a subdirectory. Point the web server at a public/ folder, as Laravel and many frameworks intend, so the project root with .env and .git is never served.
  • Deploy artifacts, not repositories. Use git archive, a build step or rsync with --exclude=.git, instead of cloning onto the server.
  • Keep configuration outside the repository. The twelve-factor method stores config in the environment because "there is little chance of them being checked into the code repo accidentally," and offers a good litmus test: could the codebase be made open source at any moment "without compromising any credentials"?
  • Do not edit files in place on production. OWASP calls that "a particularly bad habit" because editors leave backup copies.
  • Use least privilege for the secrets themselves. A database user with only the permissions the app needs limits what a leaked file can do. See least privilege on a Linux server.

If a file was exposed, rotate everything

Blocking the path stops future reads but not the ones that already happened. If a .env or .git was reachable at any point, assume it was copied, and act in this order:

  1. Block the path (the sections above) so the exposure stops.
  2. List every secret the file or the repository history held: database passwords, API keys, cloud tokens, SMTP credentials, application keys and signing secrets.
  3. Rotate each one at the issuer, not just in your config. Revoke the old value; do not wait to see if it is used.
  4. Check what the credentials could reach and review their logs for activity you do not recognise. Sysdig noted that secret management alone is not enough and recommends monitoring the behaviour of identities associated with credentials.
  5. Remove the secret from Git history if the repository is public or shared, but treat that as cleanup only. Rewriting history does not un-leak a value that was already copied, so rotation is the real fix.

If you see signs that someone used a leaked credential on the server itself, switch to the breach response procedure.

Frequently asked questions

Why is an exposed .env file dangerous?

It usually holds database passwords, API keys and application secrets in plain text. Anyone who can request the URL can read them, and automated scanners request these paths on thousands of servers. Sysdig documented one operation that collected more than 15,000 cloud credentials this way.

How do I check whether my .git folder is public?

Request https://your-domain/.git/config and /.git/HEAD with curl or a browser. A 404 is correct. If you receive file contents with repository settings, the folder is exposed and the path must be blocked at once.

How do I block .env and .git in nginx without breaking Let's Encrypt?

Add a location block for /.well-known/acme-challenge/ with the ^~ modifier, then a regex location that returns 404 for dot paths excluding well-known. The ^~ prefix match stops nginx from checking the regex, so certificate validation keeps working.

Is returning 403 or 404 better for blocked files?

Both block access. A 404 is preferable because a 403 confirms to a scanner that the file exists at that path.

What should I do if my .env file was exposed?

Block the path, then rotate every secret in the file at its issuer, review logs for misuse and check what the credentials could access. Removing the file or rewriting Git history does not undo a copy that may already exist.

Is deleting the .env file from Git history enough?

No. History rewriting is cleanup, but a secret that was ever public or shared must be treated as compromised. Rotate it at the issuer.

Not sure where your server stands?

Get a free, read-only security check, or talk to a security engineer about managed protection.

Get your free server check Talk to a security engineer