- CDN 없이 HTTP 프로토콜/IP 대역/클라이언트 신호/TCP 특성/TLS 지문/콘텐츠 압축을 조합해, 구현이나 설정이 부실한 봇 대부분을 차단하는 방법을 정리함
- 모든 방법은 선택적으로 조정해야 하며, 1~3년치 접근 로그를 먼저 분석하지 않으면 VPN 사용자/검색엔진/CDN/학교/도서관/특정 언어 사용자를 함께 차단할 수 있음
- HTTP/1.1 클라이언트와 데이터센터의 AS/CIDR 대역을 차단하면 많은 봇을 제거할 수 있지만, GoogleBot을 비롯한 검색엔진과 정상적인 데이터센터 이용자도 제외될 수 있음
- Nginx 헤더 검사와 nftables의 TCP 윈도 크기/MSS/TTL 필터는 단순 크롤러와 스캐너를 줄이지만, LTE/VPN/Windows 같은 정상 환경에서 오탐이 발생할 수 있음
- 장기적으로는 JA4 TLS 지문 탐지와 Brotli 전용 응답을 대안으로 제시하지만 완전한 차단은 보장하지 않으며, 수익을 내는 서비스에는 사용하지 말 것을 경고함
차단 범위와 적용 전제
- 차단 목표를 일부 봇/대부분의 봇/모든 봇 가운데 먼저 결정해야 함
- 여기서 다루는 방식은 정교한 자동화 도구 전체가 아니라 구현이나 설정이 부실한 봇 대부분을 비교적 단순하게 차단하는 데 초점을 둠
- 각 방식별로 정상 사용자와 검색엔진을 차단할 위험을 함께 표시함
- 2026년 7월 26일 Hacker News에 공유된 뒤, 차단 기능 대부분을 블로그에서 별도의 데모 사이트로 옮겨 독자가 방법을 읽은 후 직접 접근을 시도하는 퍼즐 형태로 바꾸기로 함
- 모든 설정은 선택적으로 수정하거나 생략할 수 있으며, 실제 적용 전 충분한 조사와 테스트가 필요함
- 정상 사용자/조직 내부 시스템/의존 중인 외부 서비스가 차단될 수 있으므로 적용 책임은 전적으로 운영자에게 있음
- 수익을 내는 운영 환경에는 사용하지 말 것
방법 1: HTTP 프로토콜로 구분
- 정상 사용자 차단 위험은 낮고, 일부 검색엔진 차단 위험은 중간 수준
- 일반 브라우저는 HTTP/2.0을 사용하지만 많은 봇은 HTTP/1.1을 사용한다는 차이를 이용함
- GoogleBot은 HTTP/1.1을 사용한다고 보며 이 방식으로 차단됨
- Bing과 Facebook 크롤러는 HTTP/2.0을 사용함
- HTTP/1.1로 링크 제목이나 짧은 미리보기를 가져오는 서비스도 차단될 수 있음
- Opera Mini도 대상에서 제외함
- Nginx에서
$server_protocol이 HTTP/2.0이 아니면 다른 페이지로 리디렉션하거나 200, 403, 444를 반환하도록 구성함
if ($server_protocol != HTTP/2.0) {
return 403 'Upgrade your client';
}
444를 반환하면 별도 응답 없이 연결을 끊을 수 있음
- Google 검색 유입을 차단했을 때 발생할 손실보다 얻는 이점이 큰지는 각 조직이 직접 판단해야 함
방법 2: 데이터센터 IP 대역 차단
- 정상 가정용/LTE 사용자의 차단 위험은 낮지만 VPN 사용자는 중간 수준이며, 데이터센터에서 동작하는 검색엔진은 차단 위험이 높음
- 최근 1~2년의 접근 로그에서 다음 신호를 조합해 의심스러운 요청을 찾음
- HTTP 프로토콜
- User-Agent
Accept-Language
Sec-Fetch-Mode
Accept
- 의심 IP를 BGP Tools 또는 Hurricane Electric BGP Toolkit에서 조회해 소속 AS와 광고 중인 Prefix를 확인함
- 제공된 네트워크 블랙홀 목록은 CDN과 검색엔진 대역도 포함할 수 있으므로 선별 적용해야 함
- 별도 목록에 앞서
3/8, 10/8, 11/8, 25/8, 26/8, 38/8, 41/8, 60/8, 61/8, 200/8, 224/3 대역을 이미 블랙홀 처리한 구성을 사용함
- AS의 Prefix 페이지를 복사한 뒤 셸 함수로 CIDR만 추출하고 정렬/중복 제거/병합함
- sum_cidr.pl을 사용하며 Perl 모듈
Net::CIDR::Lite가 필요함
- 생성된 결과는 검토 후
/usr/local/etc/*.netset 파일로 이동함
- 서버 시작 시 각 CIDR을 블랙홀 라우트로 추가함
for CflIP in $(grep -E ^[1-9] /usr/local/etc/_cloudflare.netset); do
/sbin/ip route add blackhole "${CflIP}" 2>/dev/null
done
- 예시에서는 Cloudflare 전체 대역을 차단해 Workers 등을 통한 요청을 받지 않도록 함
- 방화벽의 ipset 규칙보다 Linux 블랙홀 라우팅이 CPU를 적게 사용한다는 이유로 라우팅 방식을 선택함
- 현재 서버가 속한 호스팅 업체의 대역도 차단할 수 있음
- DNS/설정/내부 서비스에서 같은 주소 공간을 사용하지 않아야 함
- 직접 연결된 게이트웨이 경로는 블랙홀 경로보다 우선 적용됨
방법 3: 국가/프록시/Tor/악성 IP 차단
- FireHOL Blocklists 저장소의 목록을 내려받아 필요한 대역을 블랙홀 라우트로 추가함
- 목록 파일에는 주석이 포함되므로
grep -Ev '^#'로 제거한 뒤 처리해야 함
- 특히 다음 목록을 권장함
firehol_abusers_30d.netset
firehol_level2.netset
- 큰 목록은 시작 스크립트 실행 시간이 길어질 수 있음
- 실제 서버와 방화벽 구성에 사용한 설정 파일도 제공함
방법 4: HTTP 클라이언트 신호 검사
- User-Agent나 헤더는 위조할 수 있지만, 속도를 우선하는 단순 봇은 이를 제대로 위조하지 않는 경우가 많다는 전제를 사용함
Curl, Wget 요청에는 일반 텍스트를 반환하고 Bot, GPT, LLM, Spider가 포함된 요청에는 410 Gone을 반환함
if ($http_user_agent ~* Curl) { return 200 '\nGNU Terry Pratchett\n\n'; }
if ($http_user_agent ~* Wget) { return 200 '\nGNU Terry Pratchett\n\n'; }
if ($http_user_agent ~* Bot) { return 410 '1000101'; }
if ($http_user_agent ~* GPT) { return 410 '1000101'; }
if ($http_user_agent ~* LLM) { return 410 '1000101'; }
if ($http_user_agent ~* Spider) { return 410 '1000101'; }
- 관찰된 User-Agent 일부 문자열을 하나의 긴 정규식으로 검사해 크롤러/스캐너/수집 도구를 차단함
Go-http, Java, libwww, okhttp, urllib, python, nmap, zgrab, semrush, shodan, rss, scrap, crawler, headless, github, facebook, google, bing 등에 해당하는 부분 문자열이 포함됨
- 적용 전에 2~3년치 User-Agent를 집계해 실제 정상 클라이언트가 정규식과 일치하는지 확인해야 함
sort access-user-agents.txt | uniq -c | sort
- 일치 요청이 HTTP/1.1이면 봇일 가능성이 높은 것으로 취급하되 GoogleBot은 예외로 고려함
- HTTP/2.0 요청이라면 BGP 도구에서 IP 소속을 추가로 확인함
Sec-Fetch-Mode
Sec-Fetch-Mode를 접근 로그에 추가하고 값이 cors, no-cors, navigate 중 하나가 아니면 차단함
if ($http_sec_fetch_mode !~ (cors|no-cors|navigate)) {
return 410 '1000101';
}
Referer 검사
- 다른 사이트에서 콘텐츠를 삽입하거나 스캔하는 요청을 막기 위해
Referer에 특정 문자열이 있으면 차단함
- 관리자 페이지/검색엔진/소셜 네트워크/암호화폐/성인 콘텐츠/스캐너/WordPress 관련 문자열 등을 검사함
- Google 루트 페이지
https://www.google.com/를 Referer로 주장하는 특정 봇 유형도 별도로 차단함
- 오래된 Android인 것처럼 가장하는 요청에서 관찰된 패턴임
HTTP 메서드 제한
- 정상 브라우저 요청에 필요한
GET과 POST만 허용하고 다른 메서드는 차단함
if ($request_method !~ (^GET$|^POST)) {
return 410 '1000101';
}
- 실제 애플리케이션에서
POST가 필요한 경로를 더 세부적으로 제한하거나, 사용하지 않는 경우 완전히 제외할 수 있음
프록시와 브라우저 형태 검사
X-Forwarded-For 헤더가 있으면 프록시 요청으로 판단해 차단함
- 학교나 도서관처럼 정상적인 공유 프록시 환경도 차단될 수 있음
- User-Agent에
Linux, BSD, Macintosh, Windows, Mozilla, WhatsApp 중 하나도 없으면 브라우저처럼 보이지 않는 요청으로 판단함
Accept-Language에 en 또는 es가 없으면 차단하는 규칙도 사용함
- 일부 브라우저와 영어/스페인어를 쓰지 않는 정상 사용자를 차단할 가능성이 큼
br 또는 sy가 포함된 언어 설정을 별도로 차단하는 선택적 규칙도 제시함
민감한 경로 스캔 차단
- 다음 파일이나 경로를 요청하면 자동 스캔으로 보고 차단함
방법 5: nftables로 TCP 스캐너 차단
- nftables의
raw 테이블 PREROUTING 체인에서 TCP SYN 패킷 특성을 검사함
- 예시 서버 주소
172.238.221.88을 목적지로 명시해 패킷 손실 상황에서 발생할 수 있는 오탐을 줄임
- 80/443 포트로 들어오는 SYN 패킷 가운데 다음 조건을 차단함
- TCP 윈도 크기가 12,288바이트 미만
- MSS가 1,220~1,460 범위 밖
- 실제 클라이언트는 더 큰 윈도 크기를 사용하며, 해당 범위를 벗어난 MSS는 정상 클라이언트일 가능성이 낮다는 기준을 사용함
- MSS를 정확히
1460으로 제한하면 더 엄격해지지만 LTE와 VPN 사용자 대부분을 차단할 수 있음
TTL 기반 선택적 제한
- TCP SYN의 TTL이
128보다 크면 LTE 장치 대부분을 차단할 수 있음
- TTL이
64보다 크면 Windows 시스템 대부분도 차단됨
- 기본 TTL 기준은 다음과 같이 설명함
- Linux/Mac/BSD:
64
- Windows:
128
- LTE: 이보다 더 큰 값
연결 추적 제외
- 80/443 포트를
notrack으로 지정해 conntrack 테이블에 웹 트래픽을 넣지 않음
- 이 경우 filter 테이블에서도 송수신 방향에 대한 상태 비저장 규칙을 직접 구성해야 함
- 예시에서는 클라이언트 출발 포트
1000-65535와 서버의 80/443 포트 사이 트래픽을 허용함
방법 6: 성인 콘텐츠와 로봇 헤더
add_header Rating 'RTA-5042-1996-1400-1577-RTA' always;
add_header adult 'porn, sex, politics, religion, philosophy' always;
add_header X-Robots-Tag "none,noindex,nofollow,nosnippet,noai" always;
- 일반 봇은 이러한 헤더를 무시할 수 있지만, 검색엔진이나 성인 콘텐츠를 피하도록 설계된 봇에는 영향을 줄 수 있음
방법 7: TLS 지문 탐지
- 앞선 1~6번 방법은 모두 거친 휴리스틱이며, 장기적으로는 TLS 지문 분석이 더 나은 선택일 수 있음
- 봇이 TLS 지문을 정상 브라우저와 동일하게 바꾸지 않는다는 조건에서 JA4를 활용할 수 있음
- 먼저 Deploying JA4의 배포 방법을 확인한 뒤 FoxIO JA4를 적용하도록 안내함
방법 8: Brotli 압축 콘텐츠만 제공
- 사이트 콘텐츠를 미리 Brotli로 압축하고 웹 서버가 압축된 파일만 반환하도록 구성함
- 많은 봇이 Brotli 압축 HTML을 해석하지 못한다는 점을 이용함
- 적용 후 여러 봇이 페이지 링크를 더 이상 따라가지 않아 HTML을 실제로 파싱하지 못하는 것으로 확인함
- Nginx에서는 정적 Brotli 파일을 항상 제공하도록 설정함
brotli_static always;
cat ./i.html | brotli --best -fncv > ./i.html.br
방법 9: 스캐너의 자기 식별 유도