배포 구조와 실행 범위
Linux 서버에서는 일반적으로 그래픽 클라이언트가 필요하지 않습니다. mihomo 코어를 독립적인 시스템 서비스로 실행하고, systemd가 시작·재시작·권한 제한·로그 수집을 담당하도록 구성하는 편이 안정적입니다. 설정 파일은 /etc/mihomo/에, 쓰기 가능한 실행 데이터는 /var/lib/mihomo/에, 바이너리는 /usr/local/bin/mihomo에 고정합니다. 이렇게 하면 프로그램 업데이트, 설정 변경, 캐시 정리를 서로 섞이지 않게 관리할 수 있습니다.
이 글의 예시는 Ubuntu 24.04.2 LTS, Debian 12.10, systemd 255/252, mihomo 1.19.10 환경을 기준으로 합니다. 배포판에 따라 패키지 관리 명령은 다를 수 있지만 systemd 서비스, TUN 장치, Linux capabilities를 다루는 방식은 동일합니다. 순정 Clash 코어를 사용하는 경우 일부 TUN·DNS·규칙 제공자 매개변수가 없을 수 있으므로, 먼저 버전 명령을 실행해 실제 코어 유형을 확인하세요.
uname -m
/usr/local/bin/mihomo -v
systemctl --version
ip -Version
uname -m의 일반적인 결과는 x86_64, aarch64, armv7l입니다. 바이너리를 다운로드할 때는 CPU 아키텍처가 반드시 일치해야 합니다. 버전 출력에는 mihomo 버전과 빌드 정보가 명확히 표시되어야 합니다. 명령 실행 직후 “Exec format error”가 나타난다면 대개 파일 권한이 아니라 아키텍처를 잘못 선택한 경우입니다.
| 경로 | 용도 | 권장 권한 |
|---|---|---|
/usr/local/bin/mihomo |
고정된 코어 실행 파일 | root:root 0755 |
/etc/mihomo/config.yaml |
주 설정·프록시·규칙 진입점 | root:clash 0640 |
/var/lib/mihomo/ |
Geo 데이터, 규칙 캐시 및 실행 데이터 | clash:clash 0750 |
/etc/systemd/system/mihomo.service |
systemd 서비스 유닛 | root:root 0644 |
바이너리와 전용 서비스 사용자 설치
아래 명령은 현재 CPU 아키텍처에 맞는 mihomo 실행 파일을 이미 준비해 /tmp/mihomo로 임시 저장했다고 가정합니다. 먼저 고정 경로에 설치한 다음 로그인할 수 없는 시스템 사용자를 생성합니다. 전용 사용자를 사용하면 서비스가 장기간 root 권한으로 실행되는 것을 막을 수 있으며, 설정 파일의 구독 주소·컨트롤러 키·프록시 정보도 지정된 사용자 그룹만 읽도록 제한할 수 있습니다.
sudo install -o root -g root -m 0755 /tmp/mihomo /usr/local/bin/mihomo
sudo useradd \
--system \
--home-dir /var/lib/mihomo \
--create-home \
--shell /usr/sbin/nologin \
clash
sudo install -d -o root -g clash -m 0750 /etc/mihomo
sudo install -d -o clash -g clash -m 0750 /var/lib/mihomo
/usr/local/bin/mihomo -v
시스템에 이미 clash라는 사용자가 있다면 useradd는 사용자가 이미 존재한다는 메시지를 반환합니다. 이때는 id clash로 홈 디렉터리와 사용자 그룹을 확인하면 됩니다. 서비스 사용자에게는 비밀번호가 필요하지 않으며 sudo나 docker 같은 추가 그룹에 넣을 필요도 없습니다.
최소 실행 설정 작성
다음 설정은 로컬 mixed 프록시 포트, REST 컨트롤러, TUN 가로채기를 먼저 구성합니다. mixed-port: 7890은 HTTP와 SOCKS5 연결을 모두 허용하고, 컨트롤러는 루프백 주소의 9090 포트로 제한합니다. DNS는 권한이 필요한 53번 포트 대신 비특권 포트 1053을 사용합니다.
mixed-port: 7890
bind-address: 127.0.0.1
allow-lan: false
mode: rule
log-level: info
ipv6: false
external-controller: 127.0.0.1:9090
secret: "충분히 긴 무작위 컨트롤러 키로 교체하세요"
profile:
store-selected: true
store-fake-ip: true
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
strict-route: true
dns-hijack:
- any:53
- tcp://any:53
dns:
enable: true
listen: 127.0.0.1:1053
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
default-nameserver:
- 223.5.5.5
- 1.1.1.1
nameserver:
- https://223.5.5.5/dns-query
- https://1.1.1.1/dns-query
proxies: []
proxy-groups:
- name: PROXY
type: select
proxies:
- DIRECT
rules:
- GEOIP,CN,DIRECT
- MATCH,PROXY
이 설정으로 프로세스·포트·TUN이 정상인지 확인할 수 있지만, 현재 PROXY 그룹에는 DIRECT만 있어 실제 프록시 전달은 이루어지지 않습니다. 실제 사용 시에는 기존 구독을 mihomo가 인식할 수 있는 설정으로 변환하거나, 서비스 제공자가 제공하는 호환 구독을 proxy-providers로 참조하세요. 웹 구독 주소를 단일 프록시 노드로 직접 입력해서는 안 됩니다.
저장한 뒤 권한을 제한하고 서비스 사용자 권한으로 구문을 검사합니다. -d는 실행 데이터 디렉터리를 지정하고 -f는 설정 파일을 명시합니다. 두 경로를 분리하면 mihomo가 다운로드하는 Geo 데이터와 규칙 캐시가 /etc에 저장되지 않습니다.
sudo chown root:clash /etc/mihomo/config.yaml
sudo chmod 0640 /etc/mihomo/config.yaml
sudo -u clash /usr/local/bin/mihomo \
-t \
-d /var/lib/mihomo \
-f /etc/mihomo/config.yaml
TUN 장치와 capabilities 설정
TUN 모드에는 커널이 제공하는 /dev/net/tun이 필요하며, 프로세스에는 라우팅·정책 라우팅·가상 네트워크 인터페이스를 수정할 권한도 필요합니다. 핵심 capability는 CAP_NET_ADMIN입니다. 기본 포트인 7890, 9090, 1053은 모두 1024보다 크므로 CAP_NET_BIND_SERVICE는 필요하지 않습니다.
TUN 모듈 확인
test -c /dev/net/tun && echo "TUN device ready"
ls -l /dev/net/tun
sudo modprobe tun
cat /sys/class/misc/tun/dev
정상이라면 마지막 명령에서 10:200이 출력됩니다. modprobe tun이 성공했는데도 장치가 없다면 현재 커널에서 TUN 지원이 제외되었는지 확인하세요. 컨테이너에서는 호스트가 문자 장치 10:200을 컨테이너에 개방해야 합니다. 컨테이너 내부에 같은 이름의 파일만 만들어서는 TUN 기능을 사용할 수 없습니다.
부팅할 때마다 모듈을 명시적으로 로드해야 한다면 modules-load 설정에 다음 내용을 추가할 수 있습니다.
echo tun | sudo tee /etc/modules-load.d/tun.conf
sudo systemctl restart systemd-modules-load.service
systemd에서 우선 capability 부여
Linux에서 흔히 사용하는 방법은 바이너리에 파일 capability를 기록하거나, systemd가 프로세스를 시작할 때 capability를 부여하는 것입니다. 서비스로 배포할 때는 두 번째 방식이 더 적합합니다. 바이너리를 업데이트해도 파일 교체로 인해 확장 속성이 사라지지 않고, 서비스 유닛에서 권한 범위를 직접 검토할 수 있기 때문입니다.
/etc/systemd/system/mihomo.service 생성:
[Unit]
Description=mihomo proxy service
Documentation=https://wiki.metacubex.one/
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=clash
Group=clash
WorkingDirectory=/var/lib/mihomo
ExecStartPre=/usr/local/bin/mihomo -t -d /var/lib/mihomo -f /etc/mihomo/config.yaml
ExecStart=/usr/local/bin/mihomo -d /var/lib/mihomo -f /etc/mihomo/config.yaml
Restart=on-failure
RestartSec=3s
TimeoutStopSec=15s
LimitNOFILE=1048576
AmbientCapabilities=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN
NoNewPrivileges=true
DevicePolicy=closed
DeviceAllow=/dev/net/tun rw
PrivateDevices=false
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/mihomo
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
[Install]
WantedBy=multi-user.target
AmbientCapabilities는 CAP_NET_ADMIN을 root가 아닌 서비스 프로세스에 전달하고, CapabilityBoundingSet은 목록에 없는 capability를 얻지 못하도록 제한합니다. DevicePolicy=closed와 DeviceAllow=/dev/net/tun rw를 함께 사용하면 실행에 필요한 TUN 문자 장치만 개방할 수 있습니다. ProtectSystem=strict는 시스템 디렉터리를 읽기 전용으로 만들며, ReadWritePaths는 mihomo가 상태 디렉터리에만 쓰도록 별도로 허용합니다.
배포판이나 구형 systemd가 특정 강화 지시어를 지원하지 않으면 로그에 알 수 없는 필드가 명확히 표시됩니다. 이 경우 실제 systemd 버전에 맞춰 해당 줄을 조정하세요. 단일 오류를 피하려고 root 사용자로 되돌려 실행해서는 안 됩니다.
파일 capability를 대안으로 사용하기
systemd로 관리하지 않는다면 바이너리에 CAP_NET_ADMIN을 부여할 수 있지만, 새 바이너리로 덮어 설치하면 이 속성이 사라집니다. 이 방식을 사용할 때는 systemd의 AmbientCapabilities를 중복으로 설정하지 마세요.
sudo setcap cap_net_admin=+ep /usr/local/bin/mihomo
getcap /usr/local/bin/mihomo
# 철회해야 할 때
sudo setcap -r /usr/local/bin/mihomo
nosuid가 적용된 일부 파일 시스템은 파일 capability를 무시하며, 일부 컨테이너 런타임도 capability를 필터링합니다. Operation not permitted가 발생하면 마운트 옵션, 컨테이너 capability 목록, 호스트 장치 매핑을 함께 확인해야 합니다.
시작·부팅 시 자동 실행 및 동작 확인
서비스 유닛을 작성한 뒤 먼저 systemd가 설정을 다시 읽도록 하고, 서비스를 시작한 다음 부팅 대상에 등록합니다. enable --now는 자동 시작 링크 생성과 서비스 시작을 동시에 수행합니다.
sudo systemctl daemon-reload
sudo systemctl enable --now mihomo.service
sudo systemctl status mihomo.service --no-pager
정상 상태라면 Active: active (running)이 표시되고 주 프로세스 사용자는 clash여야 합니다. 기준 환경에서는 약 620개 규칙과 프록시 제공자 2개를 포함한 설정의 초기 시작에 약 0.8~1.2초가 걸렸고, 안정적으로 실행된 뒤 상주 메모리는 약 45~85MB였습니다. 규칙 집합·Geo 데이터·연결 수가 늘어나면 메모리 사용량도 달라집니다.
포트와 인터페이스 확인
sudo ss -lntup | grep -E ':(7890|9090|1053)\b'
ip tuntap show
ip rule show
ip route show table all | grep -E '198\.18\.|default'
systemctl show mihomo.service -p User -p MainPID
포트의 수신 주소가 설정과 일치해야 합니다. 7890, 9090, 1053은 모두 127.0.0.1에서만 수신 대기해야 합니다. mihomo가 생성하는 TUN 인터페이스 이름은 버전과 설정에 따라 달라질 수 있으므로 고정된 이름만으로 판단해서는 안 됩니다. ip tuntap show, 정책 라우팅, 서비스 로그를 함께 확인하는 편이 더 정확합니다.
HTTP 프록시와 TUN 라우팅 확인
먼저 mixed 포트를 명시해 프록시 진입점을 테스트한 다음, 프록시 환경 변수를 지정하지 않은 일반 연결도 테스트합니다. 예시 정책 그룹은 현재 DIRECT를 가리키므로, 여기서는 요청이 mihomo를 거치는지, DNS 타임아웃이 없는지, 라우팅 루프가 발생하지 않는지를 확인하는 것이 핵심입니다.
curl --proxy http://127.0.0.1:7890 \
--connect-timeout 5 \
https://www.example.com/ -I
env -u http_proxy -u https_proxy -u all_proxy \
curl --connect-timeout 5 \
https://www.example.com/ -I
sudo journalctl -u mihomo.service -n 50 --no-pager
프록시 노드를 실제로 가져온 뒤에는 log-level을 잠시 debug로 바꿔 대상 도메인이 어떤 규칙에 매칭되고 어느 정책 그룹이 선택되는지 확인할 수 있습니다. 점검이 끝나면 info로 되돌리세요. 장시간 debug로 실행하면 로그가 과도하게 쌓입니다.
설정 업데이트와 무중단에 가까운 재로드
YAML을 수정한 뒤 바로 재시작하지 마세요. 먼저 테스트 명령으로 들여쓰기, 규칙 형식, 정책 그룹 참조, provider 경로가 유효한지 확인한 다음 systemd로 서비스를 재시작합니다. YAML은 공백으로 들여쓰며 Tab 문자는 파싱 오류를 일으킵니다. 규칙에서 참조하는 정책 그룹 이름도 proxy-groups와 완전히 일치해야 합니다.
sudo -u clash /usr/local/bin/mihomo \
-t \
-d /var/lib/mihomo \
-f /etc/mihomo/config.yaml
sudo systemctl restart mihomo.service
sudo systemctl status mihomo.service --no-pager
sudo journalctl -u mihomo.service --since "2 minutes ago" --no-pager
설정이 구독에서 제공되는 경우 먼저 임시 파일로 다운로드하고, 구문 테스트를 마친 뒤 정식 설정 파일을 원자적으로 교체하는 것이 좋습니다. 이렇게 하면 systemd가 재시작할 때 다운로드가 끝나지 않은 불완전한 YAML을 읽지 않습니다.
sudo install -o root -g clash -m 0640 \
/tmp/config.yaml \
/etc/mihomo/config.yaml.new
sudo -u clash /usr/local/bin/mihomo \
-t \
-d /var/lib/mihomo \
-f /etc/mihomo/config.yaml.new
sudo mv /etc/mihomo/config.yaml.new /etc/mihomo/config.yaml
sudo systemctl restart mihomo.service
mihomo의 외부 컨트롤러도 설정을 다시 불러올 수 있지만, 원격 자동화 스크립트에서는 컨트롤러 키·인터페이스 수신 범위·실패 시 롤백을 신중하게 처리해야 합니다. 단일 서버라면 먼저 테스트한 뒤 systemctl restart를 실행하는 편이 직관적이며, 중단 시간은 보통 수 초 이내입니다.
로그 확인과 일반적인 장애 해결
서비스가 반복해서 재시작될 때
먼저 현재 시작 주기의 로그와 종료 코드를 확인하세요. 서비스에 Restart=on-failure가 설정되어 있으므로 구문 오류가 연속 재시작을 일으킬 수 있습니다. 문제를 확인할 때는 먼저 서비스를 중지한 뒤 설정 테스트를 직접 실행하면 됩니다.
sudo journalctl -u mihomo.service -b --no-pager
sudo systemctl show mihomo.service \
-p ExecMainCode \
-p ExecMainStatus \
-p NRestarts
sudo systemctl stop mihomo.service
sudo -u clash /usr/local/bin/mihomo \
-t \
-d /var/lib/mihomo \
-f /etc/mihomo/config.yaml
흔한 오류로는 YAML 들여쓰기 불일치, 존재하지 않는 정책 그룹 참조, clash 사용자가 설정 파일을 읽을 수 없는 경우, provider 디렉터리에 쓰기 권한이 없는 경우가 있습니다. namei -l /etc/mihomo/config.yaml로 경로의 각 디렉터리 권한을 단계별로 확인할 수 있습니다.
TUN 생성 시 permission denied가 표시될 때
TUN 장치, 서비스 capability, systemd 장치 정책을 순서대로 확인하세요. /dev/net/tun의 존재 여부만 확인해서는 충분하지 않습니다. 프로세스에 CAP_NET_ADMIN이 없으면 인터페이스를 생성하거나 정책 라우팅을 기록할 수 없습니다.
ls -l /dev/net/tun
systemctl show mihomo.service \
-p AmbientCapabilities \
-p CapabilityBoundingSet \
-p DevicePolicy
sudo journalctl -u mihomo.service -n 100 --no-pager | \
grep -Ei 'tun|permission|operation not permitted'
서비스가 LXC, Docker 또는 다른 컨테이너에서 실행된다면 호스트에서 /dev/net/tun을 개방하고 NET_ADMIN을 부여해야 합니다. 클라우드 서버 커널에 TUN이 포함되어 있지 않다면 컨테이너 설정만으로 커널 기능을 보완할 수 없습니다.
TUN 활성화 후 DNS 타임아웃이 발생할 때
먼저 1053 포트가 수신 대기 중인지 확인한 다음 상위 DNS에 연결할 수 있는지 점검하세요. 시스템에서 systemd-resolved도 실행 중이라면 일반적으로 로컬 127.0.0.53:53을 사용하지만, 이는 mihomo가 127.0.0.1:1053에서 수신 대기하는 것과 직접 충돌하지 않습니다. 실제로 주의할 부분은 TUN의 DNS 가로채기가 mihomo 자체의 상위 DNS 요청을 다시 TUN으로 보내 루프를 만드는지 여부입니다.
sudo ss -lnup | grep ':1053'
resolvectl status
dig @127.0.0.1 -p 1053 www.example.com
sudo journalctl -u mihomo.service -n 100 --no-pager | \
grep -Ei 'dns|timeout|loop'
처리 순서는 다음과 같아야 합니다. 기본 외부 인터페이스가 올바르게 인식되는지 확인하고, nameserver 주소에 연결되는지 확인한 뒤, 규칙이 DNS 상위 서버를 잘못 차단하는지 점검하세요. 네트워크 인터페이스가 여러 개인 서버에서는 TUN 설정에 인터페이스를 명시해 Docker·WireGuard·임시 VPN 인터페이스가 자동으로 선택되지 않도록 하는 것이 좋습니다.
로컬에서는 연결되지만 LAN 장치에서는 연결되지 않을 때
예시 설정은 의도적으로 bind-address: 127.0.0.1과 allow-lan: false를 사용하므로 다른 장치가 7890에 접속할 수 없습니다. LAN에 프록시를 제공해야 한다면 수신 주소를 서버의 내부 주소로 변경하고 LAN 접근을 활성화하세요. 방화벽에서는 신뢰할 수 있는 네트워크 대역만 허용해야 합니다.
# 예시 전용: 192.168.10.0/24에서 TCP 7890 접근 허용
sudo ufw allow from 192.168.10.0/24 to any port 7890 proto tcp
sudo ufw status numbered
외부 컨트롤러 9090은 계속 루프백 주소에 바인딩하고 SSH 포트 포워딩으로 접근하는 것이 좋습니다. 반드시 내부 주소에서 수신해야 한다면 최소한 무작위 키를 설정하고 접근 가능한 출발지 주소를 제한하세요. 컨트롤러 인터페이스를 인터넷에 직접 노출해서는 안 됩니다.
운영 및 유지 관리 점검 목록
- 바이너리 아키텍처가
uname -m결과와 일치하는지 확인하고, 업데이트 후mihomo -v를 다시 실행합니다. - 주 설정은
root:clash가 소유하고 권한은0640으로 설정하며, 실행 디렉터리는 서비스 사용자가 쓸 수 있어야 합니다. - 설정을 교체할 때마다
mihomo -t를 실행하고, 테스트가 통과한 뒤 서비스를 재시작합니다. - TUN 모드에는
CAP_NET_ADMIN만 부여하며, 기본 포트에는 낮은 포트 바인딩 권한이 필요하지 않습니다. /dev/net/tun이 존재하는지 확인하고, 컨테이너 환경에서는 호스트 장치 매핑과 capability 부여를 모두 완료합니다.- 컨트롤러를
127.0.0.1:9090에 바인딩하고 키를 설정하며, 원격 관리는 SSH 포워딩을 사용합니다. - 로그 수준은
info로 유지하고, 짧은 장애 점검 시간에만debug로 전환합니다. - TUN 라우팅을 수정하기 전에 자동 중지 작업을 준비해 기본 경로 변경으로 원격 SSH가 끊기는 상황을 방지합니다.
이 단계를 마치면 네트워크가 준비된 뒤 systemd가 mihomo를 시작하고, 비정상 종료 시 3초 후 자동으로 재시도하며, 종료할 때는 15초의 중지 시간을 확보합니다. 설정·바이너리·실행 데이터를 পৃথ পৃথ 관리하므로 이후 코어 업데이트, 구독 조정, 서버 이전도 명확한 경계에 따라 진행할 수 있습니다.