Run mihomo on an OpenWrt Router: Transparent Proxy Setup Guide

A practical guide to running mihomo on an OpenWrt gateway or secondary router, covering binary placement, configuration, transparent proxy routing, and LAN gateway settings.

Understand the Setup: The Core, Gateway, and Transparent Proxy Are Separate

Running mihomo directly on OpenWrt does not install a graphical Clash client. The core reads the configuration, establishes proxy connections, and applies rules; the operating system routes device traffic to the core. A clean startup log confirms only that the process is running—it does not prove that LAN devices are using the proxy.

This guide uses the firewall4 and nftables setup common to OpenWrt 23.05 and 24.10, with Linux TUN and automatic routing. Check your mihomo version’s configuration validation to confirm which fields it supports. Do not apply these traffic interception rules unchanged to older firewall3 systems, vendor-customized firewalls, or setups with existing proxy plugins.

  • Main router setup: OpenWrt is already the default gateway for client devices, so focus on traffic interception, firewall permissions, and DNS forwarding.
  • Secondary router setup: OpenWrt shares a LAN with the main router, so you also need to change the test device’s gateway and DNS settings.
  • Explicit proxy test: Connect an application directly to the 7890 mixed port to verify nodes and rules first. This does not mean transparent proxying is set up.

Plan the Secondary Router Address and Client Gateway

Test with one device first; don’t immediately change the DHCP settings for the entire LAN. The addresses below are a complete example. For an actual deployment, adjust them consistently to match your subnet—don’t change just one address.

Device or settingExample valuePurpose
LAN subnet192.168.10.0/24Main router, secondary router, and client share the same subnet
Main router192.168.10.1Connects to the internet and continues providing DHCP
OpenWrt secondary router192.168.10.2Static address; keep it outside the DHCP pool
Secondary router default gateway192.168.10.1Upstream route for the core’s node connections and direct traffic
Test device192.168.10.100Keep the existing subnet mask
Client gateway and DNS192.168.10.2Send the test device’s traffic and DNS queries to the secondary router

In LuCI, first check the static address, IPv4 gateway, and DNS settings under “Network” → “Interfaces” → “LAN” → “Edit.” A single-interface secondary router usually reaches the main router through br-lan. A dual-interface main router may use PPPoE or another WAN device for its outbound connection, so don’t guess the egress interface from its name.

ip -4 addr show
ip -4 route show
ip route get 223.5.5.5
cat /etc/resolv.conf

The secondary router’s default route should point to the main router, not back to itself. For the initial setup, give the secondary router a system DNS server it can reach directly. This avoids a startup loop where the core needs to resolve a node hostname before it can start, but DNS itself depends on the core.

Keep a single DHCP server on the main router. Don’t let the secondary router hand out a second set of addresses on the same broadcast domain. Plan IPv6 separately: even after manually changing a client’s IPv4 gateway, it may still use IPv6 route advertisements from the main router to connect directly.

Place the mihomo Binary and Configuration Files

Check the Architecture, Kernel Modules, and Storage

Check the CPU architecture and firmware details before choosing a Linux binary. x86_64, aarch64, and different MIPS variants are not interchangeable; some releases also distinguish instruction set levels. Similar-looking filenames don’t guarantee a binary will run on your device.

uname -m
ubus call system board
df -h /overlay /tmp
opkg list-installed | grep -E 'kmod-tun|firewall4|nftables'

On official firmware that uses opkg, install the TUN module with opkg update and opkg install kmod-tun. The module must match the firmware’s current kernel. If dependencies don’t match, fix the firmware and package feed configuration instead of forcing a module built for another kernel version. The nftables features needed for automatic traffic interception also depend on the firmware build options.

Set a Fixed Binary Path and Working Directory

Upload the extracted, architecture-matched binary to /usr/bin/mihomo and place the configuration at /etc/mihomo/config.yaml. The configuration may contain subscription credentials, so restrict access to the directory and file. If the device lacks enough persistent storage, plan for external storage rather than keeping the production configuration in /tmp, which is cleared on reboot.

mkdir -p /etc/mihomo
chmod 700 /etc/mihomo
chmod 755 /usr/bin/mihomo
chmod 600 /etc/mihomo/config.yaml
/usr/bin/mihomo -v
/usr/bin/mihomo -t -d /etc/mihomo -f /etc/mihomo/config.yaml

Use -d to set the working directory, -f to specify the configuration file, and -t to validate the configuration only. If the configuration references GeoIP, GeoSite, rule-set, or proxy-provider files, also check their paths, read permissions, and update connectivity. For initial troubleshooting, remove these external dependencies so a download failure isn’t mistaken for a TUN issue.

Test the Explicit Proxy Before Enabling TUN

Start in two stages. First, use a working node configuration with tun.enable set to false, then test the connection from the router itself through the mixed port. After confirming the node works, enable TUN and automatic routing. This separates node issues from traffic forwarding issues.

