Guide · Deployment exposure
Is your .git directory publicly readable?
If a web server is pointed at a folder that contains a .git directory, that directory is usually served like any other file. Anyone who asks for it can reconstruct your repository — every file, and every commit you have ever made, including the ones you deleted.
Below: how to check in ten seconds, why a CDN can hide it from you while the internet still gets it, and how to fix it on each common stack.
Check it yourself, right now
Run this against your own domain. It asks for the one file that always exists in a git repository and prints the status code:
curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/.git/config
404 or 403 is what you want. A 200 means it is being served — but do not stop there, because a 200 alone can be a false alarm. Sites that serve one page for every address (a single-page app, a holding page) answer 200 for anything. Look at what actually came back:
curl -s https://yourdomain.com/.git/config
If you see your repository's configuration — [core], a [remote "origin"] block with your repository URL — it is genuinely exposed. If you see HTML, you are looking at your own homepage and this particular check is fine.
Then check whether the repository can actually be rebuilt
A readable config is embarrassing. A readable object store is what lets someone clone the whole thing:
for p in /.git/HEAD /.git/index /.git/logs/HEAD /.git/refs/heads/main; do
echo -n "$p "
curl -s -o /dev/null -w "%{http_code}\n" "https://yourdomain.com$p"
done
If those answer 200, publicly available tooling will reconstruct the working tree and the history from them. It is not a difficult attack and it is not a manual one.
The part that catches people: a CDN can hide this from you
Cloudflare and similar services block requests for .git paths at the edge under their managed rules. That is helpful, and it is not a fix. The origin server is still serving the files; something in front of it is declining to pass them on.
This matters because origin addresses are not secret. They appear in certificate transparency logs, in historical DNS records, and in any service that has ever recorded where the domain used to point. Anyone who finds the origin address can request the files directly and never touch the CDN.
So test the origin, not the public URL:
curl -s -k --resolve yourdomain.com:443:YOUR_ORIGIN_IP \
https://yourdomain.com/.git/config
We found exactly this while testing our own infrastructure. One site looked clean from every public check and was serving .git/config, .git/HEAD and .git/index from the origin the whole time. Protection you do not control and cannot test is not protection.
Why it is worse than it sounds
Someone can stand up a copy of what you built
Not scrape your pages — clone the source, the build configuration, the environment scaffolding, the lot. The better your project is doing, the more worth doing that becomes, which makes it the one problem that gets worse precisely as you succeed.
Git keeps everything you ever committed
This is the part that surprises people. A key you pasted into a file once and removed the next day is still in the history, still readable, and still valid unless you replaced it at the source. Deleting a file in a later commit does not remove it from earlier ones — that is what version control is for.
So if your repository was reachable, the question is not "what is in it now". It is "what has ever been in it".
How to fix it
Two things, in this order. Blocking the path stops it getting worse; rotating credentials is what undoes the exposure that already happened.
1. Stop serving dotfiles
nginx — in the relevant server block:
location ~ /\.(?!well-known) {
deny all;
return 404;
}
The negative lookahead matters. A regex location outranks a prefix location in nginx, so a bare location ~ /\. will shadow your ACME challenge block and break certificate renewal — silently, and up to ninety days later. Reload nginx afterwards, and re-check the response rather than assuming.
Apache — in the virtual host:
<FilesMatch "^\.">
Require all denied
</FilesMatch>
Caddy:
@dotfiles path_regexp /\.(?!well-known)
respond @dotfiles 404
Vercel, Netlify, Cloudflare Pages and similar — these deploy a build output directory rather than your project folder, so a .git directory is not normally published. If one is, the usual cause is a build command that copies the project root into the output directory. Check what your build actually emits.
2. Stop deploying the repository at all
The rule above blocks the path. It does not stop the files being on the server. If your deploy copies a whole project folder, exclude the git directory at the source — rsync --exclude='.git', a .dockerignore entry, or deploying a build artefact rather than a working tree. Two layers is right here: the deploy should not put it there, and the server should not serve it if it does.
3. Rotate anything the repository has ever held
If the object store was reachable, treat every credential that has ever been committed as public — not just the ones currently in the files. Rotate them at the issuer. Removing a secret from the repository afterwards is housekeeping; the rotation is the fix.
To find out what is actually in your history rather than guessing, tools such as gitleaks and trufflehog scan every past commit rather than the current checkout.
Check the rest of the surface while you are here
.git is one of a family of things that end up published by accident. The closest relative is an exposed .env file — same cause, no tooling required to read it, and it usually holds every credential at once. Then there are source maps, debug pages, and staging settings that keep a finished site out of Google. None are visible from a browser, because the site looks completely normal while it happens.
We also scanned twelve real AI-generated projects to see what a scan of the deployed site misses — every one we could assess carried a known-vulnerable dependency, and one had a database key in its git history.
Preflight scans a site's public surface for 42 of them and tells you what it found, how it was observed, what that does not prove, and the specific change that fixes it. The scan is free and needs no account.
This page describes how a misconfiguration behaves and how to correct it on your own site. It is not advice about anyone else's, and running these checks against a domain you do not control is your responsibility, not ours.