자동 검사에서 오류가 난 링크, 바로 지워도 될까?
자동 링크 검사는 실패했는데 브라우저에서는 글이 열렸다. 실제로 결과가 갈린 사례를 바탕으로 링크를 삭제하기 전에 접근 여부, 본문 내용, 인용 근거를 나눠 확인하는 방법을 설명한다.
출처 링크를 자동으로 검사했더니 오류가 나왔다. 그런데 브라우저로 같은 주소를 열면 글이 보였다. 검사 결과만 믿고 그 출처를 지워도 될까?
2026년 9월 24일 이 블로그의 한 주소에서 실제로 그런 차이를 확인했다. 같은 서버에서 한 도구는 접근 거부를 받았고 다른 도구는 본문을 받았다. 자동 검사에서 읽지 못했다는 사실만으로 페이지가 사라졌다고 판단할 수는 없었다.
오류 목록은 삭제 목록이 아니라 확인할 목록이다
링크를 바로 삭제하기 전에 브라우저에서 열어 기대한 제목과 본문이 있는지 확인한다. 열리더라도 인용하려는 주장을 그 본문이 실제로 뒷받침하는지 따로 읽어야 한다. 열리지 않으면 ‘확인 필요’로 남기고 접근 거부인지, 주소가 사라진 것인지 구분한다.
이 글은 AI나 자동화 도구로 출처를 점검하는 사람이 어떤 결과를 사람이 다시 봐야 하는지 정하기 위한 사례다. 먼저 실제 관측을 보여주고, 뒤에서 접근 거부 코드인 403의 의미와 판단 순서를 설명한다. 자동 검사의 정확도나 시간 절감 효과를 측정한 글은 아니다.

같은 페이지라도 요청 조건에 따라 결과가 달라질 수 있음을 나타내는 개념도다.
실측: 한 URL에서 갈린 결과
측정 대상은 qsike.com 발행 글 한 개의 URL이었다. 같은 Linux 서버의 두 HTTP 클라이언트가 약 0.1초 차이로 같은 URL을 요청했고, 다른 환경의 Windows 브라우저로도 같은 날 페이지를 열었다.
| 측정 조건 | 결과 |
|---|---|
| Python 3.12.3 urllib — Linux 서버, 2026-09-24 12:05:39 UTC | HTTP 403, 본문 17바이트(error code: 1010), 기사 본문 없음 |
| curl 8.5.0 — 같은 서버, 12:05:39 UTC | HTTP 200, 본문 34,459바이트, 기사 본문 있음 |
| Windows 브라우저 — 별도 네트워크 환경, 같은 날 | 기사 제목·본문 표시 (HTTP 상태는 측정하지 않음) |
두 HTTP 응답 모두 server: cloudflare였고, Cloudflare가 챌린지 응답에 붙이는 cf-mitigated 헤더는 둘 다 없었다. 결과의 분기는 같은 서버·거의 같은 시각 조건에서 클라이언트 도구 차이와 함께 관측됐으며, 전송 헤더·프로토콜 등 요청 내부 차이는 통제하지 않았다.
error code 1010이 말하는 것과 말하지 않는 것
본문의 error code: 1010은 Cloudflare 차단 코드다. 공식 문서에 따르면 1010은 “사이트 소유자가 브라우저 시그니처(browser’s signature)를 근거로 접근을 차단”한 상태이며, 관련 설정으로 Browser Integrity Check가 언급된다 — Error 1010 · Cloudflare Support docs.
이번 응답과 공식 문서로 확인할 수 있는 것은 “브라우저 시그니처 기반 차단 기준에 걸려 거부되었다”는 데까지다. 어떤 규칙이 실제로 발동했는지는 서버 쪽 로그 없이는 알 수 없고, 이번 측정에서는 설정·로그를 확인하지 않았으므로 특정 설정을 원인으로 단정하지 않는다.
cf-mitigated 헤더는 챌린지 페이지(Challenge Page) 응답 탐지용 표지다 — Detect a Challenge Page response · Cloudflare challenges docs. 이번 403 응답은 직접적인 요청 거부로 챌린지 페이지가 아니었고, 실제로 이 헤더가 없었다. 헤더 부재가 “어떤 보안 차단도 없었다”를 뜻하지는 않는다 — 403이 반환된 것 자체가 그 반례다.
403은 죽은 링크가 아니다
RFC 9110은 403(Forbidden)을 “서버가 요청을 이해했지만 수행을 거부한다(refuses to fulfill it)“로 정의한다 — RFC 9110: HTTP Semantics §15.5.4. 403은 “이 요청이 이 조건에서 거부되었다”는 기록이지, “이 URL이 누구에게도 작동하지 않는다”는 증거가 아니다.
자동 검사의 403 하나로 다음 결론은 따라오지 않는다.
- 링크가 죽었다 — 자원 부재는 404/410의 영역이고, 403은 거부의 기록이다.
- 사이트 전체 장애 — 다른 경로·다른 클라이언트 확인 없이는 단정할 수 없다.
- Google 차단이나 SEO 문제 — Search Console 실시간 테스트가 없었으므로 검색엔진·색인에는 어떤 결론도 내릴 수 없다.
단정할 수 있는 것은 “이 조건에서 보낸 요청이 거부되었다”는 사실 하나뿐이다.

