An exposed .git directory gives away your source code.
Deploying with git clone or git pull straight into the web root deploys the .git folder too. If the web server serves it, your whole repository can be downloaded: every file, every commit, and every secret that was ever committed — including ones deleted since.
How to check
DotenvScan requests /.git/config in every scan. By hand:
curl -s https://example.com/.git/HEAD
A reply like ref: refs/heads/main means the directory is exposed; a 403 or 404 is what you want. If other apps are deployed in subfolders, check those paths too.
Why it’s worse than it looks
- Turning off directory listing doesn’t help. Git’s layout is predictable —
HEAD,refs/,index, pack files — and free tools walk it file by file to rebuild the repository. .git/configcan hold a live credential. A remote set up ashttps://user:TOKEN@github.com/…stores that access token in plain text.- History keeps everything. A password deleted three commits ago is still in the objects.
- Source code helps attackers. Hard-coded keys, admin routes and unpatched bugs are easier to find with the code in hand.
1. Block it now
# nginx (inside the server block)
location ~ /\.git {
deny all;
}
# Apache (virtual host or .htaccess)
RedirectMatch 404 /\.git(/|$)
For Caddy, and to block .env files and backups at the same time, use the rules in the main guide.
2. Rotate what it exposed
- Tokens in remote URLs. Run
git remote -von the server. Revoke any token you see with GitHub, GitLab or Bitbucket, then switch the server to a read-only deploy key:git remote set-url origin git@github.com:org/repo.git. - Secrets in history. Run a secret scanner such as gitleaks or TruffleHog over the whole history and rotate everything it finds. Rewriting history afterwards doesn’t un-leak anything, so rotate first.
- A committed
.env. If one is in the history, follow the exposed .env guide for every key in it.
3. Check whether it was downloaded
sudo zgrep -hE '"GET /([^ "]*/)?\.git/[^ "]* HTTP/[0-9.]+" 200 ' /var/log/nginx/access.log*
A burst of hits from one address — HEAD, config, index, then object files — means the repository was dumped. For Apache, search /var/log/apache2/access.log* or /var/log/httpd/access_log*.
4. Deploy without .git in the web root
- Serve a subfolder. Keep the checkout outside what is served and point the document root at its
public/folder (or equivalent) only. - Export, don’t clone.
git archive HEAD | tar -x -C /var/www/sitewrites the files without any.git. - Sync a build.
rsync -a --exclude=.git ./ server:/var/www/site/, or your CI’s deploy step.