[개발 깨알 상식_Tips] / Nginx access_log에서 AI fetch와 클릭 유입을 같은 숫자로 보지 않기: ua·referer probe 먼저 만들기.md

Nginx access_log에서 AI fetch와 클릭 유입을 같은 숫자로 보지 않기: ua·referer probe 먼저 만들기

조회

2026년 4월 21일 | 개발 깨알 상식_Tips


Nginx access_log를 보다 보면 ChatGPT-User 같은 요청과 실제 사람이 클릭해서 들어온 방문이 한 숫자로 뭉개질 때가 있다. 나도 한동안은 "AI가 나를 읽었다"와 "AI가 나를 추천해서 사람이 들어왔다"를 대충 같은 AI 유입으로 묶어 봤는데, 최근 SurfacedBy 글를 읽고 생각이 확실히 바뀌었다. 이 둘은 로그에서 보이는 모양도 다르고, 해석해야 하는 의미도 완전히 다르다.

내가 오늘 바로 메모한 팁은 단순하다. AI 트래픽을 한 숫자로 세지 말고, provider-side fetch와 real clickthrough를 먼저 나눠라. 그리고 기본 combined 로그로는 이 차이가 잘 안 보이니, 최소한 user-agent, referer, accept, request_uri 정도는 따로 남기는 probe를 하나 만들어 두는 편이 낫다.

Nginx 로그로 AI fetch와 클릭 유입을 구분하는 예시

Figure 1. 핵심은 "AI가 답변을 만들기 위해 원문을 읽으러 온 요청"과 "사람이 답변을 보고 링크를 눌러 들어온 방문"을 같은 줄로 보지 않는 데 있다.

1. 먼저 나눌 숫자는 두 개였다

provider fetch는 모델이나 에이전트가 내 origin을 직접 읽으러 오는 요청이다. 이 경우는 전용 user-agent가 찍히고 referer가 비어 있는 경우가 많다. 반대로 real visit은 사람이 답변 안의 링크를 눌러 들어오는 방문이라서, 일반 브라우저 user-agent에 chatgpt.com, claude.ai, perplexity.ai, gemini.google.com 같은 referer가 붙는 식으로 보인다.

이 둘을 합치면 숫자는 그럴듯해 보여도 판단이 흐려진다. provider fetch는 모델이 실제로 내 페이지를 retrieval에 썼는지를 말해 주고, clickthrough는 답변이 사람을 데려왔는지를 말해 준다. GEO나 검색 노출을 보려는 사람과 실제 유입 전환을 보려는 사람은 관심사가 다르니, 로그도 처음부터 분리해 두는 편이 맞다.

신호 로그에서 보이는 것 내가 읽는 의미
provider fetch 전용 UA, referer 없음, probe용 query string 일치 모델이 실제로 원문을 읽으러 왔는가
real clickthrough 일반 브라우저 UA + assistant referer 사람이 답변을 보고 링크를 눌렀는가
index/training bot GPTBot, ClaudeBot, Googlebot 같은 장기 크롤러 특정 질문 응답이 아니라 장기 인덱싱·학습 활동

2. combined 로그 대신 probe 포맷을 하나 더 두는 편이 훨씬 낫다

Nginx 공식 log module 문서를 다시 보니, log_format에서 필요한 변수를 얼마든지 뽑아 별도 포맷으로 남길 수 있고 JSON escaping도 지원한다. 나는 이런 상황이면 기존 access log를 버리기보다, probe용 로그를 옆에 하나 더 붙이는 쪽이 마음이 편하다.

log_format ai_probe escape=json '{'
  '"time":"$time_iso8601",'
  '"uri":"$request_uri",'
  '"status":$status,'
  '"ua":"$http_user_agent",'
  '"referer":"$http_referer",'
  '"accept":"$http_accept"'
'}';

access_log /var/log/nginx/ai-probe.log ai_probe;

실전에서는 probe URL도 같이 분리해 두는 편이 좋았다. 예를 들어 /?ai=chatgpt, /?ai=claude처럼 assistant마다 query string을 다르게 주면 grep 한 번으로 어느 테스트에서 나온 요청인지 바로 좁혀진다. 원문 글도 이 패턴을 썼고, 나도 이런 방식이 제일 덜 헷갈렸다.

jq -r 'select(.uri | test("ai=")) | [.time, .uri, .referer, .ua] | @tsv' /var/log/nginx/ai-probe.log

