Choose the deployment model first: desktop client or standalone core
Clash deployment on Linux usually follows one of two paths. Desktop environments can use a GUI client with configuration management, proxy-group switching, and log viewing. Servers, development machines, and always-on mini PCs are often better served by running the Clash Meta (mihomo) core directly and managing it with systemd. Both approaches read YAML configuration, but they differ in startup method, permission scope, and how they connect to the system proxy.
| Use case | Recommended approach | Key benefits | What to watch |
|---|---|---|---|
| GNOME or KDE Plasma desktop | GUI client | Import subscriptions, switch nodes, and view logs more easily | Desktop proxy settings, tray support, and core permissions |
| Headless cloud server | mihomo + systemd | Clear resource usage and automatic restarts | Configuration path, service user, and bind address |
| Development workstation | GUI or standalone core | Configure separate proxies for Git, package managers, and containers | Environment-variable scope and DNS behavior |
| Gateway or transparent-proxy host | mihomo TUN or transparent proxy | Can handle traffic from more applications | Routing tables, nftables, DNS, and network permissions |
If the goal is simply to route browser traffic, Git, and terminal commands through a proxy, start with an HTTP, SOCKS, or mixed port. TUN mode creates a virtual network interface and changes routing, providing broader coverage but also introducing CAP_NET_ADMIN, DNS interception, and firewall compatibility concerns. Verify that a regular proxy port is stable before enabling TUN; troubleshooting will be much simpler.
Install the mihomo binary and plan the directory layout
Check the processor architecture before installing. The common x86_64 architecture corresponds to amd64, while Raspberry Pi 4 systems, some cloud hosts, and newer development boards commonly use arm64. Do not infer architecture from the distribution name; reading the system result directly is more reliable.
uname -m
getconf LONG_BIT
cat /etc/os-release
When uname -m returns x86_64, choose the amd64 build; when it returns aarch64, choose the arm64 build. Download and extract the matching file, then install the actual binary at a fixed path. The example below assumes the extracted filename is mihomo-linux-amd64:
sudo install -m 0755 ./mihomo-linux-amd64 /usr/local/bin/mihomo
/usr/local/bin/mihomo -v
Keep the program, read-only configuration, and runtime data separate. Put the binary in /usr/local/bin, the main configuration in /etc/mihomo, and cache files, Geo data, proxy providers, and runtime state in /var/lib/mihomo. This makes systemd write restrictions easier to manage and prevents binary updates from overwriting configuration.
sudo useradd --system \
--home-dir /var/lib/mihomo \
--shell /usr/sbin/nologin mihomo
sudo install -d -m 0750 -o root -g mihomo /etc/mihomo
sudo install -d -m 0750 -o mihomo -g mihomo /var/lib/mihomo
sudo install -d -m 0750 -o mihomo -g mihomo /var/lib/mihomo/providers
sudo install -m 0640 -o root -g mihomo ./config.yaml /etc/mihomo/config.yaml
Prepare a testable baseline configuration first
A practical local configuration can listen on mixed port 7890 and bind the external control interface to the loopback address. A mixed port accepts both HTTP and SOCKS connections, making it suitable for terminal tools, browsers, and desktop proxy settings.
mixed-port: 7890
allow-lan: false
bind-address: 127.0.0.1
mode: rule
log-level: info
ipv6: false
external-controller: 127.0.0.1:9090
secret: "linux-local-controller-2026"
profile:
store-selected: true
store-fake-ip: true
Proxy nodes, proxy groups, and rules are not listed here because they should come from a working configuration or subscription. After importing them, confirm that the final rules include a fallback policy such as MATCH,PROXY, or a rule that references the actual proxy-group name. Proxy-group names are case-sensitive; referencing a group that does not exist will make the configuration test fail.
sudo -u mihomo /usr/local/bin/mihomo \
-t \
-f /etc/mihomo/config.yaml \
-d /var/lib/mihomo
A successful test reports that the configuration loaded correctly. For YAML parsing errors, first check indentation, spaces after colons, and proxy-group names. YAML does not allow Tabs as a substitute for indentation. Do not create an automatic restart loop until the configuration test passes, or the logs may be buried under repeated startup messages.
Write the systemd service and keep it running at boot
The systemd unit should define the service user, working directory, restart policy, and writable paths explicitly. A standard HTTP or SOCKS proxy does not require root privileges or network-management capabilities. The unit below is intended for a basic deployment without TUN.
[Unit]
Description=mihomo proxy service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=mihomo
Group=mihomo
WorkingDirectory=/var/lib/mihomo
ExecStartPre=/usr/local/bin/mihomo -t -f /etc/mihomo/config.yaml -d /var/lib/mihomo
ExecStart=/usr/local/bin/mihomo -f /etc/mihomo/config.yaml -d /var/lib/mihomo
Restart=on-failure
RestartSec=5s
LimitNOFILE=1048576
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/mihomo
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
[Install]
WantedBy=multi-user.target
Save the content as /etc/systemd/system/mihomo.service, then reload the unit and start it. enable --now enables the service at boot and starts it immediately, so there is no need to run start separately.
sudo systemctl daemon-reload
sudo systemctl enable --now mihomo
systemctl status mihomo --no-pager
journalctl -u mihomo -n 80 --no-pager
Check the proxy ports and control interface
An active service only confirms that the process is still running; also verify the bind addresses for ports 7890 and 9090. The expected result is 127.0.0.1:7890 and 127.0.0.1:9090, not 0.0.0.0, which exposes the ports on every network interface.
ss -lntp | grep -E '7890|9090'
curl --max-time 5 \
--proxy http://127.0.0.1:7890 \
-I https://www.gstatic.com/generate_204
When the connection is healthy, a test request usually returns 204 or an HTTP status that can be explained by the proxy chain within 0.4 to 2 seconds. If it times out after 5 seconds, first check the mihomo logs to see whether a proxy group was selected, then inspect node connection errors. An immediate Connection refused usually indicates a local listening-port problem, not a remote proxy issue.
Terminal proxy variables: current sessions, persistent settings, and one-off commands
Linux terminal programs do not automatically use a proxy just because mihomo is running. Whether curl, wget, Git, or a language package manager reads the desktop system proxy depends on the individual implementation. The most portable approach is to set HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY in the current Shell.
export HTTP_PROXY=http://127.0.0.1:7890
export HTTPS_PROXY=http://127.0.0.1:7890
export ALL_PROXY=socks5h://127.0.0.1:7890
export NO_PROXY=localhost,127.0.0.1,::1,.local
export http_proxy="$HTTP_PROXY"
export https_proxy="$HTTPS_PROXY"
export all_proxy="$ALL_PROXY"
export no_proxy="$NO_PROXY"
The h in socks5h means that hostname resolution is delegated to the SOCKS proxy, which can avoid mismatches between local DNS resolution and the proxy path. Set both uppercase and lowercase variables because tools do not use a consistent variable name. Variables apply only to the current Shell and its child processes; already-open terminal tabs will not receive later changes automatically.
Choose the syntax based on scope
- Proxy one command only:
HTTPS_PROXY=http://127.0.0.1:7890 curl https://example.com. - Proxy the current terminal only: run export directly; the setting ends when the session closes.
- Persist for Bash: add it to
~/.bashrc, then runsource ~/.bashrc. - Persist for Zsh: add it to
~/.zshrc, then reopen the terminal or runsource ~/.zshrc. - Configure Git separately: use
git config --global http.proxy http://127.0.0.1:7890.
To remove the proxy from the current session, delete both uppercase and lowercase variables so that no tool continues using a leftover value.
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY
unset http_proxy https_proxy all_proxy no_proxy
git config --global --unset http.proxy
git config --global --unset https.proxy
sudo may filter proxy environment variables by default, and systemd services do not inherit an interactive Shell's ~/.bashrc. If a background service genuinely needs a proxy, create a dedicated drop-in and set Environment= under [Service] instead of changing the global system environment. This keeps the proxy limited to the intended service.
Linux desktop clients and system-proxy integration
A GUI client suits desktop users who frequently import subscriptions, switch proxy groups, and inspect connection logs. When choosing one, verify that it offers a Linux build, can locate the core binary, clearly defines configuration storage, and supports the current desktop environment. Common package formats include AppImage, Deb, RPM, and archives.
Key considerations for each package format
- AppImage: run
chmod +xfirst, then launch it as a regular desktop user. Some minimal systems require a FUSE compatibility package. - Deb: suitable for Debian, Ubuntu, and derivatives; install it and resolve dependencies with
sudo apt install ./client-file.deb. - RPM: suitable for Fedora, Rocky Linux, openSUSE, and similar systems; use dnf or zypper as appropriate.
- Archive: you must maintain the desktop entry, update path, and executable permissions yourself; this suits environments that need a pinned version.
Desktop clients commonly provide switches such as “System Proxy,” “TUN Mode,” and “Start at Login.” If you only need browsers and applications that follow desktop proxy settings, enable System Proxy. In GNOME, the setting is under “Settings” → “Network” → “Network Proxy”; in KDE Plasma 6, it is usually under “System Settings” → “Network” → “Proxy.” For manual configuration, enter 127.0.0.1 as the HTTP, HTTPS, and SOCKS host and use the mixed port shown by the client, such as 7890.
Do not start the GUI client's core and the systemd core described above at the same time with both listening on 7890. On a port conflict, the later instance will usually report address already in use. If both environments must remain available, assign different ports such as 17890 and 17891 to one of them, and check their external control ports separately.
Permissions and DNS settings when enabling TUN mode
TUN mode can handle applications that do not read HTTP proxy variables, including some game launchers, closed desktop applications, and software using a custom network stack. mihomo creates a virtual network device and changes routing, so it needs at least CAP_NET_ADMIN. Some network operations may also use CAP_NET_RAW. Grant the service only the capabilities it needs; there is no need to run the entire process as root.
Create an override configuration with sudo systemctl edit mihomo:
[Service]
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW
PrivateDevices=false
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
After saving, run the following commands to apply the override:
sudo systemctl daemon-reload
sudo systemctl restart mihomo
systemctl show mihomo -p AmbientCapabilities
ip address show
ip route show table all
Key TUN configuration fields
tun:
enable: true
stack: mixed
auto-route: true
auto-redirect: true
strict-route: true
dns-hijack:
- any:53
auto-route automatically adds routes, auto-redirect can use nftables on supported Linux systems to improve traffic interception, and strict-route helps reduce traffic bypasses. Whether these fields suit the system depends on NetworkManager, systemd-networkd, Docker, and existing firewall rules. Container networks, corporate VPNs, and TUN can all add policy routes; when they conflict, compare ip rule and ip route show table all before and after enabling them.
DNS is a key focus when troubleshooting TUN. If IP connections work but domain names do not, first run resolvectl status and resolvectl query example.com. If systemd-resolved already occupies port 53 locally, do not bind mihomo's regular DNS listener to the same address and port. You can use a high port such as 127.0.0.1:1053 and forward to it through explicit system configuration, or let TUN DNS hijacking handle port-53 requests arriving through the virtual interface.
Common troubleshooting: from process and ports to rule results
The service keeps restarting
Read the logs for the current startup cycle first instead of restarting repeatedly:
systemctl status mihomo --no-pager
journalctl -u mihomo -b -n 120 --no-pager
sudo -u mihomo /usr/local/bin/mihomo \
-t \
-f /etc/mihomo/config.yaml \
-d /var/lib/mihomo
Common causes include YAML indentation errors, references to missing proxy groups, insufficient configuration-file permissions, an unwritable Geo data directory, or a listening port already occupied by another client.
7890 is listening, but the terminal still connects directly
Check the variables actually received by the current process instead of only inspecting configuration files:
env | grep -i proxy
curl --max-time 5 -I https://example.com
curl --max-time 5 --proxy http://127.0.0.1:7890 -I https://example.com
If the second command fails while the third succeeds, mihomo is working and the problem lies in the terminal proxy variables or the application's own settings. If both fail, inspect the logs for DNS, handshake, or node timeout messages.
The subscription updated, but the node list did not change
Proxy-provider files should be stored in a directory writable by the mihomo user, such as /var/lib/mihomo/providers. Check the file modification time, the provider health-check result, and the subscription response. systemd's ProtectSystem=strict prevents the service from writing to directories not listed in ReadWritePaths, so do not place dynamic provider files in the read-only /etc/mihomo.
The LAN or containers lose connectivity after enabling TUN
Disable TUN first and confirm that the mixed port still works, then compare routes and rules before and after enabling TUN. LAN ranges usually need direct-connection rules such as 192.168.0.0/16, 10.0.0.0/8, and 172.16.0.0/12. Docker commonly uses 172.17.0.0/16, but the actual range should be taken from docker network inspect; never assume it is identical on every host.
Updates and rollback: keep configuration and program files independent
Before updating the core, record the current version and keep the working binary. Because the configuration and program are separated, rollback only requires restoring the old binary and restarting the service; there is no need to overwrite /etc/mihomo/config.yaml or provider data.
/usr/local/bin/mihomo -v
sudo cp /usr/local/bin/mihomo /usr/local/bin/mihomo.previous
sudo systemctl stop mihomo
sudo install -m 0755 ./mihomo-linux-amd64 /usr/local/bin/mihomo
sudo -u mihomo /usr/local/bin/mihomo \
-t \
-f /etc/mihomo/config.yaml \
-d /var/lib/mihomo
sudo systemctl start mihomo
If the new version will not start, run sudo install -m 0755 /usr/local/bin/mihomo.previous /usr/local/bin/mihomo and restart the service. After an upgrade, also check the logs, ports, proxy-group selection, and one real network request. Seeing a changed version number is not a substitute for complete runtime verification.