Deploy mihomo on Linux: Desktop GUI Clients and Headless systemd Setup
Install a Linux desktop client or run mihomo headlessly on a server with systemd. Covers configuration, startup on boot, and secure panel access.
1. Use a GUI client on desktops and systemd on servers
There are two common ways to run a proxy on Linux. If you have a desktop environment and want to switch nodes from the system tray, install a GUI client that bundles the mihomo core. If you only have an SSH terminal and need the proxy to keep running after you log out, deploy the mihomo core directly and manage it with systemd. Both approaches use the same configuration concepts, but differ in configuration paths, subscription updates, and permission management.
ClashX is a macOS client, not a Linux package. On Linux desktops, choose a compatible package for your distribution from clients such as Clash Verge Rev or FlClash. Servers don't need a desktop environment or a GUI client process to use a proxy.
| Use case | Recommended approach | Configuration and startup |
|---|---|---|
| Personal Linux desktop | GUI client | Client settings and desktop session startup |
| SSH server | mihomo with systemd | /etc/mihomo/config.yaml, system service |
| Proxy for specific commands | Local HTTP / SOCKS listener | Application settings or process environment variables |
| Route more system traffic through the proxy | Check permissions before configuring TUN | Core configuration, routing, and DNS |
2. Install and configure a Linux desktop client
Check your architecture and package format
First, run the following command to identify your system. x86_64 usually corresponds to amd64 / x64 packages, while aarch64 usually corresponds to arm64 packages; they aren't interchangeable. Ubuntu and Debian typically use DEB packages, while Fedora typically uses RPM. Distribution versions also affect system library compatibility, so check the selected client's current release notes before installing.
uname -m
cat /etc/os-release
- Go to the downloads page, select Linux, then choose a client package for your architecture and package format.
- Open the downloaded DEB or RPM in your file manager and install it with your system's software installer. If dependency resolution fails, address the dependencies reported by the package manager rather than forcing the installation.
- Launch the client and import a subscription URL or local YAML file from the “Configuration” or “Profiles” page. Menu names vary by client and version, so follow the interface you're using.
- Select the imported configuration, confirm that the node list and proxy groups are present, then check the proxy port under “Settings” and note the actual listening port.
- Test with the explicit proxy command below before considering the client's “System Proxy” toggle.
curl --proxy http://127.0.0.1:7890 \
--connect-timeout 10 --max-time 20 \
-I https://example.com/
Here, 7890 is an example; not every client uses this port. An HTTP response confirms that a request went through, but doesn't prove it used a remote node. If the rules match DIRECT, the request is still sent directly. Check the connection log's matched rule, proxy group, and outbound node to confirm the route.
Desktop startup is not the same as a system service
A client's “Launch at startup” option usually depends on a desktop session, meaning it starts after a user logs in. That isn't suitable for a background proxy on a server that restarts without anyone logging in. Also, don't run a GUI client and the systemd service below on the same 127.0.0.1:7890 port. Use one or assign them different ports.
3. Install the mihomo core and configuration
These steps are for Linux systems that use systemd. Run the commands as a regular user with sudo access. Examples use standard tools found on Debian and Ubuntu; if your distribution doesn't include commands such as useradd, install the relevant tools with its package manager first. Containers and some minimal systems may not include systemd, so this service setup won't apply as written.
Install the executable
Download a Linux build from the official mihomo releases that matches your architecture and CPU instruction set. Extract it and place the executable at /tmp/mihomo. Use the filename from the downloaded archive; don't try to run the archive itself. Older CPUs may not support builds that require newer instruction sets.
file /tmp/mihomo
sudo install -m 0755 /tmp/mihomo /usr/local/bin/mihomo
/usr/local/bin/mihomo -v
Use -v to record the core version you're running. For Exec format error, check the CPU architecture; for Illegal instruction, check the build's instruction set requirements. This guide doesn't pin a specific version in the service file, so upgrades can use the same installation path.
Create a dedicated user and directories
sudo useradd --system --user-group \
--home-dir /var/lib/mihomo \
--shell /usr/sbin/nologin mihomo
sudo install -d -o root -g mihomo -m 0750 /etc/mihomo
sudo install -d -o mihomo -g mihomo -m 0750 /var/lib/mihomo
If the user already exists, skip creation and check its group membership. If your distribution puts nologin elsewhere, locate it with command -v nologin and adjust the path. Store configuration in /etc/mihomo and cache and runtime data in /var/lib/mihomo. The service account only needs to read the configuration; it shouldn't need to modify files containing subscription credentials.
Validate the process with a minimal configuration
sudo tee /etc/mihomo/config.yaml > /dev/null <<'YAML'
mixed-port: 7890
allow-lan: false
bind-address: 127.0.0.1
mode: rule
log-level: info
ipv6: false
rules:
- MATCH,DIRECT
YAML
sudo chown root:mihomo /etc/mihomo/config.yaml
sudo chmod 0640 /etc/mihomo/config.yaml
sudo -u mihomo /usr/local/bin/mihomo \
-t -d /var/lib/mihomo -f /etc/mihomo/config.yaml
When importing a provider's configuration, make sure the response is YAML that mihomo can read—not a web page, login prompt, or text containing only node URIs. After replacing the configuration, check the listening address, controller port, and final fallback rule. Subscription settings may override the local listening restrictions described here.
-t checks the configuration; it doesn't confirm that remote nodes, rule download URLs, or target websites are reachable. The check may also access files referenced by the configuration. Read the logs to distinguish YAML syntax errors from path permission or resource download errors.
4. Create a systemd service and enable startup on boot
Start with a regular, non-root account for the standard proxy. Listening on a high port such as 7890 doesn't require root. This service doesn't grant TUN permissions; it's suitable for an explicit HTTP / SOCKS proxy, not a transparent gateway.
sudo tee /etc/systemd/system/mihomo.service > /dev/null <<'UNIT'
[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=5s
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/mihomo
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now mihomo.service
sudo systemctl status mihomo.service --no-pager
sudo journalctl -u mihomo.service -b -n 80 --no-pager
enable --now enables the service at boot and starts it immediately. Whether network-online.target actually waits for networking depends on whether your distribution configures the relevant wait-online service. It doesn't guarantee that the internet or subscription URL is reachable. Plan sensible retries and update handling for remote resources.
- After changing
config.yaml, validate the configuration, then runsudo systemctl restart mihomo. - After editing the service unit, run
sudo systemctl daemon-reload, then restart the service. - To check the status, run
systemctl is-active mihomoandsystemctl is-enabled mihomo. - To stop the service and disable startup on boot, run
sudo systemctl disable --now mihomo.
This unit doesn't define ExecReload, so don't assume systemctl reload mihomo is a universal update command. Restarting is the clearest way to confirm that the new configuration has loaded, but it may interrupt existing connections. For a graceful update, follow the current core's controller API documentation and design a separate procedure.
5. Verify the port, proxy route, and application behavior
sudo ss -lntp 'sport = :7890'
curl --proxy http://127.0.0.1:7890 \
--connect-timeout 10 --max-time 20 \
-I https://example.com/
curl --proxy socks5h://127.0.0.1:7890 \
--connect-timeout 10 --max-time 20 \
-I https://example.com/
The mixed port accepts both HTTP proxy and SOCKS requests. With socks5h, curl sends the target hostname to the proxy for resolution; with socks5, the hostname is usually resolved locally first. This affects DNS troubleshooting, so the same port doesn't mean the two behave identically.
Proxy only selected programs in the current shell
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
export no_proxy=localhost,127.0.0.1,::1
curl --connect-timeout 10 --max-time 20 -I https://example.com/
unset http_proxy https_proxy no_proxy
The value of https_proxy can still use http://. This means an HTTP proxy establishes the CONNECT tunnel; it doesn't mean the local proxy port itself needs to provide TLS. Environment variables affect only programs that read them and their child processes. They won't automatically apply to browsers, other systemd services, or all UDP traffic.
Package managers, container daemons, and background services may have their own proxy settings. A successful curl request in one terminal doesn't mean Docker image pulls or system updates are using the proxy too. Check each program's proxy configuration separately. Don't put proxy variables containing credentials in files readable by all users.
6. Access the controller API and dashboard through an SSH tunnel
mihomo's controller API and proxy port serve different purposes. Port 7890 accepts proxy requests; 9090 in the configuration below is for the controller API. For a server dashboard, use SSH local port forwarding rather than exposing the controller directly to the internet.
Enable the local controller API
Run the following command once, starting from the minimal configuration used above. If the existing configuration already has external-controller or secret, edit those fields rather than adding duplicate keys. The command generates a random secret and writes it to the protected configuration without printing it in the terminal.
controller_secret=$(openssl rand -hex 32)
printf '\nexternal-controller: 127.0.0.1:9090\nsecret: "%s"\n' \
"$controller_secret" | \
sudo tee -a /etc/mihomo/config.yaml > /dev/null
unset controller_secret
sudo -u mihomo /usr/local/bin/mihomo \
-t -d /var/lib/mihomo -f /etc/mihomo/config.yaml
sudo systemctl restart mihomo
OpenSSL must be installed for this step. The secret is a control credential. Before entering it in the dashboard, view the configuration's secret field from a secure administrative terminal. Don't paste the entire configuration into a public support ticket. Enabling the controller API alone doesn't provide a web dashboard.
Install the dashboard's static files
Download the built static files from the official release package of a compatible dashboard project, then place the contents of the directory containing index.html in /etc/mihomo/ui. Don't use an unbuilt source directory or add an extra directory level that leaves the entry file in the wrong location.
sudo install -d -o root -g mihomo -m 0750 /etc/mihomo/ui
sudoedit /etc/mihomo/config.yaml
Add the following fields at the top level of the configuration, and make sure the service account can read the dashboard files:
external-ui: /etc/mihomo/ui
After validating the configuration, restart the service. On your local computer, run the SSH command to forward local port 9090 to port 9090 on the server's loopback address. mihomo-server is an example host alias defined in your SSH configuration; replace it with a reachable hostname or an alias you've configured.
ssh -N -L 127.0.0.1:9090:127.0.0.1:9090 mihomo-server
Keep the SSH session open, then visit http://127.0.0.1:9090/ui/ in a browser on your local computer. Set the dashboard backend to http://127.0.0.1:9090 and enter the secret from the configuration. The local address is on the computer running the browser; the SSH tunnel connects it to the server. Don't have the browser connect directly to the server's loopback address.
7. TUN, subscription updates, and common troubleshooting pitfalls
TUN requires more than flipping a switch
Linux TUN requires an available /dev/net/tun device, and usually needs network administration privileges. It also involves automatic routes, policy routing, and DNS interception. The dedicated-user service in this guide doesn't grant those capabilities, so adding tun.enable: true may fail with a permissions error. Running every component as root long-term shouldn't be your first response.
Protect SSH access especially carefully on servers. Incorrect default routes, DNS hijacking, or a proxy loop can cut off your connection. Before enabling TUN, prepare a recovery route through your cloud console or serial console, check the current core documentation for permission requirements, then plan service capabilities, route exclusions, and rollback steps separately. A desktop client's TUN service mode is not the same as this standard proxy unit.
Manage subscription updates separately
Downloading a complete subscription YAML as config.yaml doesn't mean the core will periodically download the entire configuration again. GUI clients often offer scheduled configuration updates. For a headless setup, decide whether to use a script to update the full configuration or update node providers using proxy-providers with url, path, and interval.
A node provider file and a full configuration have different structures; you can't paste any subscription URL into a provider field. Store provider cache files under /var/lib/mihomo, where the service can write to them. To update the full configuration, download it to a temporary file, validate its format, then replace the active file. Don't overwrite your only working configuration directly from a network download.
| Symptom | Check first | What to do |
|---|---|---|
| address already in use | sudo ss -lntp | Check whether a GUI client or another mihomo process is already using 7890 / 9090. |
| permission denied | Configuration readability, cache paths, and systemd write restrictions | Keep the configuration read-only and move cache and provider files to a writable data directory. |
| Service keeps restarting | journalctl -u mihomo -b | Fix the first configuration, resource, or permission error in the logs before restarting. |
| Dashboard is blank or returns 404 | /etc/mihomo/ui/index.html | Confirm that you have the built files, the directory structure is correct, and the service account can read them. |
| Dashboard reports an authentication failure | Backend URL, SSH forwarding port, and secret | Use the controller port, not the proxy port, and check the secret in the current configuration. |
| Proxy request succeeds but still goes direct | Connection log, matched rule, and proxy group selection | Check whether the request matched DIRECT; the minimal test configuration only has a direct-connection rule. |
Upgrade and rollback checklist
Before upgrading, keep a copy of the old binary, configuration, and service unit, and record the output of mihomo -v. Test the existing configuration with the new binary using -t before scheduling the replacement and restart. After upgrading, check the service status, port 7890, an explicit proxy request, and the dashboard connection in that order. If anything fails, restore the old binary and compatible configuration rather than changing several network settings at once.
A deployment is ready when the service is active after a reboot, the proxy listens only on the intended address, applications make requests according to the rules, and the controller is accessible only through a protected management route. Verify all four separately; seeing a running process alone isn't enough.