The three routes map onto three kinds of machine. A desktop you click around on every day wants the graphical app. A VPS in a datacentre has no use for a desktop, so the bare core is enough. And if it should come up at boot and restart itself after a crash, you put a systemd unit around that bare core — the last two are really two halves of the same route: get it running by hand first, then hand it to the system.
Route one: the graphical app
- Pick the package format on the project's release page:
debfor Debian and Ubuntu,rpmfor Fedora and openSUSE,AppImagefor everything else. Every architecture sits side by side under the same version, so do not grab the arm64 build for an x86 box. - Install a deb with
sudo apt install ./filenameand an rpm withsudo dnf install ./filename. The leading./is not optional; without it the package manager goes looking in the repositories for something by that name. dpkg and rpm work directly too, but neither resolves dependencies for you. - For the AppImage, make it executable first —
chmod +x— then double-click it or run it from a terminal. It writes nothing into system directories, so deleting the file is the uninstall. - Start it from a terminal the first time, not from the applications menu. When startup fails the window flashes and disappears, and the terminal is the only place the error survives.
The usual failure is a missing runtime library. An AppImage bundles the application's own code and borrows GTK, WebKit and friends from the system, so when one is absent the terminal names the specific .so file. Search your package manager for that filename and you will normally land on the right package; the naming is not consistent across distributions, with Debian using something containing webkit2gtk and Fedora and Arch each spelling it differently. Tray icons are a separate genre of problem. GNOME does not draw a tray area by default and needs the AppIndicator extension before anything shows up, and Wayland sessions lose icons more often than X11 ones — if the extension is installed and the icon still is not there, log into an X11 session once to confirm that is what you are fighting.
Route two: the bare mihomo core
uname -m
chmod +x ./mihomo
sudo install -m 755 ./mihomo /usr/local/bin/mihomo
mkdir -p ~/.config/mihomo
mihomo -d ~/.config/mihomo
If uname -m answers x86_64, take the amd64 build; if it answers aarch64, take arm64. Get this wrong and the shell tells you it cannot execute the binary, which has nothing to do with your config. Release pages usually publish a compressed single file whose extracted name carries the architecture and version, so rename it to mihomo to keep the rest short. The -d flag points at the config directory, which needs at least a config.yaml inside it. Do not background it on the first run — let it print to the terminal so you can watch whether the subscription was read and whether the port actually came up. If you want something to manage it with, enable external-controller and attach a web dashboard, which is a topic of its own.
Route three: keep it running under systemd
[Unit]
Description=mihomo daemon
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/mihomo -d /etc/mihomo
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Both paths in ExecStart have to match where you actually put things — the binary first, the config directory second — and copying them verbatim will most likely fail to start. User is root here so that TUN and low-numbered ports work; if you are not using TUN, an ordinary user is the safer choice. Restart=on-failure only revives it after an abnormal exit, so switch to always if you want it up no matter what. Where the unit file belongs differs slightly between distributions, and sudo systemctl edit --force --full mihomo.service lets systemd create the file for you so you do not have to remember. Then sudo systemctl daemon-reload and sudo systemctl enable --now mihomo. After that, systemctl status mihomo tells you whether it survived and journalctl -u mihomo -f follows the log, where the core's own lines say whether the config failed to parse or the port was taken.
TUN needs one extra step on Linux
The same TUN switch that costs one click and a service install on Windows behaves differently here. Creating a virtual adapter and rewriting the routing table are network-administration privileges, and a process running as an ordinary user does not have them. The symptom is consistent: the switch flips itself back off, or the TUN lines in the log say plainly that permissions were insufficient or the device could not be opened. Two ways out — a client installed from deb or rpm normally ships a service mode, so turn that on and let the privileged half create the adapter, or skip the service and grant the capability to the binary directly:
sudo setcap cap_net_admin,cap_net_bind_service=+ep $(which mihomo)
getcap $(which mihomo)
A few smaller things that will trip you
- setcap on an AppImage is mostly wasted effort. The AppImage runtime mounts its contents into a temporary directory and executes the program from there, so a capability attached to the outer file never reaches the process that actually runs. If you need TUN, use the deb or rpm build with service mode and stop burning time here.
- Replacing the binary drops the capability. setcap marks the file, so run it again after every core upgrade or file swap, and use
getcapto confirm it is still set. - Programs in a terminal ignore the system proxy settings entirely. curl, git and package managers read environment variables such as
https_proxy;export https_proxy=http://127.0.0.1:7890covers you for one shell, and making it stick is a separate topic. - Verify the hash before you run anything. Release pages generally publish checksum files alongside the builds, and there is a dedicated article on how to compare them.
sudo systemctl disable --now mihomo.