두 응답의 차이를 비교하는 설명용 삽화다. 기호는 실제 상태 코드나 측정 화면을 재현한 것이 아니다.
상태 코드별로 다른 질문
“실패” 한 칸에 뭉뚱그리기 전에: 링크 검사에서 자주 만나는 상태 코드는 각자 다른 질문에 답한다. 아래는 표준 정의다.
| 상태 | 표준 정의의 핵심 | 링크 검사에서 답하는 질문 |
|---|---|---|
| 200 OK | 요청이 성공했다 | 접근은 됐지만, 본문이 기대한 글인지는 별도 확인 필요 |
| 403 Forbidden | 이해했으나 수행을 거부한다 | 이 조건에서 거부됨 — 부재의 증거 아님 |
| 404 Not Found | 현재 표현을 찾을 수 없다 | 부재 신호 — 다만 서버가 존재를 숨길 때도 쓰는 코드 |
| 410 Gone | 더 이상 사용할 수 없고 영구적일 가능성이 높음 | 강한 부재 신호 |
| 429 Too Many Requests | 요청 빈도를 초과했다 | 링크 상태가 아니라 검사 도구의 속도 문제 — 간격을 두고 재시도 |
429는 자동 검사에서 흔하다. 여러 URL을 연속 요청하다 속도 제한에 걸리면 “링크 오류”로 잘못 기록되기 쉬운데, 이는 링크 문제가 아니라 검사 일정 조정 문제다.

