Run mihomo on an OpenWrt Bypass Router: Transparent Proxy Setup
Configure an OpenWrt bypass router with mihomo: gateway roles, service setup, transparent proxy and DNS interception, plus a recovery plan for outages.
1. Network topology: route one test device through the bypass router first
Deploying mihomo on an OpenWrt bypass router involves three distinct steps: the client sends traffic to the bypass router, the firewall passes connections to the core, and the core selects an outbound route according to its rules. Starting mihomo alone or changing DNS alone does not complete a transparent proxy setup.
This guide uses a single-interface, same-subnet IPv4 test topology, based on the firewall4 and nftables environment commonly found in OpenWrt 23.05 and 24.10. The addresses below form a complete example; check each one against your actual network before running the commands. Do not apply these rules on a device already running OpenClash, another transparent proxy plugin, or custom policy routing.
| Device | Address or setting | Role |
|---|---|---|
| Main router | 192.168.10.1/24 | Handles the broadband connection and outbound NAT; continues to provide DHCP |
| OpenWrt bypass router | 192.168.10.2/24 | Runs mihomo and receives traffic from the test client |
| Bypass router default gateway | 192.168.10.1 | Used by the core to connect to proxy nodes and upstreams for direct traffic |
| Test client | 192.168.10.100/24 | Set the gateway and DNS to 192.168.10.2 |
| Bypass router LAN interface | br-lan | Receives test traffic from devices on the same LAN |
Gateway and DHCP roles
- In OpenWrt, go to “Network” → “Interfaces” → “LAN” → “Edit” and set a static address, IPv4 gateway, and DNS server. Confirm that the bypass router itself can reach the internet.
- On the same screen, open “DHCP Server” → “General Setup” and select “Ignore interface” so the main router and bypass router do not both hand out addresses.
- Start by changing only the test client’s IPv4 gateway and DNS. Do not immediately change the gateway option in the main router’s DHCP settings to point to the bypass router.
- Check the LAN zone policies under “Network” → “Firewall.” This guide assumes the common setup where LAN input, output, and intra-zone forwarding are allowed. Restricted networks need separate allow rules.
With a same-subnet bypass router, replies to direct connections may go straight from the main router to the client without passing through the bypass router again. This asymmetric path can affect connection statistics and stateful firewall checks. For initial testing, keep the main router’s existing outbound NAT; do not add masquerading across the entire LAN just to make the path “symmetric.”
2. Core setup: verify the binary and a minimal configuration first
mihomo is a proxy core developed as a continuation of Clash Meta, not an OpenWrt management plugin. Running the core directly means the administrator is responsible for its configuration directory, process supervision, subscription updates, and firewall rules. Choose a Linux binary for the device’s CPU architecture; do not rely on the product name alone.
ubus call system board
uname -m
df -h /overlay
free -m
mkdir -p /etc/mihomo
chmod 700 /etc/mihomo
chmod 755 /usr/bin/mihomo
/usr/bin/mihomo -v
These commands assume the extracted core is installed at /usr/bin/mihomo. For example, aarch64 usually means ARM64, but ARMv7 and MIPS devices also require checking the instruction set, byte order, and floating-point ABI. If you see Exec format error, check the architecture first rather than changing the firewall. Use the version reported by mihomo -v on your device, and save that output for troubleshooting.
Configuration file: verify the interception path with DIRECT first
Save the following as /etc/mihomo/config.yaml. This is a testable direct-connection baseline with no proxy nodes configured, so it will not send traffic to a proxy server. First confirm that traffic reaches the core, then add your own nodes and proxy groups. This helps separate network issues from subscription issues.
mixed-port: 7890
tproxy-port: 7893
allow-lan: true
bind-address: 192.168.10.2
mode: rule
log-level: info
ipv6: false
dns:
enable: true
listen: 0.0.0.0:1053
ipv6: false
enhanced-mode: redir-host
nameserver:
- udp://223.5.5.5:53
- udp://119.29.29.29:53
rules:
- MATCH,DIRECT
7890 is the mixed HTTP/SOCKS proxy port, 7893 is the TPROXY port, and 1053 is mihomo’s DNS listener. This example uses redir-host to avoid introducing a Fake-IP address pool and additional routing during initial setup. The two upstream DNS servers are example resolvers used for direct connections; they are not encrypted DNS.
chmod 600 /etc/mihomo/config.yaml
/usr/bin/mihomo -t -d /etc/mihomo -f /etc/mihomo/config.yaml
Test the configuration before enabling traffic interception. This baseline does not depend on GEOIP, GEOSITE, or rule sets. If you use a subscription configuration instead, check that its data files, rule-provider download URLs, and proxy-group references are all present. Configurations generated for desktop clients may also include DNS, TUN, or controller settings that are unsuitable for a router.
3. Process supervision: start mihomo with procd
OpenWrt uses procd to manage services. Do not run a long-lived process in an SSH session or start it repeatedly from a task scheduled every minute. The script below starts the process, restarts it after an unexpected exit, and sends its logs to the system log. It does not install transparent proxy rules.
Save the following as /etc/init.d/mihomo:
#!/bin/sh /etc/rc.common
START=95
STOP=10
USE_PROCD=1
start_service() {
/usr/bin/mihomo -t -d /etc/mihomo \
-f /etc/mihomo/config.yaml || return 1
procd_open_instance
procd_set_param command /usr/bin/mihomo \
-d /etc/mihomo -f /etc/mihomo/config.yaml
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param file /etc/mihomo/config.yaml
procd_close_instance
}
chmod 755 /etc/init.d/mihomo
/etc/init.d/mihomo enable
/etc/init.d/mihomo start
logread -e mihomo
ss -lnptu | grep -E ':(7890|7893|1053)\b'
If ss is not installed, use netstat -lnptu to check listening ports. If the logs report a port conflict, look for an old proxy process, a duplicate service, or a DNS plugin first. Do not switch ports without updating the firewall rules as well.
4. Transparent proxying: the roles of TPROXY, policy routing, and the firewall
On Linux, TPROXY can receive TCP and UDP traffic while preserving the original destination address. It relies on three components working together: nftables selects connections, policy routing sends marked packets to the local machine, and mihomo’s transparent listener receives them. Setting tproxy-port alone, without the first two components, will not route client traffic into the core.
The example below matches only 192.168.10.100 and is intended for a test environment where conflicts have been ruled out. Before running it, use ip -4 rule show, ip -4 route show table 100, and nft list ruleset to confirm that route table 100, rule priority 100, and mark 0x1 are not already in use. Multi-WAN, VPN, and other proxy setups often use these resources already.
Kernel modules and forwarding settings
Install the module that provides nftables TPROXY support from the package feed for your firmware. On firmware using opkg, check for kmod-nft-tproxy. The package must match the running kernel version. If you see Operation not supported or nftables cannot find the TPROXY expression, troubleshoot the module first.
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.all.send_redirects=0
sysctl -w net.ipv4.conf.br-lan.send_redirects=0
ip -4 rule add priority 100 fwmark 0x1/0x1 lookup 100
ip -4 route add local 0.0.0.0/0 dev lo table 100
Disable ICMP Redirects to stop the bypass router from advising clients to use the same-subnet main router instead. Check interface-level settings too. If strict reverse-path filtering is enabled, also inspect net.ipv4.conf.all.rp_filter and net.ipv4.conf.br-lan.rp_filter. Strict mode may drop valid packets in a policy-routing setup; adjust it to loose mode or disable it as your network security requirements allow.
nftables rules that intercept traffic from a single source address
Save the following as /etc/mihomo/test-tproxy.nft. This is a standalone test table; do not paste it into a table include file used by firewall4. Check the syntax before loading it, and do not load it again if a table with the same name already exists.
table inet mihomo_test {
chain ingress_proxy {
type filter hook prerouting priority mangle; policy accept;
iifname != "br-lan" return
ip saddr != 192.168.10.100 return
meta l4proto { tcp, udp } th dport 53 return
ip daddr {
0.0.0.0/8,
10.0.0.0/8,
100.64.0.0/10,
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
} return
meta l4proto { tcp, udp } counter \
tproxy to :7893 meta mark set 0x1 accept
}
chain ingress_dns {
type nat hook prerouting priority dstnat; policy accept;
iifname "br-lan" ip saddr 192.168.10.100 \
meta l4proto { tcp, udp } th dport 53 \
counter redirect to :1053
}
}
nft -c -f /etc/mihomo/test-tproxy.nft
nft -f /etc/mihomo/test-tproxy.nft
nft list table inet mihomo_test
DNS traffic on port 53 returns from the transparent proxy chain first, then the later NAT chain redirects it to port 1053. This prevents ordinary DNS requests from being picked up by TPROXY first. Private and multicast addresses are passed through to preserve basic functions such as router management and access to LAN devices. If your organization also uses public address ranges internally or has dedicated routes, add those to the exclusions too.
An accept in an nftables test chain does not skip other base chains that firewall4 processes later. TPROXY packets retain their original destination port, so allowing only traffic destined for port 7893 cannot replace the input policy. This guide assumes a test environment that allows LAN input. If your LAN input policy is restrictive, add limited firewall4 input rules for TCP/UDP packets from the LAN with the relevant mark, and allow DNS on port 1053 separately.
5. DNS interception: traditional DNS, encrypted DNS, and resolver loops
Setting both the client’s gateway and DNS server to the bypass router does not mean all DNS lookups will pass through mihomo. The rules above intercept TCP/UDP traffic on port 53 that passes through the bypass router. Browser Secure DNS, system Private DNS, and app-integrated DoH may connect to other resolvers over ports 443 or 853.
- Basic test: Temporarily disable encrypted DNS on the test client to verify the traditional DNS path, then re-enable it and check your policies.
- Port assignments: Keep OpenWrt dnsmasq on port 53 and use port 1053 for mihomo. Do not let both processes compete for the same listening address and port.
- Upstream loops: Do not point mihomo’s upstream at dnsmasq while also pointing dnsmasq’s upstream back to mihomo. This creates a DNS loop or causes timeouts.
- Proxy node hostnames: After adding proxy nodes, check how their server hostnames are resolved. Use
proxy-server-nameserverto specify a separate resolver for node hostnames, so establishing a proxy connection does not depend on that same proxy. - LAN hostnames: If you need to resolve DHCP hostnames, configure a dedicated resolver policy for internal domains and make sure it does not create the loop described above.
The example intercepts DNS only from the test client’s source address, so DNS requests sent by the bypass router itself are not caught by the same rule. If you later expand the rules to all source addresses, review the exemptions for core upstreams, node connections, and traffic originating on the router. Do not simply remove the source-address restriction.
Why TUN mode is not just another switch to turn on
mihomo also supports TUN, but TUN requires a supported kernel device and a plan for routing and DNS interception. Settings such as auto-route and auto-redirect can change existing routes and nftables rules, so do not combine them directly with the manual TPROXY setup in this guide. On a single-interface bypass router, also verify that forwarded traffic actually enters TUN; testing requests from the router itself is not enough.
If you choose TUN, first remove the test table and policy routes from this guide, then deploy TUN using the configuration for your current mihomo version. Both methods aim to send selected connections into the core. Troubleshooting is easier when there is one clear interception path than when several entry points are enabled at once.
6. Validation sequence: from a direct-connection baseline to proxy policies
First confirm that the bypass router itself can access the internet, then test client traffic. The baseline example ends with the rule MATCH,DIRECT, so it is normal for the outbound IP to match a regular direct connection. That does not mean traffic was not intercepted. Check counters and core logs to verify.
- Configuration check: Run
mihomo -tand confirm there are no YAML indentation errors, unknown proxy groups, or failed rule-provider loads. - Explicit proxy check: Run the curl command below on the test client. This checks the port 7890 proxy listener only; it does not test transparent interception.
- DNS check: Query a domain through
192.168.10.2and check whether the DNS NAT counter increases. NAT chain counters track new connection matches, not the number of DNS packets. - Transparent connection check: Disable any explicit proxy on the client, then visit a public website. Check the TPROXY counter and connection logs in
logread -e mihomo. - Proxy egress check: Import a working node, configure the intended proxy groups, and replace the final rule before repeating the request. Confirm that the outbound name in the logs matches expectations, then test large downloads, video, and UDP apps.
# Run on the test client
curl -I --proxy http://192.168.10.2:7890 https://example.com/
nslookup example.com 192.168.10.2
curl -I --noproxy '*' https://example.com/
# Run on the bypass router
ip -4 rule show
ip -4 route show table 100
nft list table inet mihomo_test
logread -e mihomo
If browsing works but games do not, common causes include missing UDP interception, a node that does not support UDP, or the app using a different address family. If small pages load but larger transfers stall, check MTU, Path MTU Discovery, and packet loss on the link. Do not assume every issue is a DNS problem or change kernel parameters without logs.
During testing, temporarily disable software and hardware flow offloading under “Network” → “Firewall” to reduce interference from the fast path. Once the setup is stable, re-enable each option one at a time and retest. This guide does not provide measured throughput figures: results depend on the device CPU, node protocol, encryption algorithm, and upstream bandwidth. Use repeated tests under the same conditions on your own device.
7. Outage recovery: restore the gateway first, then remove interception rules
If you change the DHCP gateway for the entire network to the bypass router, clients will not automatically switch back to the main router if the bypass router loses power. procd can handle a process crash, but it cannot fix a hardware failure, incorrect rules, or an upstream outage. Prepare a recovery plan before expanding the deployment.
Recovery steps for a single test client
- Change the test client’s IPv4 gateway and DNS back to
192.168.10.1, or restore automatic settings from the main router’s DHCP server. - On the bypass router, delete the test nftables table to stop intercepting connections.
- Remove only the policy route and local route created for this test, then stop the core.
- Reconnect to the network or clear the client’s DNS cache, then confirm that direct access works again. Existing proxy connections will usually need to be re-established.
nft delete table inet mihomo_test
ip -4 rule del priority 100 fwmark 0x1/0x1 lookup 100
ip -4 route del local 0.0.0.0/0 dev lo table 100
/etc/init.d/mihomo stop
/etc/init.d/mihomo disable
Run deletion commands only after confirming that the objects were created for this test. Do not use nft flush ruleset to clear all rules on the device, or flush the entire policy routing table; doing so can affect firewall4, VPNs, and other network services.
Maintenance checklist before expanding to the whole network
- Save the main router’s original DHCP gateway and DNS settings, and note the lease renewal interval. After restoring the options, clients with existing leases may continue using the bypass router.
- Keep a management path that does not depend on the bypass router, such as a maintenance computer with a static address and the main router’s management address.
- Update subscriptions in this order: download to a temporary file → test the configuration → replace it → restart. Keep the previous working configuration if the update fails.
- Monitor disk space and log volume. Leaving debug logging on for too long can exhaust the device’s limited flash storage.
- Practice stopping the core and powering off the bypass router, then verify the client recovery steps. Do not test only a normal startup.
A stable deployment is not defined by a service showing “running.” Verify the gateway, DNS, transparent interception, rule-based egress, and outage recovery separately. Complete the end-to-end test with one client first, then expand gradually to limit the impact of each change.