Installing a client on every device is tedious, and televisions, projectors and IoT devices cannot run one at all. Deploy Mihomo on a soft router or NAS and every device on the Wi-Fi is covered automatically.
Three ways to hook it up
This article takes side router plus TProxy as its main line, the most practical combination for a home.
1. Installing Mihomo
Using a Linux amd64 soft router or NAS:
# download and extract (pick the architecture to match)
gunzip mihomo-linux-amd64-v1.19.29.gz
chmod +x mihomo-linux-amd64-v1.19.29
sudo mv mihomo-linux-amd64-v1.19.29 /usr/local/bin/mihomo
mihomo -v
# prepare the working directory
sudo mkdir -p /etc/mihomoARM devices (Raspberry Pi, ARM NAS) take the linux-arm64 build; uname -m printing aarch64 means that one.
2. The configuration file
/etc/mihomo/config.yaml:
# ===== basics =====
mixed-port: 7890
tproxy-port: 7895 # for TProxy
redir-port: 7892 # for REDIRECT (optional)
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
ipv6: false
unified-delay: true
tcp-concurrent: true
external-controller: 0.0.0.0:9090
secret: "set a sufficiently long random password"
external-ui: /etc/mihomo/ui
# ===== DNS =====
dns:
enable: true
listen: 0.0.0.0:1053
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- "*.lan"
- "*.local"
- "+.home.arpa"
- "+.pool.ntp.org"
- "time.*.com"
- "+.msftconnecttest.com"
- "+.msftncsi.com"
- "captive.apple.com"
default-nameserver: [223.5.5.5, 119.29.29.29]
nameserver:
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
fallback:
- https://1.1.1.1/dns-query
fallback-filter:
geoip: true
geoip-code: CN
# ===== where nodes come from =====
proxy-providers:
main:
type: http
url: "your subscription URL"
interval: 3600
path: ./providers/main.yaml
header:
User-Agent: ["clash.meta"]
exclude-filter: "(?i)remaining|expire|website|traffic"
health-check:
enable: true
url: http://www.gstatic.com/generate_204
interval: 300
lazy: true
# ===== policy groups =====
proxy-groups:
- name: "PROXY"
type: select
proxies: ["AUTO", "HK", "JP", "SG", DIRECT]
- name: "AUTO"
type: url-test
use: [main]
url: http://www.gstatic.com/generate_204
interval: 300
tolerance: 50
lazy: true
- name: "HK"
type: url-test
use: [main]
filter: "(?i)HK|Hong ?Kong"
interval: 300
tolerance: 50
- name: "JP"
type: url-test
use: [main]
filter: "(?i)JP|Japan"
interval: 300
tolerance: 50
- name: "SG"
type: url-test
use: [main]
filter: "(?i)SG|Singapore"
interval: 300
tolerance: 50
- name: "FINAL"
type: select
proxies: ["PROXY", DIRECT]
# ===== rules =====
rules:
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
- IP-CIDR,100.64.0.0/10,DIRECT,no-resolve
- DOMAIN-SUFFIX,lan,DIRECT
- DOMAIN-SUFFIX,local,DIRECT
# per-device routing (examples)
- SRC-IP-CIDR,192.168.1.50/32,DIRECT # a family member's phone stays direct
- SRC-IP-CIDR,192.168.1.60/32,PROXY # the TV is always proxied
# ordinary routing
- GEOIP,CN,DIRECT
- MATCH,FINAL3. The systemd service
/etc/systemd/system/mihomo.service:
[Unit]
Description=Mihomo Daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
Restart=always
RestartSec=5
ExecStartPre=/usr/local/bin/mihomo -t -d /etc/mihomo
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
[Install]
WantedBy=multi-user.targetThe -t in ExecStartPre is a config check — a broken config then fails to start rather than taking down a working service, and that detail saves a great deal of trouble.
sudo systemctl daemon-reload
sudo systemctl enable --now mihomo
sudo systemctl status mihomo
journalctl -u mihomo -f4. Approach A: point the gateway manually (start here)
Touch no firewall rules; on the devices that need proxying, change:
- Gateway: to the soft router's address (
192.168.1.2, say) - DNS: also
192.168.1.2
The strength of this approach is fault tolerance — if Mihomo dies it only affects the devices you changed, and nobody else in the house notices.
5. Approach B: TProxy transparent proxying
Zero device configuration, at the price of firewall rules on the gateway.
Enable kernel forwarding
sudo tee /etc/sysctl.d/99-mihomo.conf > /dev/null <<'EOF'
net.ipv4.ip_forward = 1
net.ipv4.conf.all.route_localnet = 1
EOF
sudo sysctl --systemiptables rules
#!/bin/bash
# /etc/mihomo/tproxy.sh
TPROXY_PORT=7895
ROUTE_TABLE=100
FWMARK=1
# policy routing: marked packets go to the local table
ip rule add fwmark $FWMARK table $ROUTE_TABLE 2>/dev/null
ip route add local default dev lo table $ROUTE_TABLE 2>/dev/null
# our own chain
iptables -t mangle -N MIHOMO 2>/dev/null
iptables -t mangle -F MIHOMO
# let private and reserved ranges through untouched
for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 \
172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
iptables -t mangle -A MIHOMO -d $net -j RETURN
done
# mark everything else and hand it to the TProxy port
iptables -t mangle -A MIHOMO -p tcp -j TPROXY --on-port $TPROXY_PORT --tproxy-mark $FWMARK
iptables -t mangle -A MIHOMO -p udp -j TPROXY --on-port $TPROXY_PORT --tproxy-mark $FWMARK
# apply to forwarded traffic (from other devices)
iptables -t mangle -A PREROUTING -j MIHOMO
# DNS hijacking: redirect port 53 queries from devices to Mihomo
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 1053
iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 1053A cleanup script:
#!/bin/bash
# /etc/mihomo/tproxy-clear.sh
iptables -t mangle -D PREROUTING -j MIHOMO 2>/dev/null
iptables -t mangle -F MIHOMO 2>/dev/null
iptables -t mangle -X MIHOMO 2>/dev/null
iptables -t nat -F PREROUTING 2>/dev/null
ip rule del fwmark 1 table 100 2>/dev/null
ip route del local default dev lo table 100 2>/dev/nullAttach the rules to systemd so they come and go with the service:
[Service]
ExecStartPost=/etc/mihomo/tproxy.sh
ExecStopPost=/etc/mihomo/tproxy-clear.shGetting devices to use it
In the main router's DHCP settings, set both the default gateway and the DNS server to the soft router's address. Devices pick it up after reconnecting to the Wi-Fi.
6. Fault tolerance: do not annoy your household
This is the most important section for a home deployment.
A safe subscription update script
#!/bin/bash
# /usr/local/bin/update-mihomo.sh
set -euo pipefail
CONF=/etc/mihomo/config.yaml
TMP=$(mktemp)
trap 'rm -f "$TMP"' EXIT
curl -fsSL --max-time 30 -A "clash.meta" -o "$TMP" "your subscription URL"
# basic sanity check
grep -q "^proxies:\|^proxy-providers:" "$TMP" || { echo "subscription content looks wrong"; exit 1; }
# let mihomo validate it too
cp "$CONF" "${CONF}.bak"
cp "$TMP" "$CONF"
if mihomo -t -d /etc/mihomo; then
systemctl restart mihomo
echo "updated"
else
cp "${CONF}.bak" "$CONF"
echo "validation failed, rolled back"
exit 1
fiAdd it to cron:
0 4 * * * /usr/local/bin/update-mihomo.sh >> /var/log/mihomo-update.log 2>&1With a proxy-provider you do not actually need this script — the core pulls nodes on its own schedule and the main config never changes. That is an important reason to prefer a provider over a whole subscription.
7. The web panel
external-ui: /etc/mihomo/ui
external-ui-name: metacubexdPut the panel's files in /etc/mihomo/ui, then visit http://router-ip:9090/ui in a browser, enter the secret, and you can switch nodes, inspect connections and read logs from a web page.
Other people in the house can use that page to switch nodes themselves rather than asking you.
8. Performance and resources
The order to optimise in when performance is tight:
- Trim the rule sets (drop streaming and ad lists you do not use)
- Switch rule sets to the binary mrs format
- Set
log-leveltowarning, neverdebug - Reduce
health-checkfrequency - TProxy uses fewer resources than TUN, so prefer TProxy
9. Diagnostics
# service status
systemctl status mihomo
journalctl -u mihomo -n 100 --no-pager
# what is listening
ss -tulnp | grep mihomo
# does the API respond
curl -H "Authorization: Bearer your-secret" http://127.0.0.1:9090/version
# test the proxy port from another device
curl -x http://192.168.1.2:7890 -I https://www.google.com
# hit counts on the iptables rules
iptables -t mangle -L MIHOMO -v -nIn short
- Get "side router plus manual gateway" working first, and only move to TProxy once it is stable
- secret is mandatory;
0.0.0.0:9090is open to your entire LAN - Use a proxy-provider rather than a whole subscription, so the core updates itself and the main config never moves
- Add
-tto ExecStartPre so a broken config cannot displace a running service - Give important devices a direct SRC-IP-CIDR rule so the household is unaffected
- Before touching TProxy, schedule an automatic cleanup to leave yourself a way back
Related: the control API and web panels and managing several subscriptions.
Related docs
127.0.0.1 inside a container is not the host. Configuration for all three Docker scenarios — daemon pulls, build time and run time — plus two approaches for WSL2's different networking modes.
A reference table worth bookmarking. The proxy command for each tool, where its config file lives, how to undo it, and why some tools ignore your environment variables.
The complete RESTful API exposed by external-controller: switching nodes, inspecting connections, reloading the config, deploying a web dashboard, and several practical automation scripts.