Linux Setup mihomo Core: CLI, systemd Service, and Desktop Clients

Compare two ways to run mihomo on Linux: as a systemd service or in a graphical client. Covers config paths, ports, and proxy environment variables.

Choose a deployment: background service or desktop client

mihomo is a proxy core descended from Clash Meta. It reads configuration, establishes proxy connections, and applies traffic-routing rules. The core has no desktop interface, and starting its process does not automatically route all Linux applications through a proxy. Applications must use a local proxy port, or have their traffic captured by a configured TUN interface.

A binary managed by systemd is a good fit for servers, headless devices, and machines that need to stay online in the background. For everyday desktop use, switching nodes, or updating subscriptions, consider a graphical client such as Clash Verge Rev or FlClashs. You can test both approaches separately, but don’t let two cores listen on the same port at once.

CategoryBinary + systemdDesktop client
Best forServers, remote terminals, and persistent background operationLinux desktops with a graphical session
Configuration managementMaintain YAML files and updates yourselfImport, update, and switch configurations from a list
How it startsRuns as a system service, independently of user loginStarts with the app or automatically at desktop login
PermissionsA dedicated unprivileged user can run a standard proxy portTUN typically requires a client service or elevated permissions

Prepare the binary: architecture, permissions, and working directory

Check the system architecture and init system

uname -m
ps -p 1 -o comm=
systemctl --version

x86_64 usually means an amd64 build, while aarch64 means arm64. For 32-bit ARM, check the build requirements rather than choosing a file based only on the device brand. The second command should show systemd. Containers, WSL, and systems with a different init system may require another startup method.

Get the mihomo binary for your architecture from a trusted project release source. Extract it, name the file mihomo, and place it in the current directory. You can’t run the archive itself. If you see Exec format error, check the architecture and make sure the file is extracted before repeatedly changing its executable permissions.

chmod +x ./mihomo
./mihomo -v
sudo install -m 0755 ./mihomo /usr/local/bin/mihomo

Use -v to record the exact core version. When checking whether a configuration field is supported, refer to this output—not the desktop client’s version number.

Keep the binary, configuration, and runtime data separate

This setup uses three fixed locations: the binary at /usr/local/bin/mihomo, the configuration at /etc/mihomo/config.yaml, and runtime data at /var/lib/mihomo. The core needs a writable data directory to download rule sets or databases and save caches. The configuration file can remain read-only.

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

If the dedicated user already exists, skip useradd and confirm with id mihomo. The nologin path varies by distribution; check it with command -v nologin. Don’t make the entire configuration directory world-writable just to fix a permissions issue.

Configuration: verify the local port before adding a subscription

Test the setup with a minimal configuration

First, create config.yaml in the current directory with the contents below. This config uses direct connections only; it helps verify the binary, port, and permissions. It has no proxy nodes and is not a substitute for a complete configuration from your provider.

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

rules:
  - MATCH,DIRECT

mixed-port: 7890 accepts both HTTP proxy and SOCKS5 requests on the same TCP port. With allow-lan: false and a loopback address, the proxy is restricted to the local machine. This example does not configure external-controller, so it does not expose a control interface.

sudo install -m 0640 -o root -g mihomo ./config.yaml /etc/mihomo/config.yaml
sudo -u mihomo /usr/local/bin/mihomo -t -d /var/lib/mihomo -f /etc/mihomo/config.yaml
sudo -u mihomo /usr/local/bin/mihomo -d /var/lib/mihomo -f /etc/mihomo/config.yaml

-t checks whether the configuration loads; -d sets the runtime data directory; and -f specifies the configuration path. Run the core in the foreground, watch for errors, and check the port from another terminal:

ss -lntp 'sport = :7890'
curl -I --connect-timeout 10 --max-time 20 --proxy http://127.0.0.1:7890 https://example.com/

In this example, the request passes through local mihomo and connects directly to its destination. An HTTP response confirms that this route works, but does not prove that a remote proxy was used. When you’re done testing, press Ctrl+C in the terminal running the core to stop it and avoid a port conflict when systemd starts.

Replace it with a working subscription configuration

  1. Get a YAML configuration explicitly provided for Clash or mihomo. Node share links, web subscription instructions, and formats for other clients can’t be used directly as YAML.
  2. Keep a usable local copy, then install the new file at /etc/mihomo/config.yaml with permissions set to 0640 and ownership set to root:mihomo.
  3. Check the listening ports, proxy-groups, rules, and whether the policy names referenced by the rules match.
  4. Run -t again and start the service only if the check passes. For remote rule sets or databases, also verify network access and write permissions on the data directory.

Run mihomo as a systemd service: startup, logs, and config updates

Create /etc/systemd/system/mihomo.service and add the following contents. This service provides a standard local proxy and does not include the permissions required for TUN.

[Unit]
Description=mihomo local proxy service
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=mihomo
Group=mihomo
WorkingDirectory=/var/lib/mihomo
ExecStart=/usr/local/bin/mihomo -d /var/lib/mihomo -f /etc/mihomo/config.yaml
Restart=on-failure
RestartSec=5
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/mihomo

[Install]
WantedBy=multi-user.target

Restart=on-failure attempts to restart the process after an unexpected exit; it won’t fix an invalid configuration. ProtectSystem=strict restricts filesystem writes, granting write access only to the data directory. If the configuration references other paths, make sure the service user can read them and that downloaded files are saved to an allowed location.

sudo systemctl daemon-reload
sudo systemctl enable --now mihomo
systemctl status mihomo --no-pager
sudo journalctl -u mihomo -b -n 80 --no-pager

enable --now enables the service at boot and starts it immediately. network-online.target sets a startup ordering constraint. Whether the system actually waits for the network depends on its wait-online service; it does not guarantee internet access. Subscriptions, rule sets, and node connectivity still need to be checked separately.

