1 먼저 층을 나눈다: «가져오기 실패» vs «파싱 실패」
구독 URL은 브라우저에서 열면 예쁜 Base64 덩어리로 보이지만, 코어는 그걸 User-Agent·쿠키·리퍼 조건이 맞는 HTTP 세션에서만 내려주는 원격 설정이기도 합니다. 403 Forbidden이면 권한·핫링크 방지·지역/ASN 기반 WAF·봇 탐지 쪽이 의심됩니다. 반면 200인데도 노드가 0이면, 응답이 HTML 오류 문구·빈 목록·Clash로 파싱할 수 없는 포맷일 수 있어 «가져온 뒤 해석」 문제로 옮겨갑니다.
이 글의 초점은 첫 HTTP 단계입니다. 이미 원격의 최종 열기 URL·쿼리 토큰이 만료되었는지(공지된 로테이션 주기)는 서비스 쪽 이야기이므로, 여기서는 같은 URL을 Mihomo·curl이 어떤 헤더로 치느냐에 맞췄습니다. GUI에서 «전체 복사」로 얻은 주소에 눈에 안 보이는 공백·끝에 붙은 잘못된 문자가 있으면 404·403이 섞여 나올 수 있으니, 메모장에 붙여 한 줄로 정리한 뒤 다시 넣는 습관이 좋습니다.
2 코어 로그·API로 최종 URL과 상태를 본다
log-level을 info 이상으로 두고, 갱신 직후 provider 이름 옆에 붙는 다운로드 URL·응답 코드를 확인합니다(클라이언트·버전에 따라 표기 위치는 다르며, 공통은 «실제로는 어떤 주소에 요청이 나갔는가»입니다). GUI에 «실패 사유·HTTP」가 요약돼 있으면 그대로 캡처해 두는 것이 이후 같은 조건으로 curl을 쏠 때의 기준이 됩니다. external-controller와 yacd를 켜 둔 환경이면, 리스트·로그 탭을 병행하면 provider 별 마지막 갱신 시각도 대조하기 쉽습니다.
동일 머신·같은 DNS 환경에서 터미널에 다음과 같이 헤더만 먼저 봅니다(실제 토큰·도메인은 본인 것으로 바꿉니다). -L은 스택된 리다이렉트를 따라갑니다.
curl -sSIL "https://example.com/your-subscription-path"
여기서 30x가 이어지다 끝에 4xx/5xx로 끝나는지, 아니면 200에 content-type이 text/html인지(로그인 페이지·차단 HTML)를 구분하세요. 403이면 IP·ASN·봇 탐지·핫링크 대비이므로, 브라우저와 curl 기본의 User-Agent 차이를 아래 절로 넘깁니다.
3 User-Agent: Clash·Mihomo 기본과 CDN 규칙
일부 Cloudflare 뒤의 정적·구독 엔드포인트, 혹은 Bitly·자체 짧은 링크 뒤의 정책이 非브라우저 UA를 403으로 돌리는 사례가 있습니다. 공항 패널에 «Clash 용 / Surge 用」처럼 권장 UA 문자열이 안내돼 있으면 그대로 쓰는 것이 맞고, 공식 문서가 없다면 브라우저에 가까운 짧은 문자열로 AB 테스트해 볼 수 있습니다. Mihomo proxy-providers의 type: http 블록에는 header에 User-Agent를 박는 예가 흔합니다.
proxy-providers:
my-sub:
type: http
url: "https://example.com/sub"
path: ./proxies/my-sub.yaml
interval: 3600
header:
User-Agent: "clash" # or provider-recommended / browser-like string
curl로 재현할 때는 -A "..."로 같은 문자열을 맞춥니다. 401·액세스 키 쿼리를 요구하는 URL이라면, UA가 아니라 쿼리·Authorization 쪽이 병목이므로 혼동하지 않는 것이 좋습니다. Referer를 강제하는 핫링크 방지라면 Referer: https://same-site/를 header에 같이 싣는 케이스도 있습니다(공식 안내·실제 응답 헤더를 기준으로).
4 짧은 링크, 302, HTTPS 혼합
공항 패널이 주는 링크가 http://로 시작하거나 서브도메인이 단계마다 바뀌는 경우, 최종이 https인지, 중간에 광고·통계용으로 302가 한 번 더 있는지 맨끝을 확인합니다. Mihomo는 HTTP 클라이언트는 따르지만, 인증서가 다른 호스트로 튀는 크로스 도메인 302·HSTS·쿠키 유지가 필요한 흐름이면, 브라우저는 되고 코어는 막히는 불일치가 납니다. curl -sSIL 출력에 나오는 location: 체인을 아래로 읽으면서, 의도한 구독 호스트에 닿는지 봅니다.
자체 단축 URL·bit.ly 류는 «만료·비공개·지역» 정책이 바뀌기 쉬우므로, 대시보드에서 원문·직접 링크(공개돼 있을 때)를 쓰는 편이 장기적으로 안정적입니다. 직접 링크로 바꾼 뒤에 403이 사라지는 사례는 흔히 리다이렉트 중에 끼어 있던 중간 도메인이 클라이언트를 거부했을 때로 설명됩니다. 리눅스·headless 쪽에서 이전과 동일 config를 쓰는 경우에도, 이번 절이 «브라우저에선 되는데 서버·코어에선 왜 403?」의 1차 답이 됩니다.
5 TLS·SNI·자체 서명: MITM·기업 CA
응답이 끊기거나 인증서 오류로 떨어지면, ISP·사내 방화벽·로컬 AV의 HTTPS 검사(HTTPS MITM)가 새 루트를 끼워 넣지 않는 한 OS·코어 신뢰 저장소에 없어 실패하는 경우가 있습니다. 셸에서 curl -v로 verify 단계의 메시지를 보면, self-signed·호스트명 불일치가 잡힐 수 있습니다(운영 측이 «인증서 경고는 무시»라는 공지를 냈는지도 함께 확인). «구독만» 자체 인증·비정규 포트다면, 가능한 한 공인 CA·정상 호스트로 바꾸는 쪽이 안전하고, 클라이언트에서 인증을 끄는 옵션은 보안상 권장되지 않습니다(반드시 위험을 이해한 뒤·제한적 환경에서만).
IPv4·IPv6·이중 스택
AAAA가 붙은 도메인이 터널·ISP에 따라 다른 경로로 가며 한쪽만 403·타임아웃을 내는 경우, 일시적으로 IPv4 고정·다른 DNS·DoH로 나눠 보면 원인 분리가 됩니다. Meta DNS 글의 맥락과 겹칠 수 있으나, 구독 엔드포인트는 보통 시스템 DNS·터널 밖에서 먼저 raw HTTP로 검증한 뒤, 코어 의도한 DNS 정책에 넣는 순서를 추천합니다.
6 Content-Encoding, gzip, 문자 집합
200 OK인데 깨진 바이너리·빈 YAML이면, gzip/deflate 처리·Base64 단계·UTF-8/UTF-8-sig와 같은 문자 이슈가 섞일 수 있습니다. 대부분의 클라이언트는 Content-Encoding에 맞게 풀어 주지만, 중간 변환·Sub-Converter·스크립트로 한번 더 감쌌다가 이중으로 압축된·잘못된 MIME을 넣는 실수는 여전히 있습니다. 수동 점검은 «브라우저 다른 이름으로 저장」과 같은 URL을 curl로 받은 뒤 file·hex로 비교」가 빠를 때가 많습니다.
Clash / Mihomo가 노드로 인식하려면 Proxy 목록·provider 문법이 맞는 YAML/구독 여야 합니다. 범 JSON·HTML·빈 200이면, HTTP는 성공해도 노드 0이 정상에 가깝습니다. Sub-Converter로 Clash 프로필로 맞출 때, 스키마·include·template이 어긋나면 빈 providers를 내는 점에 유의하세요(이 문서는 «변환·템플릿」보다 원격 1차에 힘을 둡니다).
7 왜 Sub-Converter·mixin과 짝이 되는가
Sub-Converter는 원시 구독·다양한 입력을 Clash 호환 YAML/구독로 바꾸는 레이어이고, mixin은 이미 로딩된 구성 위에 덧씌우는 구성입니다. 403이면 변환기에 넣기 전에 원 URL이 살아 있는지(동일 LAN·동일 exit) 먼저 맞추는 편이 낫습니다. 반대로 200인데 필드가 비정상이면 변환 파라미터·include·client 옵션을 점검하세요. 이렇게 당신의 티켓을 HTTP / 파싱 / 룰 중 어디에 붙이는지 정하면, 포럼·지원팀에 질문할 때도 로그와 한 줄 요약이 훨씬 날카로워집니다.
8 정리: 순서를 지키면 원인이 좁아진다
Mihomo로 구독·proxy-providers를 쓰다 403·0 노드가 보이면, (1) 갱신 직후 로그/상태로 HTTP 코드, (2) User-Agent·Referer 등 권장 헤더가 공지와 맞는지, (3) curl -sSIL로 리다이렉트 끝의 최종 URL·TLS가 의도한지, (4) 본문이 진짜 구독·Clash로 파싱 가능한가를 순서대로 봅니다. 클라이언트·데스크톱·터미널이 같은 룰을 쓰고 싶다면, 앱뿐 아니라 Clash 배포·문서에 정리된 최신 클라이언트로 config를 옮기며 재현해 보는 것도 좋은 비교입니다. 동일 YAML이 한쪽만 실패할 때, 그때 HTTP 차이가 힌트가 됩니다.
한 번에 모든 옵션을 바꾸지 말고, 한 변수씩 되돌리면서 재갱신하세요. 공항이 IP 제한·동시 세션 정책을 바꾸면 예전엔 됐다가 갑자기 403이 되기도 합니다. 그때는 이 글보다 서비스 공지·갱신된 직접 링크를 우선 확인하는 것이 맞습니다. 정상으로 돌아온 뒤에는 provider interval·log-level을 다시 info 이하로 맞추어 디스크·노이즈를 줄이면 충분합니다.