After development of the old Clash Premium core stopped, router deployments gradually shifted to the actively maintained mihomo. Unlike desktop clients, a router has no graphical interface to manage the core’s config directory, permissions, DNS forwarding, or process supervision. The goal is not merely to open a proxy port, but to ensure that LAN traffic follows a clear, recoverable forwarding path.
This guide covers two common environments: an OpenWrt main router and a bypass router running Debian, Ubuntu, ImmortalWrt, or another Linux system. Examples use mihomo v1.19-series configuration syntax, with TUN takeover as the primary approach, while also noting the extra work required for traditional firewall forwarding. Before installation, verify filenames against the device architecture and the target release notes.
Choose the architecture first: main-router installation or a dedicated bypass router
Installing mihomo on the main router means running it on the same device that handles the WAN connection, DHCP, NAT, and firewall. Since every client already uses it as the default gateway, the path is short and individual gateway changes are usually unnecessary. The trade-off is that a bad proxy configuration can affect the entire home network; upgrading the core, restarting the firewall, or changing DNS incorrectly can also remove the recovery path on that same device.
A bypass router delegates traffic interception to a separate Linux device. The upstream main router continues handling the WAN connection, while the bypass router handles policy routing, DNS, and mihomo. This makes maintenance easier and lets you proxy only selected devices, such as a TV, phone, or test computer. However, a single-interface bypass router on the same subnet can suffer from asymmetric return paths, gateway loops, and DHCP conflicts, so addresses and forwarding relationships must be planned carefully.
| Comparison | Main-router installation | Dedicated bypass router |
|---|---|---|
| Default gateway changes | Usually unnecessary; clients continue using the main router’s address | Required through DHCP or manual client settings |
| Failure impact | A configuration error may affect the entire network | Switch client gateways back to the main router for a quick bypass |
| Hardware headroom | Limited by the router’s CPU, flash storage, and memory | Can use an x86 mini PC or a faster ARM device |
| Maintenance complexity | Direct traffic path, but WAN and firewall duties must also be managed | Adds a device and requires forwarding, return-path, and DHCP handling |
| Best suited for | Simple networks with sufficient hardware that need global traffic interception | Staged rollouts, frequent testing, and a fast fallback path |
Example addressing for a single-interface bypass router
Assume the main router uses 192.168.1.1 and the bypass router has a fixed address of 192.168.1.2. The bypass router itself should still use 192.168.1.1 as its default gateway, while clients that should be intercepted use 192.168.1.2 for both their gateway and DNS. If DHCP is used for bulk assignment, make sure there is only one DHCP server on the broadcast domain so two devices do not issue conflicting gateways.
For a single-interface design, source NAT on the bypass router is recommended so return traffic reliably passes back through it. If rules need to match the clients’ original addresses, add a static route to the client subnet on the main router and verify that return traffic does not bypass the router. Do not combine both approaches in a way that makes the actual packet path impossible to determine.
Check the hardware, system, and binary architecture
mihomo is a single core program, but rule sets, connection tracking, and Fake-IP mappings all consume memory. A small configuration commonly uses about 70–120 MB after startup; with large domain rule sets, multiple providers, and thousands of connections, usage can reach 180–300 MB. Older routers with only 128 MB of RAM have very little headroom. 512 MB is a more practical starting point, while 1 GB or more is better for long-term operation and frequent rule updates.
First use SSH to query the system architecture and kernel information:
uname -m
uname -a
getconf LONG_BIT
cat /etc/os-release
free -m
df -h
Common uname -m results |
Typical choice | Notes |
|---|---|---|
x86_64 |
linux-amd64 | For older CPUs without support for newer instruction sets, choose a compatibility build |
aarch64 |
linux-arm64 | Common on 64-bit ARM routers and development boards |
armv7l |
linux-armv7 | Confirm the system’s floating-point ABI against the release file notes |
mips or mipsel |
Use the corresponding big-endian or little-endian MIPS build | Older devices usually have tighter performance and memory constraints |
TUN mode also depends on the kernel device and related modules. Run ls -l /dev/net/tun to check the device node, then use ip rule, ip route, and nft list ruleset to confirm that policy routing and nftables management are available. On OpenWrt, check for kmod-tun under LuCI’s “System” → “Software”, or install a package over SSH that matches the current firmware kernel.
Create the config directory and run the first validation
On general-purpose Linux, place the program at /usr/local/bin/mihomo and the config directory at /etc/mihomo. When OpenWrt flash storage is limited, larger rule files can go on a mounted drive such as /mnt/data/mihomo, but the startup service must wait until the mount is available. The commands below assume the archive has already been extracted locally; replace the filename with the downloaded build for the actual architecture:
sudo install -m 0755 mihomo-linux-amd64 /usr/local/bin/mihomo
sudo mkdir -p /etc/mihomo
sudo install -m 0600 config.yaml /etc/mihomo/config.yaml
/usr/local/bin/mihomo -v
sudo /usr/local/bin/mihomo -t -d /etc/mihomo
-t tests the configuration, while -d sets the working directory. A successful test only means that the YAML parses and the main fields load; it does not prove that the subscription URL, DNS upstreams, or nodes are reachable. After starting the service, also check the logs for provider updates, listening ports, the TUN interface, and rule matches.
A subscription is not necessarily a complete, runnable configuration
Desktop clients often wrap subscription imports, overrides, and core parameters in a graphical interface. A router running the core directly needs a complete config.yaml. Some subscriptions return a full Clash configuration, while others return only a collection of nodes; the latter must be referenced through proxy-providers, with policy groups, DNS, rules, and listening ports added separately.
Subscription URLs usually contain account tokens, so restrict the config file to administrator-only read access. After updating a provider, retain the previous working cache so an empty response from the remote server does not immediately replace the active node set. Test once before replacing the main configuration:
cp /etc/mihomo/config.yaml /etc/mihomo/config.yaml.bak
/usr/local/bin/mihomo -t -d /etc/mihomo
# Recover if the test fails
cp /etc/mihomo/config.yaml.bak /etc/mihomo/config.yaml
Transparent proxying: TUN, DNS, and listening boundaries
With only mixed-port: 7890 enabled, LAN devices must manually enter an HTTP or SOCKS proxy. It cannot automatically intercept TV apps, game consoles, or software that ignores system proxy settings. Router deployments typically use TUN or nftables-based transparent forwarding. TUN centralizes the setup, but kernel modules, policy routing, DNS interception, and firewall compatibility still need to be handled.
The following basic fragment illustrates the key fields. Nodes, policy groups, and service rules must still be supplied by a complete configuration:
mixed-port: 7890
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
ipv6: false
external-controller: 127.0.0.1:9090
secret: "replace-with-a-long-random-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: 0.0.0.0:1053
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
nameserver:
- 223.5.5.5
- 119.29.29.29
auto-detect-interface identifies the actual outbound interface and can reduce edits when the dial-up interface name changes. With multi-WAN, stacked VPNs, or many container bridges, automatic detection may not match expectations; verify the interface in the logs and with ip route get 1.1.1.1. strict-route can reduce DNS or connection bypasses, but it also makes incorrect policy-routing settings more apparent.
The example binds the DNS listener to 1053 instead of the 53 port that dnsmasq may already use. On OpenWrt, configure dnsmasq to forward upstream queries to 127.0.0.1#1053; the usual LuCI path is “Network” → “DHCP/DNS” → “General Settings” → “DNS Forwardings”. Before saving, disable parallel forwarding that sends queries to other upstreams at the same time, or DNS results may not match the proxy rules.
Fake-IP filtering must cover LAN services
Fake-IP lets domain rules participate directly when a connection is established, but printers, NAS devices, casting, and LAN discovery may depend on real addresses. Add local domain suffixes, router management domains, and required connectivity-check domains to fake-ip-filter. LAN services should preferably receive private addresses from local DNS, rather than being resolved by public DNS first.
dns:
fake-ip-filter:
- "*.lan"
- "*.local"
- "router.local"
- "nas.home.arpa"
- "+.home.arpa"
Deployment steps for an OpenWrt main router
- Back up the existing configuration. In LuCI, open “System” → “Backup / Flash Firmware” and create a restorable configuration archive. Also record the WAN interface name, LAN address, and DHCP settings.
- Check free space. Run
df -hto inspect the overlay filesystem. The executable, Geo data, and rule sets may occupy tens of megabytes in total. If space is limited, use external storage rather than filling the root partition. - Install TUN support. Under “System” → “Software”, install the
kmod-tunpackage matching the firmware, then confirm that/dev/net/tunexists. - Place the program and configuration. The recommended paths are
/usr/bin/mihomoand/etc/mihomo/config.yaml, with permissions set to0755and0600respectively. - Run it in the foreground first. Execute
/usr/bin/mihomo -d /etc/mihomo, watch the TUN, DNS, and provider logs, then verify direct and proxied rules from a test client. - Add the startup service. Create the procd service only after foreground operation is stable, then check its enabled status under “System” → “Startup”.
OpenWrt uses procd to manage services. The following script shows the minimum structure; make sure the config directory matches the actual location:
#!/bin/sh /etc/rc.common
START=95
STOP=10
USE_PROCD=1
start_service() {
procd_open_instance
procd_set_param command /usr/bin/mihomo -d /etc/mihomo
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param limits nofile="1048576 1048576"
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger "mihomo"
}
Save it as /etc/init.d/mihomo, then run:
chmod 0755 /etc/init.d/mihomo
/etc/init.d/mihomo enable
/etc/init.d/mihomo start
logread -e mihomo
If the external drive is not mounted when the system starts, the service will not find its config or rule files. You can keep the config in internal flash and move only the provider cache to external storage; alternatively, adjust the startup order and check the directory in the script. Do not hide mount-order problems with a fixed multi-second delay.
systemd configuration for a general-purpose Linux bypass router
On Debian, Ubuntu, and other systems using systemd, create /etc/systemd/system/mihomo.service. Transparent routing requires creating a TUN interface, adding routes, and managing the relevant network capabilities. The simplest initial approach is to run it as root; once the deployment is stable, reduce permissions based on the features actually used instead of removing capabilities early and causing TUN creation to fail.
[Unit]
Description=mihomo routing core
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStartPre=/usr/local/bin/mihomo -t -d /etc/mihomo
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
Restart=on-failure
RestartSec=5
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now mihomo
sudo systemctl status mihomo
sudo journalctl -u mihomo -n 100 --no-pager
The bypass router must also enable IPv4 forwarding. Create /etc/sysctl.d/90-router-forward.conf:
net.ipv4.ip_forward=1
Then run sudo sysctl --system. If IPv6 is enabled, plan its default route, DNS, and firewall separately; enabling only net.ipv6.conf.all.forwarding=1 does not send all traffic through mihomo. Until IPv6 interception is complete, disable IPv6 on the test network to prevent clients from connecting over an IPv6 path that is outside the rules.
Do not duplicate firewall and TUN interception rules
When nftables, Docker, Tailscale, or another VPN is already present, rule priorities may conflict. After enabling mihomo, run ip rule, ip route show table all, and nft list ruleset separately to check for duplicate marks, a default route pointing back to TUN, or container subnets being sent through the proxy unintentionally. When TUN’s automatic routing already captures the target traffic, do not copy in a second set of legacy REDIRECT rules.
Performance checks and troubleshooting
After deployment, do not judge success solely by whether a webpage opens. Verify at least six areas: LAN direct access, proxied connections, DNS, rule matches, recovery after reboot, and stability under concurrent load. During testing, keep the same node and test client fixed so node fluctuations and local configuration changes do not overlap.
- Port status: Run
ss -lntupto check that7890,9090, and1053are listening as expected. - DNS path: Run
nslookup example.com 192.168.1.2, then check the mihomo logs for the corresponding query. - Gateway forwarding: Run a route trace from the client and confirm that the first hop is the planned main or bypass router, not an outdated gateway issued by another DHCP server.
- Rule matches: Temporarily set logging to
debugand verify that the target domain reaches the expected policy group. Restoreinfoafterward to avoid excessive long-term logging. - Recovery after reboot: Restart the entire device and check that mihomo, DNS forwarding, and TUN recover automatically, and that the provider cache remains readable.
- Fallback test: Stop mihomo, switch the test client’s gateway back to the main router, and confirm that basic networking still works independently.
As a hardware reference, under a 500 Mbps connection, mihomo v1.19-series, and roughly 8,000 rules, an Intel N5105 quad-core mini PC commonly reaches about 430–470 Mbps through TUN forwarding; a quad-core Cortex-A53 device with 1 GB of RAM commonly reaches about 180–240 Mbps. Results vary with the encryption protocol, node distance, NIC driver, rule-set size, and whether traffic sniffing is enabled, so they should not be treated as advertised device performance.
If speed tests reach only half the direct-connection rate, first check whether a single CPU core is near 100%, then inspect the MTU. With PPPoE, WireGuard, and TUN stacked together, an MTU that is too large can cause fragmentation or stalls on some sites. Test gradually from 1500 to 1492, 1480, or 1400; change one parameter at a time and record throughput, latency, and packet loss.
Handoff process for updating the core and configuration
Router updates should follow this order: validate the new file, keep the old file, then switch the service. Do not overwrite the running binary and restart immediately. Save the new version as mihomo.new, run the version check and configuration test, then stop the service, replace the file, and start it again. Once operation is confirmed stable, keep the old version for at least one update cycle.
sudo install -m 0755 mihomo.new /usr/local/bin/mihomo.new
sudo /usr/local/bin/mihomo.new -v
sudo /usr/local/bin/mihomo.new -t -d /etc/mihomo
sudo systemctl stop mihomo
sudo mv /usr/local/bin/mihomo /usr/local/bin/mihomo.previous
sudo mv /usr/local/bin/mihomo.new /usr/local/bin/mihomo
sudo systemctl start mihomo
sudo systemctl status mihomo
Configuration updates should likewise be written to a temporary file, tested, and then moved into place. When DNS modes, the TUN stack, rule-set formats, or provider behavior change, validate on one client first rather than combining the change with a firmware upgrade. With the old core retired, mihomo replaces more than an executable: configuration fields and traffic-interception methods will continue to evolve. A fixed validation and rollback process is more reliable than depending on a script that never changes.