What the external-controller actually is
In Mihomo-family cores, external-controller starts an HTTP listener that speaks the same REST surface Clash users have relied on for years: version probes, configuration dumps, proxy group lists, selective traffic control, connection tables, and rule-provider reload hooks. Graphical clients embed a native UI on top of that API; Yacd is simply a static web front-end that calls the same JSON endpoints from your browser. Nothing about the dashboard replaces the core—if Mihomo is not running, or the API is bound to an address your browser cannot route to, Yacd will show spinners or authentication errors no matter how pretty the CSS is.
Think of the controller as an admin socket. It is more sensitive than the ordinary mixed-port HTTP/SOCKS listener because a client that can reach the API can change outbound policy without touching system proxy settings. That is exactly why production setups pair a non-default port, a long random secret, and often loopback-only binding unless there is a deliberate LAN operations story. The mental model matters: opening Yacd does not “enable” Mihomo; it only visualizes an API you must explicitly expose in YAML (or in a GUI that writes equivalent YAML under the hood).
Minimal YAML: port, bind address, and secret
A conservative desktop baseline keeps the API on loopback and picks a port that does not collide with your mixed or SOCKS listeners. The following fragment is illustrative—merge it into your real profile and restart or hot-reload according to how your distribution handles API changes:
# Management API (REST)
external-controller: 127.0.0.1:9090
secret: "replace-with-long-random-string"
# Example data plane listener (must differ from 9090 unless you enjoy pain)
mixed-port: 7890
The address before the colon controls who may initiate TCP toward the API. 127.0.0.1 limits callers to software on the same operating-system network stack as Mihomo, which is perfect when Yacd runs as a local browser tab on the same PC. 0.0.0.0 listens on all interfaces, which is how phones or tablets on your Wi-Fi can reach a Mihomo instance on a home server—at the cost of widening exposure to every device that can route to that port. Some packaged clients hide these fields behind toggles labeled “Allow LAN” or “External controller”; underneath, they are still writing the same keys.
Security: Treat an API reachable from LAN or WAN like remote root on your egress path. Use firewall source restrictions, VPN overlays, or SSH port forwarding before you publish 0.0.0.0 on untrusted networks.
Localhost access versus LAN dashboards
When Yacd loads in Chrome on the same machine that runs Mihomo, your API base URL is usually http://127.0.0.1:9090 (swap the port if you changed it). Browsers treat that as a same-origin-friendly loopback destination: no Wi-Fi hop, no NAT hairpins, no confusion about which NIC owns the address. If you instead type the machine’s LAN IP, you are implicitly requiring Mihomo to bind something other than pure loopback—either 0.0.0.0:9090 or the specific RFC1918 address of the interface. Many newcomers observe “mixed port works from my phone as a proxy, but Yacd on the phone cannot talk to the API” because the YAML still says 127.0.0.1:9090, which from the phone’s perspective is the phone itself, not the server.
Docker and VM setups add one more indirection: the browser might run on the host while Mihomo listens inside a container namespace. Published compose ports remap container 9090 to a host port; your Yacd URL must target whichever side your browser can see. Our Docker port-mapping guide walks through that split in detail; the short version is “curl the same URL the browser will use, from the same network namespace.”
1Verify the API with curl before opening Yacd
Dashboard debugging is faster when you already know the API answers. From the same environment where the browser will run, execute:
curl -sS -H "Authorization: Bearer YOUR_SECRET" http://127.0.0.1:9090/version
Replace YOUR_SECRET with the literal string from your YAML (some builds also accept the token in alternative header shapes, but Bearer is widely supported). A JSON payload with version fields confirms the listener, the port, and the secret alignment. If curl times out, Yacd will fail too—fix binding, firewall, or process crashes first. If curl returns 401 or 403, rotate the secret in YAML and reload; partial typos are more common than exotic CORS bugs at this stage.
When testing from another host on the LAN, substitute the server’s IP and ensure the controller is not loopback-only. If HTTPS reverse proxies sit in front of the API, match the scheme Yacd will use; mismatched http versus https produces confusing browser errors because the fetch layer fails before Mihomo ever parses your request.
2Open Yacd and attach it to your controller
Yacd ships as a static bundle; most users load a hosted build in the browser, while privacy-focused readers self-host the files beside their internal wiki. After the page loads, locate the controller URL field (wording varies by release) and enter the base endpoint without trailing path noise—typically http://127.0.0.1:9090 for local setups. Paste the same secret you configured; the UI stores it in browser local storage on many builds, which is convenient on a trusted personal machine and risky on a shared lab terminal.
Once authenticated, you should see proxy groups mirroring your YAML, live connection rows, and log streams depending on build features. Use the connections view to confirm traffic is hitting the expected policy: when DNS or rule order is wrong, the dashboard becomes the fastest way to notice unexpected destinations before you reach for packet captures. For resolver-heavy profiles, cross-check behavior with our Meta core DNS leak prevention guide so what you see in Yacd matches the DNS path you intended.
Tip: If the UI loads but data never appears, open developer tools, watch the network tab for failed fetches to /proxies or /configs, and compare the failing URL with your working curl command.
Subscriptions, providers, and proxy groups from the panel
Yacd excels at operational tasks: picking a different node inside a group, pausing a troublesome outbound, scanning active flows, and triggering reloads when the API exposes those routes. It is not a full replacement for every editorial workflow you might do in VS Code. Bulk edits to massive rule files, custom script providers, and exotic tun snippets still belong in version-controlled YAML or in a desktop client that manages files for you. Expect the panel to reflect whatever Mihomo already loaded; if a subscription URL is wrong in the file, the dashboard cannot magically fetch it until you fix the upstream link or conversion pipeline.
When providers hand you non-Clash links, convert them with tooling you control—our Subconverter guide for Clash YAML outlines safer patterns than pasting secrets into random web forms. After conversion, drop the resulting stanza into your profile, reload Mihomo, and use Yacd to confirm the new outbound list appears with sane latency tests.
Why Yacd “cannot connect” even when the proxy works
Wrong port or split configs
It is easy to point Yacd at 7890 because that is the number you memorized for browsers. The management API is a different socket; verify the numeric tail in external-controller matches the URL field in Yacd. GUI wrappers sometimes generate temporary files—confirm the running process actually read the profile you edited.
Loopback binding from a remote browser
Any URL containing 127.0.0.1 targets the device running the browser, not your NAS. For remote dashboards use the host’s LAN IP or a DNS name, and bind Mihomo accordingly, then verify with curl from that remote device.
OS firewalls and security suites
Windows Defender, macOS application firewalls, and corporate endpoint agents may allow the data-plane mixed port while blocking inbound connections to the controller port. Temporarily relax rules for controlled tests, then tighten them around specific executables or source subnets.
Secret mismatch and copy-paste artifacts
Quotes in YAML, trailing spaces, or UTF-8 smart quotes from chat apps break authentication silently. Regenerate a short alphanumeric secret, reload, and retry curl before reopening Yacd.
CORS anxiety versus real network failures
Modern Yacd builds usually expect direct browser-to-controller access or a same-origin proxy. If you front Mihomo with another domain, you may need to align CORS headers or terminate TLS consistently. When in doubt, curl from the same host isolates transport problems from browser policy problems.
Headless cores and graphical clients on the same network
Homelab readers often run Mihomo on a Raspberry Pi or NAS while browsing from a laptop. That pattern works well when the controller listens on 0.0.0.0:9090 (or a LAN-specific IP) and the laptop’s Yacd points at http://192.168.x.y:9090 with the shared secret. Combine that with published mixed ports if you also want phones to use the same instance as a hop. If the thought of exposing the API makes you uneasy, keep the controller on loopback and use SSH local forwarding: ssh -L 9090:127.0.0.1:9090 user@nas lets the laptop browser keep using http://127.0.0.1:9090 while traffic rides encrypted over SSH to the remote Mihomo.
For full desktop integration—tray icons, auto-start, and integrated updates—many users still prefer a packaged Clash client that embeds its own dashboard. The API concepts in this article transfer directly: whatever the GUI shows is ultimately the same REST vocabulary. When you want installers curated per platform rather than stitching together raw binaries, our official download page remains the straightforward entry point, while upstream GitHub repositories stay useful for reading release notes and verifying checksums without treating them as the primary installer channel.
Going deeper: REST paths and automation
Power users eventually script against the API with curl, Ansible, or small Go utilities to flip groups during CI jobs. The same endpoints Yacd calls are documented in community references for Clash-compatible controllers; stable automation favors explicit version pinning and idempotent reload commands over clicking in a UI. Keep secrets out of shell history where possible—environment variables or minimal ACL’d config files beat pasting tokens into team chat logs.
For vocabulary that spans GUI and headless installs—rules, parsers, DNS semantics—keep our configuration documentation open while you iterate. The dashboard is a lens; the YAML (or exported profile) remains the contract that defines how traffic behaves under load.
Summary
Wiring Yacd to Mihomo is mostly about getting three facts aligned: the external-controller bind address matches where your browser runs, the port matches what you type into the dashboard, and the secret matches on both sides. Verify with curl first, then enjoy live connection visibility and quicker node switches without living inside a text editor. Compared with ad-hoc YAML edits, a stable API plus a solid panel reduces mistakes during day-to-day operations—especially when you are already juggling containers, TUN interfaces, or split DNS setups on the same machine.
When you want that experience bundled with polished first-run flows and update channels, a maintained desktop or mobile client is the natural next step—