Using the mihomo Core on macOS: Command-Line Guide
On this page
The mihomo core runs on macOS as a single command-line binary: no installer, no window, no menu bar icon. You download one compressed file, make it executable, point it at a configuration directory, and it starts listening on a local port. That minimalism is the appeal — everything the core does scrolls past in the terminal, which makes it the best place to debug a configuration that a graphical client silently refuses to load.
This guide walks the whole path on a Mac: choosing the amd64 or arm64 build, unpacking it, clearing the Gatekeeper quarantine flag, installing to /usr/local/bin, running the core with -d, confirming that traffic really flows, and keeping it alive in the background with a launchd agent.
One expectation to set first: the core does not configure your Mac for you. It opens a local proxy port and waits. There is no system proxy switch and no tray menu, so if you want the polished desktop experience, a GUI client is the right answer — the last section explains when to make that call.
Choose the amd64 or arm64 build
mihomo ships separate binaries for Intel and Apple Silicon Macs. One command tells you which you need:
uname -m
x86_64 means an Intel Mac and the amd64 download; arm64 means an Apple Silicon (M-series) Mac and the arm64 download. The Apple menu → About This Mac reports the same thing in words. Both builds of mihomo v1.19.29 are published as single-file .gz archives in the download center's macOS section. Choosing wrong is not fatal on Apple Silicon, where Rosetta 2 can translate the Intel build, but a native binary avoids that overhead entirely — the Intel versus Apple Silicon guide covers the trade-off in detail.
Unpack it, clear quarantine, make it executable
Everything below happens in Terminal, starting in the folder where the download landed:
cd ~/Downloads
gunzip mihomo-darwin-*.gz
chmod +x mihomo-darwin-*
xattr -d com.apple.quarantine mihomo-darwin-*
gunzip expands the archive into a plain executable and removes the .gz file in the process. chmod +x marks it runnable. The xattr line strips the quarantine attribute macOS attaches to anything downloaded from the internet — without it, Gatekeeper refuses to run the binary and complains that the developer cannot be verified.
Careful: clearing quarantine is a deliberate override of a security check. Do it only for files whose origin you trust and have checked.
Install to /usr/local/bin and run the core
Move the binary somewhere on your PATH, then confirm it starts:
sudo mv mihomo-darwin-* /usr/local/bin/mihomo
mihomo -v
A version string means the binary matches your architecture. Next it needs a data directory — mihomo keeps config.yaml and its geo databases together in one folder, and the -d flag says which folder that is:
mkdir -p ~/.config/mihomo
mihomo -t -f ~/.config/mihomo/config.yaml
mihomo -d ~/.config/mihomo
Run the -t check first. It parses the configuration, reports problems with line numbers, and exits without starting anything, which is far cheaper than reading a crash log afterwards. If you are writing the file by hand, the YAML configuration basics guide explains the structure field by field. The last command runs the core in the foreground and prints its log as it works; Control-C stops it.
Check that traffic really goes through
Leave the core running in one terminal window, open a second, and send a request through its local port:
curl -x http://127.0.0.1:7890 -I https://www.example.com
Replace 7890 with the mixed-port value from your own configuration — commonly 7890 or 7897, but your file is the authority. Response headers coming back mean the chain works end to end. A connection-refused error means nothing is listening on that port, and the log in the first window usually says why. Keep in mind that only applications pointed at that port use the proxy: your browser keeps going direct until you set the system proxy or configure it explicitly.
Keep it running with a launchd agent
Foreground mode is ideal while testing, but for a core that survives logout and reboot, macOS uses launchd. Create ~/Library/LaunchAgents/com.mihomo.core.plist with a user agent like this:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.mihomo.core</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/mihomo</string>
<string>-d</string>
<string>/Users/yourname/.config/mihomo</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/mihomo.log</string>
<key>StandardErrorPath</key>
<string>/tmp/mihomo.err</string>
</dict>
</plist>
Load it and confirm launchd picked it up:
launchctl load -w ~/Library/LaunchAgents/com.mihomo.core.plist
launchctl list | grep mihomo
Two details trip people up. First, launchd does not expand ~, so the path inside ProgramArguments must be absolute — replace yourname with your real home folder. Second, a user agent runs with your normal privileges, which is fine for local proxy ports but not for anything that needs to create network interfaces. To stop it, run launchctl unload ~/Library/LaunchAgents/com.mihomo.core.plist; on recent macOS releases the modern equivalents are launchctl bootstrap gui/$(id -u) and launchctl bootout.
Tip: the two log paths in the plist are your lifeline once the core runs detached — tail -f /tmp/mihomo.log gives you the same output you saw in the foreground.
When a GUI client is the better tool
Driving the core by hand pays off when you are debugging a configuration, scripting a setup, or want to watch every rule match scroll by. For everyday desktop use it is extra work: Clash Verge Rev bundles the same mihomo core with subscription management, node switching, a system proxy toggle and TUN mode, all of which you would otherwise assemble yourself. Plenty of people keep both — the graphical client for daily traffic, the bare binary for validating a config with mihomo -t before importing it anywhere. If you want the always-on version of this setup, the Linux server deployment guide uses the same binary and flags with systemd in place of launchd.