접근 여부, 본문 확인, 주장과 근거의 일치를 각각 살피는 세 질문을 표현했다.
단정 전에 나눠야 할 세 질문
출처 링크 검증은 별개의 질문 셋으로 나뉜다.
- URL에 접근되는가 — HTTP 상태와 본문 길이. 클라이언트·네트워크·시점에 따라 결과가 달라질 수 있다.
- 본문이 실제인가 — 200이어도 기대한 제목·본문이 있는지 확인해야 한다. 상태 코드만으로는 부족하다.
- 출처가 주장을 뒷받침하는가 — 접근되고 본문이 있어도, 인용하려는 주장과 내용이 일치하는지는 별개의 검토 단계다.
셋을 뭉뚱그리면 403 하나가 곧바로 “깨진 출처”라는 오판이 된다.
자동 검사에서 403을 만났을 때의 판단 순서
링크 검사에서 403을 받으면 다음 순서를 권한다. 모두 정상적인 진단이며, 보안 기능 끄기·우회·봇 위장은 권하지 않는다.
- 상태 코드를 구분한다. 404/410(부재)과 403(거부)을 같은 “실패”로 처리하지 않고, 429(속도 제한)도 따로 분류한다.
- 본문 길이와 내용을 본다. 17바이트짜리
error code: 1010같은 차단 안내문은 기사 본문이 아니다. - 다른 클라이언트로 재시도한다. curl·브라우저로 같은 URL을 다시 요청해 결과가 갈리면 “조건에 따른 거부”로 기록한다.
- 측정 조건을 기록하고 판단을 유보한다. 클라이언트·시각·상태·본문 크기·차단 안내문 유무를 남기고, “죽은 링크” 확정은 사람·추가 검증으로 미룬다.
기록 양식
링크 검사 로그에는 아래 항목을 쓰면 된다.
- URL / 측정 시각(UTC)
- 클라이언트와 버전 / 네트워크 환경
- HTTP 상태 / server 헤더 /
cf-mitigated유무 - 본문 바이트 수 / (선택) 본문 SHA-256 / 기사 제목 존재 여부
- 판단 한 줄 — 접근됨 / 조건부 거부 / 부재 / 확인 필요
이 몇 줄만 남아도 “403 → 죽은 링크” 식 판단을 다시 검토할 근거가 된다.
가상 예시: 검사 스크립트가 403을 만났을 때
참고: 다음은 가상 예시입니다. 도구·URL·개수는 모두 설명용 설정이며, 시간 절감이나 오판정 감소 같은 실측 성과는 제시하지 않습니다.
하루 한 번 출처 URL 20개를 검사하는 스크립트가 있다고 가정하자. 어느 날 한 URL에서 403이 돌아왔다. 위 판단 순서와 기록 양식을 적용하면:
- 상태를 “조건부 거부”로 분류하고, 같은 배치의 404와는 다른 칸에 기록한다.
- 클라이언트·시각·본문 크기·차단 안내문 유무를 로그에 남긴다.
- 그 URL만 브라우저로 열어 본문이 보이는지 사람이 확인한다.
- 보이면 “접근됨(클라이언트 조건부 거부)“으로, 안 보이면 “확인 필요”로 남기고 인용 유지 여부를 판단한다.
이 절차가 오판정을 얼마나 막는지는 각자 로그에 “확인 필요 → 사람 재확인” 건수를 기록해야 알 수 있다. 절감 수치는 약속하지 않는다.
이 측정의 한계
이 사례에는 분명한 한계가 있다. User-Agent만 바꿔 격리한 대조 실험이 아니고, 브라우저 관측은 다른 네트워크라 같은 IP 조건 비교가 아니다. Cloudflare 설정을 바꾸지 않았고 Search Console 실시간 테스트도 하지 않았다. 그래서 Googlebot·색인 결론, before/after 성능·오류율 개선 주장은 하지 않는다. 남는 것은 “동일 서버 두 클라이언트에서 결과가 갈렸다”는 조건부 관측 하나다.
마치며
자동 링크 검사의 가치는 속도가 아니라 결과 해석에 있다. 403은 링크의 사망 선고가 아니라 “이 요청은 거부되었다”는 조건부 기록이며, 그 조건을 기록한 사람만이 다음 판단을 바로 할 수 있다. 예약 발행 글이 안 보일 때의 확인 순서는 이전 글 블로그 자동 발행을 예약했는데 글이 안 올라왔다: 무엇을 확인해야 할까에서 다뤘다. 그 글이 “발행 상태 확인”을 다뤘다면, 이번 글은 “확인 결과의 해석”을 다룬다.
확인한 출처 / 근거
- Error 1010 · Cloudflare Support docs — error code 1010의 공식 의미(브라우저 시그니처 기반 차단)와 일반 원인 설명에 사용.
- Detect a Challenge Page response · Cloudflare challenges docs —
cf-mitigated헤더의 용도(챌린지 응답 탐지)와 부재의 한계를 설명하는 데 사용. - RFC 9110: HTTP Semantics §15.5.4 (403 Forbidden) — 403의 표준 정의(서버가 이해했으나 수행 거부)에 사용.
- RFC 9110: HTTP Semantics §15.3.1 (200 OK) · §15.5.5 (404 Not Found) · §15.5.11 (410 Gone) — 상태 코드 비교표의 표준 정의(성공/부재/영구 제거)에 사용. 절 번호는 원문 목차에서 확인했다.
- RFC 6585: Additional HTTP Status Codes §4 (429 Too Many Requests) — 429의 표준 정의(요청 빈도 초과)에 사용. 속도 제한과 링크 부재를 구분하는 근거.
- 참고: 2026-09-24 측정 기록(Python urllib 403/본문 17바이트, curl 200/본문 34,459바이트, 별도 Windows 브라우저 본문 표시)은 내부 확인 기록이며 공개 URL이 없다. 수치는 모두 당시 실측값이다.
- 참고: 측정 대상은 블로그 자동 발행을 예약했는데 글이 안 올라왔다: 무엇을 확인해야 할까였다.
- 참고: 상태 코드 비교표의 정의는 모두 표준 문서 발표값이며, 이번 실측에서 200·403 외의 상태는 관측하지 않았다. ‘가상 예시’ 절은 전체가 가정이며 실측 결과가 없다.