Installing mihomo on a Raspberry Pi and Setting Up a Transparent Proxy

Image: Gansu Museum’s Bronze Galloping Horse (Horse Treading on the Swallow)

I’d been running OpenClash on my Xiaomi AX3000T router, and honestly it was stable enough. But after a while OpenClash got hungrier and hungrier for memory — one day I checked and it was up to 233MB. The router OOM’d outright, SSH wouldn’t even connect, and all I could do was cut the power and reboot. A router’s little bit of memory just can’t carry that load, so I set my sights on the Raspberry Pi that had been gathering dust for ages. Idle is idle, might as well put it to work.

Install Docker with the official one-liner script:

curl -fsSL https://get.docker.com | sh

Once that’s done, add your current user to the docker group so you don’t have to type sudo every time:

sudo usermod -aG docker $USER

Then log out and log back in. Docker Hub is a bit flaky from inside China, so I set up a registry mirror:

sudo tee /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": ["https://docker.1ms.run"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

Docker’s all set, next up is deploying mihomo. Make a directory to hold the config and data:

mkdir -p ~/mihomo && cd ~/mihomo

Get your config file ready at ~/mihomo/config.yaml first, then run it with Docker:

docker run -d   --name mihomo   --restart=always   --network=host   --cap-add=NET_ADMIN   -v ./config.yaml:/root/.config/mihomo/config.yaml   -v ./cache.db:/root/.config/mihomo/cache.db   metacubex/mihomo:latest

–network=host uses the host network so the ports are available directly, –cap-add=NET_ADMIN gives it permission to do iptables operations, –restart=always starts it on boot and restarts it automatically when it dies.

If your config came from OpenClash, you can’t just use it as-is! You have to strip out the OpenClash-only fields — things like experimental and dns.enhanced-mode: redir-host, those extension fields and the like. The key settings look roughly like this:

mixed-port: 7893
port: 7890
socks-port: 7891
redir-port: 7892
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
external-controller: 0.0.0.0:9090

dns:
  enable: true
  listen: 0.0.0.0:53
  enhanced-mode: fake-ip
  nameserver:
    - 223.5.5.5
    - 119.29.29.29

Or you can use a subscription:

proxy-providers:
  my-provider:
    type: http
    url: "你的订阅链接"
    interval: 3600
    path: ./providers/my-provider.yaml
    health-check:
      enable: true
      interval: 600
      url: http://www.gstatic.com/generate_204

Port layout: 7890 is the HTTP proxy, 7891 is SOCKS5, 7892 is the REDIRECT transparent proxy for iptables, 7893 is the mixed proxy, 53 is DNS, and 9090 is the API plus the web panel. After you’ve edited the config, run docker restart mihomo to reload it.

Now for the most critical part, the iptables transparent proxy. The idea is to hijack LAN traffic with iptables and hand it over to mihomo’s port 7892. Create a script at ~/transparent-proxy.sh:

#!/bin/bash

MIHOMO_REDIR_PORT=7892
LOCAL_SUBNET="192.168.0.0/16"

iptables -t nat -N MIHOMO

iptables -t nat -A MIHOMO -d 0.0.0.0/8 -j RETURN
iptables -t nat -A MIHOMO -d 10.0.0.0/8 -j RETURN
iptables -t nat -A MIHOMO -d 127.0.0.0/8 -j RETURN
iptables -t nat -A MIHOMO -d 169.254.0.0/16 -j RETURN
iptables -t nat -A MIHOMO -d 172.16.0.0/12 -j RETURN
iptables -t nat -A MIHOMO -d 192.168.0.0/16 -j RETURN
iptables -t nat -A MIHOMO -d 224.0.0.0/4 -j RETURN
iptables -t nat -A MIHOMO -d 240.0.0.0/4 -j RETURN

iptables -t nat -A MIHOMO -m owner --uid-owner root -j RETURN

iptables -t nat -A MIHOMO -p tcp -j REDIRECT --to-ports $MIHOMO_REDIR_PORT

iptables -t nat -A PREROUTING -p tcp -s $LOCAL_SUBNET -j MIHOMO

echo "透明代理规则已设置"

The script to turn the transparent proxy off:

#!/bin/bash
LOCAL_SUBNET="192.168.0.0/16"
iptables -t nat -D PREROUTING -p tcp -s $LOCAL_SUBNET -j MIHOMO
iptables -t nat -F MIHOMO
iptables -t nat -X MIHOMO
echo "透明代理规则已清除"

chmod +x ~/transparent-proxy.sh ~/transparent-proxy-stop.sh

To get the iptables rules set up automatically at boot, create a systemd service:

sudo tee /etc/systemd/system/mihomo-proxy.service << 'EOF'
[Unit]
Description=mihomo transparent proxy iptables rules
After=network.target docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/home/sry/transparent-proxy.sh
ExecStop=/home/sry/transparent-proxy-stop.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable mihomo-proxy.service
sudo systemctl start mihomo-proxy.service

So that command-line tools go through the proxy too, set the environment variables in /etc/profile.d/proxy.sh:

sudo tee /etc/profile.d/proxy.sh << 'EOF'
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
export all_proxy=socks5://127.0.0.1:7891
export no_proxy=localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12
EOF

source /etc/profile.d/proxy.sh

Watch out! /etc/profile.d/proxy.sh only takes effect in login shells (bash -l)! systemd services, cron jobs and non-login shells will never source that file on their own. If one of your systemd services needs to use the proxy, you have to add an Environment= line to its unit file explicitly, otherwise the process connects directly and on the Chinese network it gets blocked by the GFW! I hit this trap myself and traced it for ages before realizing the environment variables were the problem.

Test whether the proxy actually works:

curl -x http://127.0.0.1:7890 https://www.google.com -I
curl --socks5 127.0.0.1:7891 https://www.google.com -I

If it comes back with a 200, the config is working. The web panel is at http://your-pi-ip:9090/ui, and I’d recommend Yacd or Metacubexd.

Last up, the biggest pit I fell into. At first I wanted TProxy mode instead of REDIRECT, since TProxy can carry UDP traffic too and sounds better on paper. I fiddled with it for half a day and TProxy just refused to work. Digging in, I found that the Raspberry Pi kernel drops packets carrying an fwmark instead of routing them properly — marked packets simply get discarded. A bit of searching turned up plenty of people with the exact same problem, and it’s basically confirmed to be the Pi kernel’s fault. So if you’re on a Raspberry Pi too, just stick with REDIRECT mode. It can only proxy TCP, but it’s stable and reliable.

All in all, mihomo running in Docker has been rock solid, and it uses far less memory than OpenClash did on the router. Every device on the LAN rides the transparent proxy automatically with no per-device setup needed, which is a joy to use.

References:

mihomo official docs

Docker official installation docs

metacubex/mihomo Docker image

How iptables REDIRECT transparent proxying works

 

Published on August 22, 2026.

Leave a Reply

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