3. map으로 분류값을 미리 달아 두면 나중에 덜 헤맨다

여기서 내가 한 번 더 좋게 본 건 map module 문서였다. 문서에도 나오듯 map 변수는 실제로 사용할 때만 평가되기 때문에, 이런 분류 레이어를 미리 붙여 둬도 부담이 크지 않다. 나중에 로그를 볼 때 정규식으로 다시 파지 말고, 애초에 bucket을 하나 찍어 두는 편이 훨씬 편하다.

map $http_user_agent $ai_fetch_bucket {
  default "";
  ~*ChatGPT-User openai_fetch;
  ~*Claude-User anthropic_fetch;
  ~*Perplexity-User perplexity_fetch;
  ~*Manus-User manus_fetch;
  ~*(meta-webindexer|Meta-ExternalFetcher) meta_fetch;
}

map $http_referer $ai_click_bucket {
  default "";
  ~*chatgpt\.com chatgpt_click;
  ~*claude\.ai claude_click;
  ~*perplexity\.ai perplexity_click;
  ~*(gemini\.google\.com|google\.com) gemini_click;
  ~*(copilot\.microsoft\.com|bing\.com) copilot_click;
  ~*grok\.com grok_click;
}

이렇게 나눠 두면 나중에는 provider fetchclickthrough를 따로 집계하기 쉬워진다. 특히 assistant마다 관찰 가능성이 다르기 때문에, 분류 키를 한 칸 더 두는 게 중요하다.

4. 해석에서 제일 자주 틀리는 부분은 "안 보이면 없었다"고 말하는 순간이다

원문에서 제일 실무적으로 좋았던 대목도 이 부분이었다. Claude-User/robots.txt를 먼저 치는 패턴이 보였고, ChatGPT-User는 여러 IP에서 한 번에 후보 페이지를 당기는 식으로 보였다고 한다. 반면 Gemini는 provider-fetch 로그가 아예 안 잡히고, 대신 gemini.google.com이나 google.com referer가 붙은 clickthrough만 보였다고 한다. 이건 "Gemini가 너를 안 읽는다"보다 이 vendor는 로그에서 관찰 가능한 신호가 비대칭적이다에 더 가깝다.

Google 문서도 비슷한 방향을 뒷받침한다. Google-Extended는 별도 HTTP user-agent가 아니라 robots.txt control token이고, 실제 크롤링은 기존 Google user-agent로 이뤄진다고 적혀 있다. 즉 Google-Extended가 access_log에 안 찍히는 건 정상이다. Copilot이나 Grok처럼 plain browser처럼 보이는 케이스도 원문에서 같이 나왔는데, 이런 쪽은 로그만으로는 긍정 귀속이 거의 안 된다.

여기서 내가 남긴 메모는 하나다. retrieval bot, clickthrough visit, indexing bot, training bot을 절대 한 bucket에 넣지 말 것. GPTBot, ClaudeBot, Googlebot은 특정 사용자 질문에 답하려고 지금 들어온 fetch가 아닐 수 있다. 이 셋을 묶어 놓고 "AI가 오늘 나를 몇 번 읽었다"고 말하면 금방 숫자가 이상해진다.

5. 내가 바로 적용하고 싶은 최소 체크리스트

  • probe 로그를 분리: 기존 access log는 두고, ai_probe 같은 별도 포맷을 하나 더 둔다.
  • assistant별 query string을 분리: ?ai=chatgpt, ?ai=claude처럼 테스트 흔적을 남긴다.
  • fetch/click/index/training을 분리 집계: 같은 "AI 트래픽"으로 합치지 않는다.
  • robots 규칙을 bot 단위로 적는다: 특히 Claude-User, ClaudeBot, PerplexityBot, Google-Extended처럼 역할이 다른 토큰을 섞지 않는다.

Nginx 로그는 원래도 많은 걸 말해 주지만, AI 쪽에서는 무엇을 한 줄에 같이 세지 않을지를 먼저 정하는 쪽이 더 중요하다고 느꼈다. 오늘 내 기준에서 제일 유용했던 건 거창한 GEO 이론이 아니라, access_log에 user-agent와 referer를 한 칸 더 남겨 두는 습관이었다.

원문은 SurfacedBy의 nginx probe 글, HN 토론, Nginx log module 문서, Nginx map module 문서, Anthropic crawler 문서, Perplexity bots 문서, Google common crawlers 문서를 같이 보면 맥락이 더 또렷하다.

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.