Migrating mihomo from OpenWrt to a Raspberry Pi

Photo: Zhuoer Mountain, Qilian County

1. Background

My bypass router is a Xiaomi AX3000T flashed with OpenWrt, running OpenClash (built on the mihomo core). I’d been on this setup for over half a year and it was mostly fine, with one fatal flaw: the AX3000T only has 233MB of RAM.

With OpenClash running, the mihomo process alone eats tens of MB, and after the OpenWrt system’s own overhead there’s usually only a dozen-odd MB left. Add a few more subscription nodes or some heavier rules and the OOM Killer fires, mihomo gets killed, and the whole proxy drops. The annoying part is the network never comes back by itself — I had to log into the admin panel and restart the OpenClash service by hand. Sometimes it died overnight and I didn’t notice until the next morning, when nothing could reach the internet. So I decided to move mihomo off the router.

2. Choosing an approach

I happened to have a spare Raspberry Pi 4B (the 8GB version) sitting around, so memory was a non-issue. The plan: run mihomo in Docker on the Pi, and let the router stop doing proxy duty.

3. Backing up the AX3000T config

First step of the migration: pull all the router’s config off it first:

# 备份OpenWrt基础配置
scp -r root@你的路由器IP:/etc/config/ ./openwrt-config/

# 备份OpenClash的mihomo主配置
scp root@你的路由器IP:/etc/openclash/config/config_real.yaml ./mihomo-config/

# 备份规则集文件
scp -r root@你的路由器IP:/etc/openclash/rule_provider/ ./mihomo-config/rule_provider/

# 备份自定义规则
scp root@你的路由器IP:/etc/openclash/custom_rules/ ./mihomo-config/custom_rules/

4. Migrating the config

The config_real.yaml that OpenClash exports can’t be used as-is, it needs a few tweaks:

1. Delete the experimental field that only OpenClash uses (OpenClash adds it itself, mihomo doesn’t recognize it)

2. Change mixed-port, port and the other ports around so they don’t clash with other services on the Pi

3. Set allow-lan: true (other devices need to reach it over the LAN)

4. Change external-controller to 0.0.0.0:9090 for remote management

Drop the cleaned-up config and the rule_provider files into a directory on the Pi, say /home/pi/mihomo/

5. Pitfalls: the ImmortalWrt Docker route

My first idea was to run ImmortalWrt (a fork of OpenWrt) in Docker on the Pi, then run OpenClash inside ImmortalWrt. That would make the config migration the easy part, and it’d feel exactly like the router.

Sounds wonderful. In practice it’s a disaster.

Once the ImmortalWrt container starts, its br-lan bridge grabs the Pi’s eth0 NIC outright! Even worse, ImmortalWrt’s default IP is 192.168.x.1 (your router IP), while my Pi’s IP is your Pi IP, and the two addresses collide on the same subnet.

Result: the Pi went dark. The whole LAN routing table went sideways — not only was the Pi unreachable, other devices lost their network too. In the end I had to physically power off and restart the Pi, boot into recovery mode and delete that Docker container.

Blood and tears lesson: never run ImmortalWrt in Docker on the Pi as your proxy! The br-lan bridge seizes the NIC, and the IP conflict takes down the entire network!

6. The right fix: just run mihomo in Docker

After ditching the ImmortalWrt route, I went back to the simplest option — run mihomo directly with Docker:

docker run -d   --name mihomo   --network host   --cap-add NET_ADMIN   -v /home/pi/mihomo/config.yaml:/root/.config/mihomo/config.yaml   -v /home/pi/mihomo/rule_provider:/root/.config/mihomo/rule_provider   --restart unless-stopped   metacubex/mihomo:latest

That one command starts mihomo, running on the Pi’s port 9090. Any other device that needs the proxy just points its main router and dns at the Pi’s IP.

7. How it compares

ComparisonAX3000T + OpenClashRaspberry Pi 4B + Docker mihomo
Available memory~233MB (usually not enough)8GB (totally overkill)
OOM riskFires all the time, drops the networkBasically non-existent
Config managementHave to log into the OpenWrt admin panelJust edit the yaml + docker restart
Firmware update riskFlashing can brick itTotally independent of the router
Network outage riskOOM kills the process, network diesAlmost never
Extra hardware costNoneNeed a Raspberry Pi
Extra power drawNoneAbout 5W (Raspberry Pi 4B idle)
Maintenance effortMediumLow (Docker container management is very convenient)

8. Wrap-up

How do you tell it’s OOM? SSH into the router and run dmesg | grep -i oom. If you see a flood of “Out of memory: Killed process” logs, that’s it. The AX3000T only has 233MB, the clash_meta process running OpenClash regularly eats 100MB+, and with the system on top there’s basically not enough memory to go around.

The whole lesson in one line: don’t run your proxy in an ImmortalWrt Docker container, just use mihomo’s own Docker image. ImmortalWrt’s br-lan bridge grabs the host’s NIC, causes an IP conflict, and crashes the entire network.

The Raspberry Pi 4B + Docker + mihomo combo solves the root cause — the router not having enough memory — and it’s far easier to manage than OpenClash. The config is a single yaml file: edit it, docker restart, done. No more clicking around OpenWrt’s cluttered web interface.

If your router keeps crashing your proxy because it’s low on memory, I’d seriously give this setup a try!

References:

mihomo project page

mihomo Docker image

OpenClash project page

Docker’s official install script

Published on August 23, 2026.

Leave a Reply

Your email address will not be published. Required fields are marked *