The minimal configuration below is for validating the configuration and direct-connect baseline. It has no proxy nodes, and its only policy group is DIRECT, so it won’t provide a proxy exit. For production use, merge in valid nodes from your subscription or proxy-providers and add the nodes to the policy group. Don’t overwrite an existing complete configuration with this example.

mixed-port: 7890
allow-lan: false
bind-address: 127.0.0.1
mode: rule
log-level: info
ipv6: false

tun:
  enable: false
  device: mihomo
  stack: mixed
  auto-route: true
  auto-redirect: true
  auto-detect-interface: true
  dns-hijack:
    - 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
  nameserver:
    - 223.5.5.5
    - 119.29.29.29

proxies: []

proxy-groups:
  - name: Proxy Selection
    type: select
    proxies:
      - DIRECT

rules:
  - IP-CIDR,192.168.10.0/24,DIRECT,no-resolve
  - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
  - MATCH,Proxy Selection

Run /usr/bin/mihomo -d /etc/mihomo -f /etc/mihomo/config.yaml in the foreground and check the port from a separate SSH session. If curl is installed on the firmware, run the request below. This command tests only the connection from the router to the mixed port; it does not test transparent forwarding for LAN devices.

curl --proxy http://127.0.0.1:7890 --connect-timeout 10 --max-time 20 -I https://example.com/

After importing real nodes, confirm that “Proxy Selection” no longer stays on DIRECT. Use the matching rule, policy group, and node names in the logs to trace the request. A page loading successfully alone doesn’t tell you whether it connected directly or through a proxy.

Connect TUN, Policy Routing, and the OpenWrt Firewall

Once the explicit proxy works, set tun.enable to true, validate the configuration again, and start mihomo. auto-route creates the routes needed to intercept traffic, while auto-redirect uses mechanisms such as nftables to redirect traffic on Linux. Automatically generated route table IDs, rule priorities, and chain names may vary by version; don’t treat a fixed set of values found online as a universal standard.

ip link show mihomo
ip rule show
ip route show table all
nft list ruleset
sysctl net.ipv4.ip_forward

Check Three System Requirements

  1. TUN device is available: You should see the mihomo device specified in the configuration. If it’s missing, check /dev/net/tun, the module, and startup permissions first.
  2. Traffic routing is active: Compare ip rule, the routing tables, and nftables rules before and after starting mihomo. The core logs should not repeatedly report failures to create an interface or add a rule.
  3. The firewall allows forwarding: IPv4 forwarding must be enabled, and the LAN-to-TUN forwarding path must be allowed. Seeing a TUN interface does not prove that firewall4 allows forwarding.

In LuCI, check zones and forwarding under “Network” → “Firewall” → “General Settings.” If you’re using a dedicated TUN zone, associate the mihomo device with that zone as appropriate for your setup, then allow forwarding from LAN to that zone. Zone input permissions and forwarding permissions are separate. Don’t set WAN input to “accept” just to test.

Firmware versions differ in how they bind unmanaged devices to zones and integrate automatic redirection with firewall4. Check your current ruleset rather than copying a fixed iptables script. In particular, don’t add REDIRECT, TPROXY, and automatic TUN interception rules at the same time. This guide uses the TUN approach.

With a single-interface secondary router, also check forwarding over the same interface, the return path, and connection tracking. Some direct traffic may enter through br-lan and leave for the main router through that same interface. If traffic works in only one direction or connections drop intermittently, capture packets to verify the source address and return path before considering targeted source NAT. Don’t blindly enable masquerading for the entire LAN.

For initial troubleshooting, temporarily disable software and hardware flow offloading under “Network” → “Firewall.” Once the connection is stable, re-enable them one at a time and retest. If the secondary router can access the internet but client devices can’t, check the forwarding path first instead of changing nodes.

Configure DNS Forwarding to Prevent Loops and IPv6 Bypasses

The DNS path in this example is: the client sends a query to port 53 on the secondary router; dnsmasq forwards it to the local mihomo listener on port 1053; mihomo then queries an upstream resolver. Using separate ports prevents dnsmasq and mihomo from competing for the same listening address.

In LuCI, set the DNS forwarding target to 127.0.0.1#1053 under “Network” → “DHCP/DNS” → “General Settings.” In the corresponding advanced settings, check the option labeled “Ignore resolv file.” The exact labels vary between LuCI versions; the key is to stop dnsmasq from also sending queries to other automatically assigned upstream servers.

# Back up first; the commands below target the first dnsmasq instance
cp /etc/config/dhcp /etc/config/dhcp.before-mihomo
uci set dhcp.@dnsmasq[0].noresolv='1'
uci -q delete dhcp.@dnsmasq[0].server
uci add_list dhcp.@dnsmasq[0].server='127.0.0.1#1053'
uci commit dhcp
/etc/init.d/dnsmasq restart

