Exposed .env and .git Files: How They Leak and How to Block Them

How .env, .git and backup files end up publicly downloadable, how to check your own site in a minute, and Nginx, Apache and Cloudflare rules to block them.

By the MakeItFlow team 8 min read
Share LinkedIn X Facebook WhatsApp
Exposed .env and .git Files: How They Leak and How to Block Them

Some of the most damaging website leaks don't involve any hacking at all. A .env file with database passwords and API keys, or a .git folder holding your full source history, gets deployed into the public web root, and anyone who requests the right URL can download it. Automated scanners request those URLs constantly. This guide explains how these files end up exposed, how to check your own site in about a minute, and the server rules that block them for good.

Key takeaways

  • Files such as .env, .git folders and backups left in the public web root can be downloaded by anyone who requests the URL.
  • The real fix is keeping secrets outside the web root by pointing the document root at the public folder and deploying build artifacts.
  • Add Nginx, Apache or Cloudflare rules that block dotfiles, except /.well-known/, and common backup file extensions.
  • If you find an exposed file, assume it has been copied and rotate every secret in it rather than only blocking the URL.

Why this matters

In 2024, Palo Alto Networks' Unit 42 documented an extortion campaign built entirely on exposed environment files. The attackers scanned more than 230 million targets, copied the .env files of at least 110,000 domains, and collected over 90,000 unique environment variables, about 7,000 of them belonging to cloud services. They used those credentials to get into victims' cloud accounts, copy data, and demand ransom. Unit 42 was explicit that this didn't come from any flaw in the cloud providers. It came from files that should never have been public.

OWASP ranks Security Misconfiguration second in the OWASP Top 10:2025, and leftover files on production servers are one of its classic examples.

What gets exposed, and what it gives away

File or path How it gets there What an attacker gets
/.env Frameworks (Laravel, Symfony, Node apps) keep config in .env; the web root is the project root instead of public/ Database credentials, mail/SMTP passwords, cloud keys, payment API secrets, app encryption key
/.git/ Site deployed with git clone or git pull directly into the web root Full source code and history, including secrets you "deleted" in a later commit
/backup.zip, /site.tar.gz, /db.sql Someone made a quick backup in the web root before an update Everything: code, config, customer data
wp-config.php.bak, config.php.old, index.php~ Manual edits, editor backups Source code served as plain text, because .bak isn't executed as PHP
.config.php.swp Vim swap file left by an open or crashed editor session Partial file contents, often credentials
/.DS_Store Files uploaded from a Mac A listing of filenames in that directory
/phpinfo.php, /info.php Debug file left after setup Server paths, module versions, environment variables
/.svn/, /.hg/ Older version control systems Same as .git

The .bak and ~ cases surprise people. Your server runs config.php through PHP, so visitors only see output. A copy named config.php.bak doesn't end in .php, so the server sends it as a plain file, source and passwords included.

A .git folder is worse than it looks. Even with directory listing disabled, the internal file layout of a Git repository is predictable. Tools can request /.git/HEAD, /.git/config and the object files one by one and rebuild the entire repository, history included.

Check your own site

Run these against your own domain. A 200 status with real content is a problem. 403 or 404 is what you want, and 404 is better, because 403 confirms the file exists.

for p in .env .git/HEAD .git/config .env.backup .env.production backup.zip db.sql wp-config.php.bak .DS_Store phpinfo.php; do
  printf "%-22s %s\n" "$p" "$(curl -s -o /dev/null -w '%{http_code}' https://example.com/$p)"
done

Watch out for false comfort. Some sites return 200 with their normal homepage for every unknown URL (a "soft 404"). If you get 200, look at the actual body: curl -s https://example.com/.git/HEAD should not print something like ref: refs/heads/main.

Also check subdomains and staging copies (staging., dev., old.). They're often deployed less carefully than production.

The real fix: keep secrets outside the web root

Blocking rules are a safety net. The real fix is making sure these files are never inside the directory your web server serves.

  • Point the document root at the public folder. Laravel's web root should be /var/www/app/public, not /var/www/app. The same goes for Symfony (public/) and most Node frameworks (dist/ or build/ for static output).
  • Deploy build artifacts, not repositories. Use a CI pipeline, rsync --exclude, or git archive to ship only the files the site needs. If you must deploy with Git, keep the repository outside the web root and use a --work-tree deploy, or put the web root in a subfolder.
  • Never back up into the web root. Write backups to a directory the web server can't reach, or straight to off-site storage.
  • Use real secret storage where you can. Environment variables set by your host or orchestrator, or a secrets manager, rather than a file on disk.

Block access at the server

