Why Clash Meta Linux headless belongs in systemd, not in a screen session
When people say they want Clash Meta Linux headless or a Mihomo server on a VPS, they usually mean three concrete outcomes at once: the core should start after a kernel upgrade reboot without anyone logging in, it should come back if the process segfaults or the OOM killer intervenes, and operators should know exactly where configuration, runtime cache, and logs live so midnight troubleshooting does not devolve into searching the whole disk. A long-running tmux or screen session can keep a binary online until the next maintenance window, but it is not a substitute for systemd autostart semantics, dependency ordering against network-online.target, or structured logging through the journal.
This article is written for administrators who already understand SSH, basic firewall concepts, and YAML-shaped Clash profiles, but who do not want to translate a glossy Windows or macOS wizard into server commands. We stay on the Linux headless path: no X11, no Wayland, no local browser tied to a control port on 127.0.0.1 unless you deliberately expose one. Along the way we link to our subscription import tutorial for profile hygiene, to Clash Meta upgrade notes when you rotate binaries, and to TUN mode concepts before you enable kernel-level forwarding on a remote machine you cannot physically unplug.
If you still need a maintained desktop build for your laptop, our Clash download page lists current clients; the steps below focus on the upstream-compatible core that those clients embed, running bare on Linux.
/etc/clash-meta as the configuration directory, and a single systemd service in multi-user.target. Distributions vary in default umask, firewall front-ends, and whether /usr/local/bin is on PATH for non-login shells; adapt paths but keep the separation between binary, immutable-ish config, and writable state (cache, GeoIP databases, runtime files).
What systemd gives you that a cron @reboot line does not
systemd is not merely a replacement for classic init scripts; it is the place where modern Linux expresses service contracts. A well-written unit encodes the working directory, the user and group context, restart policy, file descriptor limits, and optional sandboxing knobs so the same service file works on your laptop lab VM and on a production VPS. For a Mihomo server, the most important fields are usually Restart=always paired with a sane RestartSec= backoff, plus an explicit After=network-online.target so the daemon does not race DHCP or cloud-init routes on first boot.
Cron-based hacks and user-written shell loops in /etc/rc.local tend to lose stderr, ignore exit codes, and fork unintentionally. systemctl status and journalctl -u give you a single pipeline for answering whether the last restart was clean, whether YAML parsing failed, or whether an outbound TLS handshake is flapping because the clock skewed during suspend—which still matters on small cloud instances that snapshot and restore. When you treat Clash Meta Linux headless as a first-class daemon, on-call engineers inherit familiar tooling instead of bespoke documentation that rots after one OS upgrade.
Step 1: Choose a directory layout before you download anything
Pick one home for the runtime and commit to it in both the unit file and your mental model. A common pattern is /etc/clash-meta/config.yaml for the active profile, optional ruleset or provider snippets alongside it, and /var/lib/clash-meta (or a subdirectory under /etc/clash-meta if you prefer a single tree) for files the process must write: downloaded rule providers, GeoSite databases, and any cache your configuration references. The point is not which exact path wins beauty contests; the point is that backups, configuration management tools, and restore drills all know which tarball to rsync.
Create a dedicated account rather than running as root. A system user named clash with /usr/sbin/nologin shell and a fixed UID simplifies ownership: chown -R clash:clash /etc/clash-meta for files the service should read, and chown -R clash:clash /var/lib/clash-meta for mutable data. If you later enable listeners below 1024, you can grant AmbientCapabilities=CAP_NET_BIND_SERVICE instead of reverting to root. Document the layout in your internal wiki so the next maintainer does not invent a parallel /opt/mihomo tree out of frustration.
Step 2: Install the Mihomo (Clash Meta) binary on the server
Download the official release artifact that matches your architecture—most VPS plans today are linux-amd64, while ARM64 builders need the matching tarball—and unpack the single executable, historically published under names such as mihomo or platform-specific Clash Meta builds, into /usr/local/bin with executable bits. Verify integrity using the project checksum files shipped beside the release; on production hosts, pin versions in your change log rather than blindly curling the latest tag every night without review.
After extraction, run mihomo -v or the equivalent version flag to confirm dynamic linking against libc matches your distribution. Alpine or other musl-based images need the musl build, not the glibc one. If you rely on automated configuration management, treat the binary as an immutable artifact: checksum verification, staged rollout, and rollback to the previous file if health checks fail. For transparency about licensing and upstream source locations, consult the official GitHub organization in a separate bookmark from your install automation; this article still recommends fetching release binaries through your own verified mirror or pipeline, not through random third-party rehosts.
Step 3: Place config.yaml, tighten ownership, and validate once by hand
Copy a known-good profile into /etc/clash-meta/config.yaml. Ensure external-controller either stays bound to 127.0.0.1 on the server—reachable only through SSH port forwarding—or sits behind mutual TLS and a firewall if you truly need remote REST access. On Linux headless deployments, the most common footgun is leaving the dashboard open to 0.0.0.0 without authentication because “it worked on my LAN lab.” Your VPS public address is not your LAN.
Set allow-lan deliberately. If the only consumers are processes on the same machine talking to 127.0.0.1:7890, you do not need LAN exposure at all. If other VMs in a private subnet should use this host as an HTTP proxy, bind explicitly to the internal NIC address and pair it with security group or iptables/nftables rules rather than spraying the mixed port across the entire internet. Align DNS stanzas with how you plan to test: if you use Fake-IP, remember that misconfigured dns sections look exactly like “the proxy is down” from the client perspective; our Fake-IP and DNS guide walks through that failure mode in depth.
Before systemd owns the process, run a one-shot foreground test as the service user: sudo -u clash /usr/local/bin/mihomo -d /etc/clash-meta. Watch stdout for parse errors, missing provider URLs, or permission failures writing cache. Fix those interactively; the journal should only record clean startups during normal operations.
Step 4: Author the systemd unit with restart policy and hardening basics
Create /etc/systemd/system/clash-meta.service (the filename is arbitrary but should read naturally in systemctl status clash-meta). A production-shaped unit often resembles the following skeleton, adjusted for your paths and optional capability lines:
[Unit]
Description=Mihomo (Clash Meta) proxy core
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=clash
Group=clash
WorkingDirectory=/etc/clash-meta
ExecStart=/usr/local/bin/mihomo -d /etc/clash-meta
Restart=always
RestartSec=3
LimitNOFILE=1048576
# Optional: only if your unit actually needs low ports without root
# AmbientCapabilities=CAP_NET_BIND_SERVICE
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
Restart=always ensures transient faults return to a running state without human login. RestartSec prevents tight crash loops from hammering upstream APIs when your subscription URL is wrong. Raising LimitNOFILE matters when thousands of established connections flow through an exit node. If you later enable TUN, you will likely add capability lines for CAP_NET_ADMIN and revisit forwarding sysctl defaults; until then, keep the unit minimal so security reviewers see only what you actually use.
Step 5: Reload units, enable on boot, and start immediately
Run systemctl daemon-reload so systemd picks up the new file, then systemctl enable --now clash-meta.service to both schedule systemd autostart at boot and start the daemon in the current session. Confirm state with systemctl is-active clash-meta.service and inspect the high-level summary with systemctl status clash-meta.service, which prints the last few log lines and shows whether systemd considers the service healthy.
If the service enters a restart loop, resist the urge to disable the unit; instead, widen the window with journalctl -xeu clash-meta.service and fix the underlying YAML or network dependency. Common first-boot failures include DNS not yet ready when the daemon tries to fetch remote rule providers, which you can mitigate by staging static assets on disk or by ordering after a resolver-specific target if your distribution documents one.
Step 6: Standardize on journalctl for logs and log rotation expectations
Unless you explicitly configure file-based logging inside Mihomo, stdout and stderr land in the systemd journal. Operators should memorize two queries: journalctl -u clash-meta.service -f for live tailing during rule edits, and journalctl -u clash-meta.service --since '10 minutes ago' for incident windows. Export to files only when your compliance regime demands immutable off-host retention; otherwise, let journald compression and vacuum policies manage disk growth.
If you prefer structured files under /var/log/clash-meta/, add StandardOutput=append: directives in the unit and create the log directory with correct permissions. Remember that log rotation then becomes your problem again. Most small teams choose the journal because it already integrates with centralized logging agents on major clouds.
Step 7: Verify mixed inbound, rules, and outbound health with curl
Assuming your profile exposes a mixed HTTP and SOCKS listener on 127.0.0.1:7890, run curl -x http://127.0.0.1:7890 -s https://api.ipify.org on the server itself. Compare the result to curl -s https://api.ipify.org without proxy variables. Divergence confirms that at least TCP to your outbound chain works; if both paths show the same public IP, you are either bypassing the proxy or your rules mark that probe host as DIRECT. Extend checks to HTTPS sites that mirror your real workloads, not only IP echo services.
For multi-homed hosts, repeat the curl tests from a secondary private IP or from a client VM that should use this host as its explicit forward proxy. When something fails, separate DNS symptoms from TCP symptoms: NXDOMAIN or bogus answers often trace back to dns stanzas, while connection resets trace to firewalls or upstream node instability. If you expose metrics or REST on loopback only, use SSH -L forwarding to reach them from your workstation without opening another public port.
Step 8: When headless Linux proxy users ask about TUN on a VPS
Transparent TUN mode on Linux is powerful and invasive: it expects cooperation from routing tables, reverse-path filtering, and sometimes conntrack tuning. On a rented VPS you might not have full control over the hypervisor networking assumptions, and a half-configured TUN stack can make the machine unreachable over SSH. If your requirement is “applications on this same Linux host respect the proxy,” an explicit http_proxy environment or local iptables redirection to the mixed port is often safer than global TUN on day one.
When you are ready, read the TUN guide with a serial console or provider out-of-band recovery in mind, add only the capabilities you need to the systemd unit, and test during a maintenance window. Keep a rollback unit file in version control so you can revert in one systemctl daemon-reload cycle.
Step 9: Firewall posture, secrets, and least privilege on shared metal
Even a perfect Mihomo server unit is unsafe if inbound lists are wide open. Default-deny public ingress, allow SSH from known jump hosts only, and expose proxy ports solely on internal interfaces when downstream clients live in the same VPC. Store subscription URLs with tokens in files readable only by the service user; avoid world-readable chmod 644 on secrets just because YAML is plain text.
Consider optional hardening directives such as NoNewPrivileges=yes, ProtectSystem=strict, and ProtectHome=yes once you confirm the daemon never needs to write outside declared directories. systemd can also apply ReadWritePaths= allowlists when you pair them with mount namespacing. Each hardening line can break exotic plugins, so add them incrementally with tests rather than copying a maximal template from the internet wholesale.
Step 10: Upgrade workflow without orphan processes or stale unit metadata
Binary upgrades should be boring: stop the service, replace the file under /usr/local/bin, run the version command as root to sanity-check execution, then start the service again. If you use atomic replace patterns (write new binary beside old, rename into place), you avoid partially written executables mid-download. After upgrades, skim journalctl for deprecation warnings because Mihomo evolves quickly; our upgrade guide highlights common YAML transitions.
When configuration changes are riskier than binary swaps, consider systemctl reload only if upstream documents a safe SIGHUP behavior for your exact version; otherwise use a clean restart so connection tracking stays coherent. Keep previous binaries versioned on disk until you confirm metrics look stable.
Closing: treat the VPS like a tiny ISP inside your account boundary
Clash Meta Linux headless installs succeed when operators respect the same disciplines as any other network daemon: explicit paths, a systemd autostart contract, observable logs, and firewall rules that match the real consumption pattern rather than the tutorial default. Compared with dragging a GUI client onto a server and hoping X11 forwarding works, the headless approach is quieter on CPU, easier to automate, and far simpler to audit.
Once routing behaves, revisit subscription hygiene in our subscription import tutorial so remote rule fetches stay reliable. When you want the same ecosystem on your workstation with a polished UI, download Clash for free from our official page and experience the difference.