웹 서버에 .git 디렉토리가 노출되면 벌어지는 일
소스를 git으로 관리하면서 저장소를 웹 루트에 그대로 두면, 아무나 /.git/ 주소로 접근할 수 있습니다.
저도 설정을 바꾸다가 이 상태를 만들었고, 확인해보니 그대로 열려 있었습니다.
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.git/config
# 200 ← 열려 있다
결론부터 — 뭐가 털리나
.git 디렉토리에는 소스 전체와 모든 이력이 들어 있습니다. 노출되면 이것들이 나갑니다.
| 파일 | 내용 |
|---|---|
.git/config |
저장소 주소, remote URL에 토큰이 박혀 있으면 그대로 |
.git/HEAD, refs/ |
브랜치 구조 |
.git/objects/ |
소스 코드 전부. 삭제한 파일까지 |
.git/logs/HEAD |
커밋 이력, 작업자 이름·이메일 |
가장 위험한 건 objects입니다. 한 번이라도 커밋했다면 그 안에 남아 있습니다. 나중에 지운 설정 파일, 실수로 커밋했다가 되돌린 비밀번호까지 복원됩니다.
"커밋했다가 바로 지웠으니 괜찮다"는 통하지 않습니다. git은 지워도 남깁니다.
왜 생기나
의도적으로 열어두는 사람은 없습니다. 대개 이런 경로입니다.
- 서버에서
git clone으로 배포하고 웹 루트가 곧 저장소인 경우 git pull로 업데이트하는 구조- 소스를 git으로 관리하려고 구조를 바꾸다가
로컬 개발 환경에서는 문제가 안 보입니다. 배포하고 나서야 생깁니다.
확인하는 법
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.git/config
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.git/HEAD
404나 403이어야 정상입니다. 200이 나오면 열려 있는 겁니다.
내용까지 보이는지 확인하려면:
curl -s https://example.com/.git/config
[remote "origin"] 같은 게 그대로 보이면 심각한 상태입니다.
같은 방식으로
/.env,/wp-config.php,/composer.json,/.github/도 확인해보세요.
하나가 열려 있으면 보통 다른 것도 열려 있습니다.
막는 법
Caddy
@forbidden {
path /.git /.git/* /.env /wp-config.php /composer.json /package.json
}
respond @forbidden 404
Apache (.htaccess)
RedirectMatch 404 /\.git
<FilesMatch "^\.">
Require all denied
</FilesMatch>
<Files wp-config.php>
Require all denied
</Files>
nginx
location ~ /\. {
deny all;
return 404;
}
403보다 404가 낫습니다. 403은 "있는데 막혔다"를 알려주지만, 404는 존재 자체를 숨깁니다.
⚠️ 설정했는데 반영이 안 될 때
막는 규칙을 넣었는데 여전히 200이 나왔습니다. 설정이 틀린 줄 알고 한참 고쳤습니다.
원인은 프록시를 재시작하지 않은 것이었습니다. 설정 파일을 컨테이너에 마운트해 쓰고 있었는데, 파일을 고쳐도 프로세스가 다시 읽지 않습니다.
docker compose restart caddy # 이게 필요했습니다
설정을 바꿨으면 반드시 다시 측정하세요. 바꿨다는 사실이 적용됐다는 뜻은 아닙니다.
비슷한 일을 도메인 설정에서도 겪었습니다.
설정 화면에서 값을 입력하고 창을 닫았는데, 저장 버튼을 누르지 않았습니다. 입력한 걸 봤으니 당연히 반영됐을 거라 생각하고 한참 결과만 확인했습니다. 당연히 아무것도 바뀌지 않았습니다.
"입력했다"와 "저장했다"와 "적용됐다"는 전부 다른 단계입니다. 그 사이 어디에서든 끊길 수 있습니다.
더 나은 구조 — 아예 두지 않기
막는 것보다 웹 루트에 저장소를 두지 않는 게 근본적입니다.
| 방식 | 설명 |
|---|---|
| 빌드 후 산출물만 배포 | 저장소는 빌드 서버에만. .git 이 서버에 없음 |
rsync 로 파일만 전송 |
--exclude=.git 으로 제외 |
| 웹 루트를 하위로 | 저장소는 상위, 공개는 public/ 하위만 |
저는 rsync 로 파일만 보내는 방식을 택했습니다. 서버에 git을 둘 이유가 없고, 버전 관리는 작업 PC에서 하면 됩니다.
rsync -az --delete \
--exclude='.git/' --exclude='wp-config.php' --exclude='uploads/' \
./소스/ 서버:~/경로/
⚠️
--delete를 쓸 때는 제외 목록을 반드시 검증하세요.
빠뜨리면 서버의 업로드 파일이나 설정이 지워집니다.--dry-run으로 먼저 확인합니다.
정리
.git이 노출되면 소스 전체와 삭제한 파일까지 복원됩니다- 커밋했다가 지운 비밀번호도 남아 있습니다
curl /.git/config로 확인하세요. 404여야 정상입니다- 막을 때는 403보다 404로
- 설정 후 반드시 재측정하세요. 프록시 재시작이 필요할 수 있습니다
- 근본적으로는 웹 루트에 저장소를 두지 않는 구조가 낫습니다