What to do after changing the configuration

sudo cp -p /etc/mihomo/config.yaml /etc/mihomo/config.yaml.bak
sudoedit /etc/mihomo/config.yaml
sudo -u mihomo /usr/local/bin/mihomo -t -d /var/lib/mihomo -f /etc/mihomo/config.yaml
sudo systemctl restart mihomo
sudo journalctl -u mihomo -n 40 --no-pager

Restart the service only after the configuration check passes. Changes to the service file require daemon-reload; YAML changes alone do not. This setup applies configuration changes by restarting the service and does not define ExecReload, so don’t expect systemctl reload mihomo to work. A restart interrupts existing proxy connections, so plan ahead for downloads and remote sessions.

Linux proxy environment variables: terminal, Git, and APT

Once the core is listening, terminal programs still need to know the proxy address. The variables below affect only the current shell and processes launched from it afterward. They don’t change already-running browsers, other terminals, or system services.

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

curl -I --connect-timeout 10 --max-time 20 https://example.com/

The value of https_proxy can still be http://: it means HTTPS destinations are accessed through an HTTP proxy using a CONNECT tunnel, not that the connection to the destination becomes unencrypted. socks5h means the SOCKS proxy resolves the destination hostname; how the local core handles DNS from there depends on its DNS configuration.

  • Supported variables vary by program. For security reasons, curl does not accept uppercase HTTP_PROXY, so use the lowercase form shown above.
  • To apply these settings whenever you open an interactive terminal, add the variables to ~/.bashrc or ~/.zshrc, depending on your shell. Disable them when the service is stopped.
  • sudo often clears proxy environment variables. systemd services also don’t read a user’s shell startup files automatically, so configure their environment separately.
  • Inside a container, 127.0.0.1 points to the container itself, not the host. Don’t try to solve container connectivity by exposing your proxy to the entire local network.

Set a proxy explicitly for a specific command

git -c http.proxy=http://127.0.0.1:7890 ls-remote https://github.com/MetaCubeX/mihomo.git HEAD

sudo apt \
  -o Acquire::http::Proxy="http://127.0.0.1:7890" \
  -o Acquire::https::Proxy="http://127.0.0.1:7890" \
  update

These settings apply only to the current command, making them useful for testing without changing Git or APT settings permanently. To clear proxy variables from the current shell, run:

unset http_proxy https_proxy all_proxy no_proxy
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY

If an application ignores proxy environment variables and has no proxy settings of its own, a standard mixed port can’t capture its traffic automatically. That’s when TUN may be appropriate—not when you need to export the variables again.

Configure a desktop client: import, system proxy, and TUN

On a graphical desktop, choose a Linux client from the download center. Debian and Ubuntu typically use DEB packages; Fedora and other RPM-based distributions use RPM packages. Check the processor architecture too. In the commands below, the package has already been downloaded and renamed locally:

# Debian / Ubuntu
sudo apt install ./client.deb

# Fedora
sudo dnf install ./client.rpm

Client interfaces and menu names change between versions. Look for the three functional areas—configuration import, proxy selection, and traffic routing—and don’t confuse the app version with the core version.

  1. Import a configuration: Add a subscription URL on the configuration or subscription page, or import a local YAML file. Confirm that it downloaded successfully and select it. Seeing an entry in the list does not mean it is enabled.
  2. Choose a policy: Open the proxy groups page and select an available node from the group referenced by your rules. In rule mode, not all requests use that node; requests matching DIRECT still connect directly.
  3. Check the port: Find the mixed port in the network or port settings. Desktop clients may use a different port, such as 7897; don’t assume it’s 7890 as in this guide.
  4. Enable the system proxy: This mainly configures the proxy entry provided by the desktop environment. Coverage depends on whether each application honors system settings. After testing a browser, test terminal programs separately.
  5. Enable TUN if needed: Follow the client’s service installation and authorization steps, then check the virtual interface, routes, and DNS. Don’t run the client permanently as root.

TUN is not a “speed boost” button. It captures eligible traffic through a virtual network device and involves /dev/net/tun, network administration permissions, and routing. If SSH, local network access, or container networking breaks after enabling it, turn TUN off first to restore connectivity, then check route exclusions and DNS. Reassess the restrictions on the unprivileged service for your specific TUN setup; adding tun.enable: true alone won’t make it work.

Troubleshoot startup failures and proxy issues layer by layer

SymptomWhat to checkWhat to do
Architecture error when running the binaryuname -m、file /usr/local/bin/mihomoUse an extracted binary built for the correct architecture
Service keeps restartingjournalctl -u mihomo and mihomo -tFix the YAML, referenced names, or file permissions
Port already in usesudo ss -lntp 'sport = :7890'Stop the duplicate core, or align the port and application settings
Rule set download failsDownload logs, destination address, and data directory permissionsDetermine whether the issue is network access, a non-writable path, or invalid content
Browser works, terminal does notEnvironment variables and the program’s proxy settingsConfigure the terminal to use the correct local proxy port explicitly
Requests still connect directlyCurrent mode, matched rule, and policy groupCheck that the request isn’t matching MATCH, DIRECT or a group set to DIRECT

Troubleshoot in this order: process, listening port, application proxy settings, routing rules, and remote node. First, use curl --proxy explicitly to rule out application configuration issues, then check the logs for the matched rule and policy result. A node timeout and a refused local connection are different problems; don’t lump them together as “the Linux proxy isn’t working.”

After deployment, keep a record of the core version, a working configuration copy, the service file, and recovery commands. Before upgrading the binary, stop the service and keep the old one; then replace it and test the configuration. For more on rules and DNS, see the glossary. For persistent errors, continue with the steps in Troubleshooting.

Download Clash