Choose a deployment path: run the core on servers, use a client on desktops as needed
You don't need a graphical desktop to run Clash on Linux. mihomo is a core compatible with the Clash configuration ecosystem, with support for additional protocols and rules. It was commonly known as Clash Meta in earlier releases. The core reads your configuration, listens on proxy ports, and handles connections. Subscription managers, tray icons, and toggle panels are usually provided by graphical clients.
This guide uses a standalone mihomo binary managed as a system-level systemd service. It works well on SSH-accessible servers, development machines, and desktop distros that need a persistent proxy. The configuration lives at /etc/mihomo/config.yaml, runtime data at /var/lib/mihomo, and the local HTTP/SOCKS mixed port is 7890.
- Headless servers: Use this setup and manage the service from the command line and logs—no desktop environment required.
- Everyday desktops: If you regularly switch nodes or edit rules, consider a graphical client that manages its own core.
- Already using a client? Check whether it is listening on port 7890. Two cores cannot bind to the same address and port at the same time.
Check your system architecture and install the mihomo binary
1. Check for systemd and identify the CPU architecture
ps -p 1 -o comm=
uname -m
command -v curl
command -v gzip
The first command should show systemd. If you're in a container, on a minimal system, or in an environment where systemd isn't enabled, the systemctl commands below won't work as written. Use the process manager for that environment instead.
| Architecture output | Release asset to choose | Notes |
|---|---|---|
x86_64 | Linux amd64: choose a v1-compatible build when possible | v1, v2, and v3 refer to CPU instruction-set compatibility levels, not client versions. |
aarch64 | Linux arm64 | Common on ARM cloud servers and development boards running a 64-bit OS. |
armv7l | Choose the Linux armv7 asset that matches your device | Check the hardware and release notes; an arm64 file is not a substitute. |
On Debian or Ubuntu, install missing tools with sudo apt update && sudo apt install curl gzip ca-certificates. On Fedora, use sudo dnf install curl gzip ca-certificates. The download commands below use Bash.
2. Download and install
Open the official mihomo releases page, find the stable .gz binary for your architecture, and copy its full HTTPS download link. Don't use the releases page URL or a source-code archive as the binary link. The prompt below asks for the link, so there's no hard-coded example version to update.
mkdir -p "$HOME/mihomo-install"
cd "$HOME/mihomo-install"
read -r -p 'Paste the HTTPS link to the official Linux .gz asset: ' download_url
curl --fail --location --proto '=https' \
--output mihomo.gz "$download_url"
unset download_url
gzip -t mihomo.gz
gzip -dc mihomo.gz > mihomo
chmod 0755 mihomo
./mihomo -v
sudo install -m 0755 mihomo /usr/local/bin/mihomo
/usr/local/bin/mihomo -v
Run each command in order and confirm it succeeds before continuing. The -v output shows the core version and architecture. An Exec format error usually means you chose the wrong architecture. For Illegal instruction, check the CPU instruction-set requirements; on amd64, try an asset with a lower compatibility level.
If the server can't reach the releases site, download the file on your workstation, name it mihomo.gz, and upload it to the server's installation directory over SFTP. Then continue from gzip -t. Don't disable TLS certificate verification just to download the file.
Create a dedicated service account and test with a minimal configuration
The core doesn't need to run as root to serve a regular proxy on port 7890. Create a dedicated system account and keep the configuration separate from writable data to limit the impact of mistakes. The commands below use useradd, available on many common distros. If the account already exists, verify it with id mihomo instead of creating it again.
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
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
-f explicitly selects the configuration file, and -d sets the core's runtime data directory. Check that relative data files, caches, and provider files are resolved against that directory; don't assume the core always reads configuration from the current terminal directory. A configuration test catches many syntax and reference issues, but it doesn't replace testing an actual node connection.
Create a systemd service and enable it at boot
Use absolute paths throughout the service file and specify a fixed service account. These hardening options suit a local explicit-proxy setup: they let the core write to its data directory without giving it free rein to modify system directories.
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
ExecStartPre=/usr/local/bin/mihomo -t -d /var/lib/mihomo -f /etc/mihomo/config.yaml
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
systemctl status mihomo.service --no-pager
sudo journalctl -u mihomo.service -n 80 --no-pager
sudo ss -lntp 'sport = :7890'
The service should normally show active (running), with port 7890 listening on 127.0.0.1. enable starts the service at boot, while --now starts it immediately as well. Whether network-online.target actually waits for network connectivity depends on the distro's network manager and wait-online service; it doesn't guarantee access to external sites.
- After changing the configuration: Run the earlier
mihomo -ttest first. If it passes, runsudo systemctl restart mihomo. - After changing the service file: Run
sudo systemctl daemon-reload, then restart the service. - Follow the logs live: Run
sudo journalctl -u mihomo -f. Pressing Ctrl+C exits the log viewer without stopping the service. - Stop the service and disable it at boot: Run
sudo systemctl disable --now mihomo.
This guide doesn't configure ExecReload, so don't use systemctl reload mihomo as a general update command. Restarting briefly interrupts existing connections handled by the process, so schedule remote updates accordingly.
Importing a subscription: distinguish full configurations from node lists
A subscription URL doesn't always return the same format. A complete Clash/mihomo configuration usually includes proxies or proxy-providers, proxy-groups, and rules. A provider YAML containing only nodes, a Base64-encoded node list, or a single share link can't be used directly as this guide's main configuration file.
Safely replace a complete configuration
If your provider supplies a complete mihomo configuration, download it to a temporary file, then inspect and test it. The commands below prompt for the subscription URL without echoing it in an interactive Bash session, so a token-bearing URL isn't written directly to shell history. The temporary file is removed when the session exits. Server access logs and privileged local users may still be able to see the URL, so don't treat a subscription link as public.
(
umask 077
tmp=$(mktemp) || exit 1
trap 'rm -f "$tmp"' EXIT
read -r -s -p 'Paste the HTTPS URL for the full configuration subscription: ' sub_url
printf '\n'
curl --fail --location --proto '=https' \
--output "$tmp" "$sub_url" || exit 1
unset sub_url
sudo install -o root -g mihomo -m 0640 \
"$tmp" /etc/mihomo/config.next.yaml || exit 1
sudoedit /etc/mihomo/config.next.yaml
sudo -u mihomo /usr/local/bin/mihomo -t \
-d /var/lib/mihomo \
-f /etc/mihomo/config.next.yaml || exit 1
sudo cp -p /etc/mihomo/config.yaml /etc/mihomo/config.previous.yaml || exit 1
sudo mv /etc/mihomo/config.next.yaml /etc/mihomo/config.yaml || exit 1
sudo systemctl restart mihomo
)
In your editor, confirm mixed-port: 7890, allow-lan: false, and bind-address: 127.0.0.1. Review external control interfaces, DNS listeners, the TUN setting, and local file paths. This setup doesn't need an externally accessible control interface or DNS listener, and it doesn't enable TUN. Remove or disable unnecessary settings, then test the configuration.
Every policy group referenced by a rule must exist, and its nodes must be available. If a rules database or provider file fails to download, check the specific URL and error in the logs; valid YAML alone doesn't mean the subscription is ready to use. If the configuration expects files in a user's home directory, this service's ProtectHome=true setting will block access. Move those files into the data directory and update their paths.
Node providers and the limits of automatic updates
proxy-providers can update nodes using their own url, path, and interval. For example, interval: 3600 means updates every 3,600 seconds. This updates the provider; it doesn't automatically replace all rules and policy groups in the main configuration. Store provider files at relative paths within the writable data directory.
To update the full main configuration on a schedule, design a separate workflow for downloading, syntax-checking, backing up, replacing, and rolling back on failure, then schedule it with a systemd timer. Don't overwrite the active config.yaml with a one-line scheduled download command. If a subscription fetch fails, keeping the last known-good configuration is usually safer than restarting with an empty file.
Set terminal proxy variables and test HTTP and SOCKS separately
A successful systemd start only means the core is running; it doesn't automatically route every terminal app through the proxy. Start by testing with explicit proxy options to rule out environment-variable issues. The requests below use public sites as connectivity examples, not verified results. Response times and egress depend on your network, rules, and nodes.
curl --noproxy "" --proxy http://127.0.0.1:7890 \
--connect-timeout 10 --max-time 30 \
-I https://www.kernel.org/
curl --noproxy "" --proxy socks5h://127.0.0.1:7890 \
--connect-timeout 10 --max-time 30 \
-I https://www.kernel.org/
With socks5h, curl passes the destination hostname to the SOCKS proxy for resolution; with socks5, the app usually resolves it first. When an HTTP proxy connects to an HTTPS site, it normally uses a CONNECT tunnel, so the proxy URL remains http://127.0.0.1:7890—don't change the proxy endpoint to HTTPS.
Enable for the current terminal only
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 --connect-timeout 10 --max-time 30 -I https://www.kernel.org/
Environment variables are passed only to child processes started by the current shell. They don't affect apps that are already running or automatically carry over to other systemd services. Support for variable names and precedence varies by app; for curl's HTTP proxy, use lowercase http_proxy. To make the settings persistent, add them to the current user's ~/.bashrc or the appropriate shell configuration file. Keep in mind that commands relying on the proxy may fail when the proxy service is stopped.
sudo often filters proxy variables by default, so a successful test as a regular user doesn't mean every administrative command is configured to use the proxy. To proxy another systemd service, set Environment= in that service's own drop-in and confirm the app supports these variables.
Restore direct connections
unset http_proxy https_proxy all_proxy no_proxy
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY
git config --global --get http.proxy
Git may also have a separate global proxy setting. Use the last command to check it; if you no longer need the old setting, run git config --global --unset http.proxy. Clearing environment variables won't remove proxy settings saved by an application.
The service is running, but there's no connectivity: check these four layers
- Process: Run
systemctl status mihomo. If it keeps restarting, look for the first error before it exits injournalctl; don't focus only on the final “too many restarts” message. - Listener: Run
sudo ss -lntp 'sport = :7890'. No listener usually means startup failed or the port is configured differently. If you seeaddress already in use, check for another client or a manually started core. - Proxy: Test with a curl request that includes
--proxy. If the connection is refused, check the local port. If the handshake times out, check the node, outbound restrictions on the server, and protocol compatibility. - Rules: Check the logs for the matched rule and selected policy. A successful HTTP response only means the request completed; if the rule is still
MATCH,DIRECT, that doesn't prove a proxy node is working.
If the configuration test reports permission denied, check execute permissions on the /etc/mihomo directory, file group ownership, and whether a provider is writing to a read-only path. If systemd reports start-limit-hit, fix the underlying issue first, then run sudo systemctl reset-failed mihomo and sudo systemctl start mihomo.
In a remote SSH session, 127.0.0.1 refers to the server, not your workstation. You don't need to open port 7890 in the cloud security group for this setup. To temporarily use the server's proxy from your workstation, run ssh -N -L 17890:127.0.0.1:7890 your-server-ssh-target on the workstation, replacing the final argument with your usual SSH target. Then set your workstation app's proxy to 127.0.0.1:17890.
Desktop distros and TUN: don't mix two traffic-routing setups
On GNOME, open Settings → Network → Network Proxy, select Manual, and set the HTTP and HTTPS proxy host to 127.0.0.1 with port 7890. Menu labels can vary slightly by distro. Desktop proxy settings affect only apps that honor them; they don't guarantee that terminal tools, games, or background services will use the proxy.
If you switch to a graphical client to manage subscriptions and nodes, stop this standalone service first, then let the client start its own core. That keeps the ports, configuration source, and logs in one place, making troubleshooting easier. A graphical client's system proxy toggle and starting a systemd core service are separate things; neither replaces the other.
When to consider TUN
- Consider TUN only if your apps don't support explicit proxies or you need to route a broader range of traffic.
- First check whether
/dev/net/tunexists and whether the container or host allows virtual network interfaces to be created. - This unprivileged service doesn't have the permissions needed to configure network interfaces and routes. Adding
tun.enablealone isn't enough. - If you use Linux capabilities, assess permissions such as
CAP_NET_ADMINagainst the features you need, and review the systemd restrictions as well. Don't remove all protections outright. - Test automatic routing, policy routing, and DNS interception together. Keep a second SSH session open and make sure you can access the cloud console before changing networking on a remote server.
A deployment is ready when the configuration test passes, the service starts at boot, port 7890 listens only on the local machine, explicit proxy requests succeed, and the logs confirm connections match the expected policies. Complete this path before adding automatic updates or TUN; it makes issues easier to isolate.