Clash Meta Web 패널 원격 접속: bind-address, secret, 방화벽으로 external-controller 보강하기
Clash Meta(mihomo)는 설정을 바꾸고 로그를 보게 해 주는 REST API(external-controller)와, UI가 내장된 빌드라면 Web 대시보드(external-ui 등)까지 한 번에 열립니다. 로컬에서만 쓸 때는 127.0.0.1에 묶어 두면 비교적 안전하지만, 스마트폰이나 다른 PC에서 패널을 열고 싶다고 0.0.0.0에 그대로 올려 두면 같은 Wi-Fi의 누구나, 나쁜 경우에는 인터넷 쪽 스캔까지 노출될 수 있습니다. 본문에서는 검색하신 키워드 그대로 리스닝 주소(바인딩), secret 기반 인증, OS·라우터·클라우드 방화벽, 그리고 가능하면 SSH 터널로 접는 패턴까지 단계별로 정리합니다. Docker·헤드리스 배경은 Clash Meta Docker 가이드, LAN 프록시와 주소 개념은 mixed-port·allow-lan 글과 겹쳐 읽으면 이해가 빨라집니다.
external-controller가 열어 주는 «창»과 위험
컨트롤러 포트는 단순한 «상태 페이지»가 아니라, 구현·클라이언트에 따라 프록시 모드 전환, 규칙·노드 목록 조회, 심지어 설정 리로드까지 가능한 관리면입니다. 공격자가 익명으로 닿을 수 있다면, 내부망 기기를 무력화하거나 트래픽을 임의 노드로 돌리는 시나리오까지 상상할 수 있습니다. 그래서 운영 원칙은 단순합니다. 필요한 인터페이스만, 필요한 주소에만, 항상 인증과 방화벽을 전제로 노출합니다.
- 로컬 전용: 개발·디버깅은
127.0.0.1:<port>로 충분한 경우가 많습니다. - LAN 관리: 집·사무실 같은 신뢰 구역이라도, 최소한
secret과 방화벽으로 출발지 IP를 줄입니다. - 인터넷: 가능하면 코어 포트를 직접 노포워딩하지 말고, VPN·SSH·리버스 프록시(TLS·ACL) 뒤에 둡니다.
1단계: 리스닝 주소 정하기(bind / 호스트 부분)
설정에서 가장 흔한 형태는 external-controller: <host>:<port>입니다. 호스트 부분이 곧 바인딩 범위를 결정합니다. 문서나 커뮤니티에서 «bind-address»라고 부르는 논의는 대개 이 호스트(또는 별도 바인딩 옵션)를 가리킵니다.
| 값(예) | 의미 | 언제 쓰나 |
|---|---|---|
127.0.0.1:9090 |
루프백만 수신 | 본인 PC 브라우저·로컬 스크립트만 |
0.0.0.0:9090 |
모든 IPv4 인터페이스 | LAN·다중 NIC; 반드시 뒤이어 인증·방화벽 |
| LAN 고정 IP | 특정 NIC에만 | 서버에 주소가 여럿일 때 범위를 줄임 |
정리
0.0.0.0은 «편함»과 «노출 면적»을 동시에 키웁니다. 먼저 127.0.0.1에서 API가 살아 있는지 확인한 뒤, LAN이 꼭 필요할 때만 넓히세요.
Windows에서는 external-controller-pipe 같은 네임드 파이프 경로를 쓰는 빌드도 있습니다. 이 경우 HTTP 포트 노출 이야기와는 다른 보안 모델이므로, UI에서 실제로 어떤 전송을 쓰는지 먼저 확인합니다.
2단계: secret 설정과 Authorization 헤더
Meta 계열에서는 secret: "긴_무작위_문자열" 형태로 API 토큰을 두는 것이 일반적입니다. 클라이언트는 HTTP 요청에 Authorization: Bearer <secret>를 붙입니다. 웹 대시보드도 같은 값을 묻거나, 로컬 스토리지에 저장하는 방식이 흔합니다—브라우저를 남에게 열어 주지 않도록 주의하세요.
secret 만들 때
- 짧은 단어나 생일·전화번호 금지—패스워드 매니저나
openssl rand수준의 엔트로피. - 설정 파일·백업 디스크·스크린샷에 그대로 남지 않게 권한과 공유 범위 관리.
- 유출 의심 시 즉시 교체하고, 열어 둔 포트·방화벽 규칙도 함께 재점검.
# Minimal excerpt — adjust port and secret
external-controller: 127.0.0.1:9090
secret: "replace-with-a-long-random-token"
# Optional: path to bundled dashboard static files (build-dependent)
# external-ui: ui
secret을 비워 두면 편하지만, LAN에 포트를 연 순간 사실상 무인증 관리 API가 됩니다. «나만 쓰는 집»이라도 게스트 Wi-Fi·침해된 IoT 한 대면 끝나므로, 비우지 않는 것을 기본값으로 두는 편이 안전합니다.
3단계: 로컬에서 동작 검증(curl)
방화벽을 건드리기 전에, 같은 머신에서 API가 응답하는지 확인합니다. 아래는 예시이며 포트·토큰은 본인 환경에 맞게 바꿉니다.
# Example: version endpoint (use your port and secret)
curl -sS -H "Authorization: Bearer YOUR_SECRET_HERE" \
http://127.0.0.1:9090/version
401이 나오면 헤더 이름·Bearer 철자·따옴표에 붙은 공백부터 의심합니다. 연결 거부면 코어가 안 떠 있거나, 아직 127.0.0.1이 아닌 다른 주소만 듣고 있는지 확인합니다.
4단계: OS 방화벽으로 출발지 제한
리스닝을 LAN으로 넓겼다면, 운영체제 방화벽에서 TCP <controller-port>를 «모든 곳»이 아니라 «관리자 PC 서브넷» 정도로 한정합니다. 제품마다 UI 이름이 다르지만, 공통 목표는 최소 허용입니다.
Windows Defender 방화벽
«고급 설정» 인바운드 규칙에서 TCP 포트를 열고, 범위(원격 IP)를 가능하면 관리 스테이션의 주소로 제한합니다. 집에서는 DHCP로 IP가 바뀌므로, 관리용 PC에는 예약 주소를 주거나, 규칙을 주기적으로 점검해야 합니다.
Linux(netfilter / ufw / firewalld)
서버에 직접 올린 Meta라면 ufw allow from <admin-ip> to any port 9090 proto tcp 같은 식이 이해하기 쉽습니다. Docker를 쓴다면 호스트 방화벽 + 퍼블리시된 포트가 겹치므로, 실제로 어디서 필터되는지 한 번 그려 보세요. 헤드리스 전반은 Linux systemd Meta 글과도 연결됩니다.
macOS
방화벽 프로파일과 서명된 앱 규칙에 따라 동작이 달라집니다. 포트가 열렸는데도 막히면 «방화벽 로그 + 실제 리스닝 주소(lsof/netstat)»를 함께 봅니다.
라우터 포트포워딩
가정용 공유기에서 9090 같은 관리포트를 WAN에 그대로 노출하는 구성은 피하세요. 스캐너가 먼저 찾는 패턴입니다. 꼭 필요하면 VPN 또는 SSH 터널 뒤로 옮깁니다.
Docker·NAS에서 포트 퍼블리시할 때
Compose에서 ports: - "9090:9090"처럼 적으면 호스트의 모든 인터페이스에 대개 바인딩됩니다. 관리자 본인만 호스트에 SSH로 들어가서 쓸 거라면 127.0.0.1:9090:9090처럼 호스트 측을 루프백으로 제한하는 편이 안전합니다. 컨테이너 내부 external-controller는 0.0.0.0:9090이어도 되고, 바깥으로 나가는 관문은 호스트 바인딩에서 조이는 식으로 생각하면 정렬이 쉽습니다.
외지·외부망에서 접속하는 더 나은 패턴
집 밖에서 패널이 필요하면 우선순위는 대략 다음과 같습니다. (1) 집 VPN(WireGuard 등)에 먼저 붙은 뒤 LAN처럼 접속, (2) SSH 로컬 포워딩 -L 9090:127.0.0.1:9090으로 원격 브라우저를 안전하게 붙이기, (3) 정말로 공개해야 한다면 TLS 종단·ACL·레이트 리밋이 있는 리버스 프록시와 별도 계정 체계. 코어 포트를 인터넷에 «생」으로 내는 선택은 최후의 최후로 두는 것이 좋습니다.
CORS와 브라우저
브라우저에서 다른 출처의 API를 부를 때 CORS에 막히는 경우, 배포본에 external-controller-cors 류 옵션이 있는지 문서를 확인합니다. 보안상 필요한 출처만 허용하고, 불필요한 * 와일드카드는 피합니다.
TLS·리버스 프록시 한 줄 메모
일부 환경에서는 컨트롤러에 TLS를 직접 얹거나, Nginx/Caddy 뒤에 두고 HTTPS로만 노출합니다. 이 글의 중심은 «주소·시크릿·방화벽»이지만, 공인 네트워크를 오갈 계획이라면 평문 HTTP를 종료 지점까지 끌고 가지 않는 것이 좋습니다. 인증서·도메인·자동 갱신은 프록시 측에 맡기고, 뒤쪽 코어는 계속 127.0.0.1만 듣게 두는 구성이 흔합니다.
FAQ
브라우저에서 UI만 404
사용 중인 패키지가 정적 파일을 포함하는지, external-ui 경로가 실제 디렉터리와 맞는지 확인합니다. API는 되는데 UI만 없다면 빌드·배포 채널을 바꿔야 할 수 있습니다.
휴대폰만 안 붙는다
PC 방화벽은 열렸는데 휴대폰만 실패하면, 휴대폰이 «게스트 네트워크 격리」에 있는지, DNS로 잘못된 사설 IP를 보고 있는지부터 봅니다. 가능하면 휴대폰 브라우저가 아니라 curl이 있는 다른 호스트로 좁힙니다.
secret을 git에 올려 버렸다
공개 저장소라면 즉시 토큰 폐기·재발급이 맞습니다. 히스토리에 남았다면 로테이션만으로는 부족할 수 있으니, 팀 규칙에 따라 저장소 정리나 키 교체 절차를 밟습니다.
실무 체크리스트
external-controller호스트가 의도한 범위(loopback / LAN / 특정 IP)인지 확인.secret을 강하게 두고, Bearer 헤더로 API 응답을 검증.- OS·라우터·클라우드 SG에서 TCP 포트를 필요한 출발지만 허용.
- Docker는
127.0.0.1:포트:포트등으로 호스트 노출면을 제한. - 외부에서는 VPN·SSH·TLS 프록시 등 추가 계층을 우선 검토.
클라이언트와 함께
패널은 «보이는 관리면»일 뿐이고, 일상 사용은 여전히 검증된 데스크톱·모바일 클라이언트가 편한 경우가 많습니다. 규칙·DNS·TUN을 손볼 때는 로그와 API가 함께 있을 때 가장 빠르니, 본 글의 보안 순서를 기본값으로 깔아 두고 시작하세요.