nslookup example.com 127.0.0.1

These commands replace the instance’s existing DNS forwarding list. If it has conditional forwarding for corporate intranet domains, record and preserve those entries first. With multiple dnsmasq instances, don’t assume the first one serves the LAN. Also confirm that the client’s DNS address is actually 192.168.10.2.

Understand Standard DNS, fake-ip, and Encrypted DNS

  • fake-ip: In this mode, queries may return addresses from 198.18.0.0/16. These are virtual mappings, not the destination websites’ real public IP addresses. The corresponding connections must still be handled by the core.
  • Standard DNS interception: any:53 intercepts the relevant standard DNS requests. It does not identify and redirect every DNS protocol.
  • DoH and DoT: Browser Secure DNS, in-app DoH, and DoT over TCP port 853 won’t automatically use dnsmasq just because standard port 53 is intercepted. Confirm the application’s DNS path when testing.
  • Upstream resolvers: This example uses a plain IP address for the upstream resolver to reduce startup dependencies; it does not enable encrypted DNS. If you switch to a DoH URL with a hostname, plan separately for bootstrap DNS and node hostname resolution.

ipv6: false only limits mihomo’s corresponding behavior; it does not disable the IPv6 default route advertised to client devices. For initial testing, you can temporarily disable IPv6 on the test device, then plan IPv6 TUN, route advertisements, and firewall rules. Don’t assume that correct IPv4 egress means both IPv4 and IPv6 traffic are being intercepted.

Manage Startup and Restarts with procd

Register the startup service only after foreground operation, DNS, and client access all work. The procd script below supervises the process; it does not handle subscription updates or custom firewall rules. Save it as /etc/init.d/mihomo, and don’t leave another plugin’s service with the same name enabled.

#!/bin/sh /etc/rc.common

START=99
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

Before starting the service, stop the core running in the foreground to prevent two instances from competing for ports and the TUN device. This script runs as root by default because creating a TUN device and configuring routes and firewall rules require elevated permissions. That also means you must secure the configuration, subscription sources, and binary.

After changing the configuration, run mihomo -t first, then execute /etc/init.d/mihomo restart. The file declaration in the script does not provide automatic subscription updates; arrange downloads, configuration validation, and atomic replacement separately. If the configuration contains credentials, don’t publish complete startup logs in a public channel.

Starting the service later in the boot sequence doesn’t guarantee the network is ready. Test with a full system reboot and confirm that, once the interfaces come up, mihomo can resolve node hostnames, establish connections, and restore client access. If the setup relies on remote rule sets, account for the possibility that those files won’t be reachable during a cold start.

Verify Each Link and Keep a Rollback Plan

Verify the setup on the router first, then on one client, and only then expand it to DHCP settings. Change one variable at a time and record the corresponding logs and gateway settings. A speed test only shows performance at that moment; it doesn’t replace checks of the routing and DNS paths.

SymptomCheck firstHow to confirm
Core won’t startArchitecture, configuration, ports, TUN moduleCheck configuration validation and startup errors; don’t change the client gateway yet
Explicit proxy works, but client traffic failsClient gateway, TUN interception, LAN forwardingCheck whether the client’s requests appear in the core logs
IP addresses are reachable, but domain names faildnsmasq, port 1053 listener, upstream DNSTest DNS queries from the secondary router and client separately
Some apps still connect directlyRules, IPv6, in-app DNSCheck the actual matched policy and IPv4/IPv6 egress
Connections fail after the firewall reloadsCheck whether automatically generated rules are still presentCompare nftables and policy routing before and after the reload
Setup fails to recover after rebootService enabled state, interface startup order, cold-start dependenciesUse system logs to find the first failure

Test with one client before expanding the deployment

  1. First, confirm that the client can access the main and secondary router management addresses over the LAN.
  2. Check that the client’s IPv4 default gateway and DNS both point to the secondary router. Clear the old DNS cache, then open a new browser connection.
  3. Test requests that should go direct and those that should use the proxy. Check the matching rules and nodes rather than just whether a page loads.
  4. Check UDP apps, IPv6 behavior, and connectivity after a firewall reload. Only then change the gateway and DNS values distributed by the main DHCP server.

After stopping the service cleanly, check whether the TUN interface, policy routes, and automatically generated firewall rules have been removed. If anything remains, restore the saved baseline. Don’t remotely run a command that flushes the entire nftables ruleset. If you need to clear runtime state, schedule a reboot after restoring the persistent configuration.

The key to an OpenWrt secondary router setup isn’t simply placing the binary on the router. It’s connecting the client gateway, DNS, traffic interception, rule-based egress, and service lifecycle into a verifiable path. Follow this order so that when you later change subscriptions, adjust routing rules, or upgrade the core, you can pinpoint which layer caused a problem.

Download Clash Downloads and system requirements