Even with a clean deployment, add rules so a future mistake doesn't turn into a leak. The patterns below block dotfiles (except /.well-known/, which Let's Encrypt and other services need) and common backup extensions.

Nginx

Add inside your server block, above other location blocks that might match first:

# Block all dotfiles and dot-directories except /.well-known/
location ~ /\.(?!well-known/) {
    return 404;
}

# Block backup, dump and editor files
location ~* \.(bak|old|orig|save|swp|swo|sql|sqlite|log|ini|dist)$ {
    return 404;
}
location ~ ~$ {
    return 404;
}

Nginx checks regex locations in the order they appear in the config, and uses the first one that matches. Put these before your location ~ \.php$ block, or a request such as /.hidden/shell.php would go to PHP instead of being blocked. Also look for prefix locations using the ^~ modifier (for example location ^~ /uploads/). When one of those matches, Nginx skips regex locations entirely, so add the same rules inside that block.

Archives (.zip, .tar.gz) are left out on purpose, because many sites offer legitimate downloads. If yours doesn't, add zip|tar|gz|tgz|7z|rar to the list.

Apache

In the virtual host or a root .htaccess (requires mod_alias):

# Dotfiles and dot-directories, except /.well-known/
RedirectMatch 404 "/\.(?!well-known/)"

# Backup, dump and editor files
<FilesMatch "(\.(bak|old|orig|save|swp|swo|sql|sqlite|log|ini|dist)|~)$">
    Require all denied
</FilesMatch>

Apache's default configuration already denies .htaccess and .htpasswd, but not .env or .git. Don't assume you're covered.

Cloudflare

If your site is behind Cloudflare, you can block these paths at the edge with a WAF custom rule (Security > WAF > Custom rules). Use the expression editor:

(http.request.uri.path contains "/.git/") or
(http.request.uri.path contains "/.env") or
(http.request.uri.path contains "/.svn/") or
(ends_with(http.request.uri.path, ".sql")) or
(ends_with(http.request.uri.path, ".bak"))

Set the action to Block. This works across all origins behind the zone, which is handy if you run several apps. Keep the server rules as well, because origin IPs are sometimes reachable directly.

If you find an exposed file

Blocking the URL stops future downloads, but it doesn't undo past ones. Assume anything in the file has been copied.

  1. Block access immediately, using the rules above or by deleting the file.
  2. Rotate every secret in it. Change database passwords, regenerate API keys, rotate cloud access keys, reset SMTP credentials and the app key. For .git exposure, include every secret that has ever been committed, not just current ones.
  3. Check access logs for requests to the file. A 200 response to an unfamiliar IP or scanner user agent means it was downloaded:
grep -E '"GET /\.(env|git)' /var/log/nginx/access.log* | awk '$9 == 200'
  1. Review the services those credentials unlock. Look for new cloud users, unfamiliar resources, changed DNS, or outgoing mail you didn't send. Unit 42's victims were hit through cloud keys taken from .env files.
  2. Fix the deployment process so the file can't reappear on the next release.

If you see signs that the credentials were used, treat it as an incident. Our hacked site response plan covers the next steps.

Frequently asked questions

Is returning 403 good enough?

It blocks the download, which is what matters most. A 404 is slightly better, because it doesn't confirm the file exists, so scanners have less reason to come back and try variations.

My host uses a control panel. Can I still do this?

Usually yes. Most Apache-based shared hosts honor .htaccess, so the Apache rules work. More importantly, check that your project files (especially for Laravel) aren't uploaded directly into public_html. Many hosts let you change the document root to a subfolder.

Will blocking dotfiles break Let's Encrypt?

Not with the rules above. They exclude /.well-known/, which is where ACME HTTP-01 challenges live. If you write your own pattern, test renewal afterwards with certbot renew --dry-run.

Does removing a secret from Git history fix a .git exposure?

No. If the repository was downloadable, attackers may already have the old history. Rewriting history is good hygiene for the future, but every exposed secret still has to be rotated.

What to do next

Run the curl loop above against every hostname you own, fix your document root if .env or .git sits inside it, and add the blocking rules as a backstop. To probe for exposed files alongside your security headers and certificate, run a free audit. Then check the rest of your configuration with our HTTP security headers guide.

Sources

#env file #git exposure #backup files #nginx #apache

Written by the MakeItFlow team

We build the website audit these guides are based on. Every fact is checked against the primary sources listed above . How we research and check

Share LinkedIn X Facebook WhatsApp

Check your own site in 30 seconds

Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.

Weekly newsletter

One practical website fix, every week

New guides on speed, SEO, email deliverability and security — straight to your inbox. No spam, unsubscribe in one click.

Keep reading