Tutorial · Estimated reading 16 mins

Mihomo External Controller and Yacd:
Remote node switching and secret key hardening

The Mihomo core exposes a small HTTP REST API on the external-controller port so dashboards such as Yacd can list proxies, flip select groups, and inspect traffic without editing YAML by hand. That convenience becomes a liability the moment the API listens beyond 127.0.0.1 without a strong secret, packet filtering, or transport encryption. This guide walks through the configuration grammar, how Yacd authenticates, and practical patterns—localhost-only dashboards, LAN tablets, and SSH tunnels—for keeping the web panel useful without handing your network to strangers.

Mihomo · external-controller · Yacd · secret · Web UI · API

1 Why bother with an external controller when clients already ship GUIs?

Desktop Clash-compatible apps bundle profile editors, tray menus, and sometimes embedded dashboards, yet many power users still enable the standalone external-controller endpoint. Headless Mihomo deployments on Linux servers or routers have no native windowing stack; a browser-based web panel becomes the ergonomic way to switch nodes after you SSH in. Even on workstations, separating the data plane (the running core) from the control plane (a tab in Chrome or Firefox) lets you bookmark one URL, share instructions with housemates who should not touch raw YAML, or operate the same core from a phone on the same Wi-Fi.

The trade-off is exposure. Unlike a SOCKS port that merely forwards bytes when applications opt in, the management API can query configuration, change policy groups, and—depending on build flags and modules—surface enough detail to fingerprint your traffic mix. Treat external-controller like remote administration: convenient on a trusted LAN, dangerous when it faces the public internet with a guessable password or no password at all. The rest of this article assumes you want that convenience while keeping the blast radius small.

If you already run Mihomo under systemd, our Linux Mihomo and systemd guide shows a minimal YAML that already includes external-controller and secret; return here for the browser side and security layering.

2 external-controller: bind address, port, and IPv6

In Mihomo’s Clash-compatible schema, external-controller accepts a string of the form host:port. The host controls which network interfaces accept inbound connections. Binding to 127.0.0.1:9090 limits access to programs on the same machine—ideal when you only open Yacd locally. Binding to 0.0.0.0:9090 listens on all IPv4 interfaces, which includes Ethernet, Wi-Fi, and tunnel adapters; that is the pattern people use when a phone or another PC on the LAN should reach the dashboard without SSH, but it also means any device that can route to your machine may attempt connections unless a firewall drops them first.

IPv6-first networks sometimes set [::]:9090 or a specific global address. Be explicit about scope: link-local, ULA, and global IPv6 addresses behave differently across VLANs. If you do not need remote IPv6 exposure, prefer loopback or a single LAN address plus host firewall rules rather than opening the API on every interface “just in case.” Port choice is arbitrary as long as nothing else binds there; 9090 is a community convention borrowed from earlier Clash releases, not a mandate.

YAML (illustrative)
# Local-only management API (recommended baseline)
external-controller: 127.0.0.1:9090
secret: "replace_with_a_long_random_string"

# LAN-wide dashboard (only with firewall + strong secret)
# external-controller: 0.0.0.0:9090
# secret: "replace_with_a_long_random_string"
Prefer loopback first: Start with 127.0.0.1, confirm Yacd works, then widen the bind only after you know which remote path (LAN IP, SSH tunnel, or reverse proxy) you actually need.

3 secret: shared key, Bearer header, and empty-string pitfalls

The secret field sets a shared token between Mihomo and any client that calls the REST API, including Yacd. When secret is non-empty, compliant clients attach it as an HTTP Authorization: Bearer <secret> header on each request. The core rejects calls that omit the header or supply the wrong value, which stops casual port scanners from retrieving /proxies or flipping your select group from a vanilla browser tab with no credentials.

An empty secret—or leaving the key at a factory default you copied from a tutorial—defeats the purpose. Anyone who can reach the TCP port can issue API calls. Generate a long random string: password managers and openssl rand -hex 32 both work. Store it only in the YAML (with restrictive file permissions) and in your dashboard client; avoid checking real secrets into public repositories. Rotate the token if you suspect exposure, and restart Mihomo so the new value loads.

Remember that TLS termination matters separately from Bearer tokens. HTTP Basic-style secrets protect against unauthenticated callers, but they ride in cleartext on the wire unless you wrap the service with HTTPS through a reverse proxy or tunnel SSH’s encrypted channel. We cover those patterns in the next sections.

Optional: CORS and browser-hosted Yacd

When you open a third-party hosted instance of Yacd in the browser and point it at http://127.0.0.1:9090, modern browsers enforce same-origin rules; Mihomo may need external-controller-cors allowances depending on version and how you host the UI. Many users instead run Yacd locally or use the official static bundle served from file:// or a local static server to avoid cross-origin surprises. If requests fail with CORS errors in the developer console, consult the Mihomo release notes for your exact version rather than blindly disabling browser security.

4 Connecting Yacd to Mihomo step by step

Yacd (Yet Another Clash Dashboard) is a lightweight single-page app that speaks the same REST surface as mainstream Clash cores. After Mihomo is running with external-controller listening where you expect, open Yacd—in a desktop browser or a self-hosted copy—and locate the connection panel. Enter the base URL of the API, typically http://127.0.0.1:9090 for local-only binds or http://192.168.x.x:9090 when you intentionally listen on the LAN. Paste the identical string you placed in secret into the dashboard’s secret or token field; Yacd will attach it as a Bearer token on subsequent requests.

Once authenticated, you should see proxy groups, outbound nodes, and live throughput charts depending on build features. Use select groups to hop between servers without reloading configuration files. If you rely on url-test or fallback automation, our URL-Test and Fallback guide explains how those groups behave when latency shifts; the dashboard reflects the active candidate but does not replace thoughtful YAML design.

Verify connectivity with a quick command-line probe before blaming the UI. From the same host, curl -H "Authorization: Bearer YOUR_SECRET" http://127.0.0.1:9090/version should return version JSON when the secret matches. If curl works but Yacd does not, double-check mixed-content rules (HTTPS page talking to HTTP API), typos in the base URL, or browser extensions blocking localhost requests.

Phone browsers on cellular data: Typing your home public IP into Yacd without a VPN or tunnel usually fails—your ISP may use CGNAT, and even with port forwarding you should not expose raw external-controller to the open internet. Use an SSH tunnel or a proper VPN back into the LAN instead.

5 Remote and LAN access patterns that scale down risk

LAN tablets and laptops are the simplest case: bind external-controller to your machine’s RFC1918 address or to 0.0.0.0 and restrict who joins the Wi-Fi. Still set a strong secret because guests, IoT gadgets, and compromised phones may share the same broadcast domain. On many home routers you can also place management devices on a separate SSID or VLAN; if routing between VLANs is disabled, the API never leaves the admin network.

SSH local port forwarding is the workhorse for remote administration without publishing port 9090 globally. From your laptop, run ssh -L 9090:127.0.0.1:9090 user@home-server, keep Mihomo bound to loopback on the server, and point Yacd at http://127.0.0.1:9090 on the laptop. The SSH session encrypts traffic and authenticates you via host keys and credentials; the API never faces the raw internet. This pattern mirrors how database administrators reach MySQL through tunnels instead of opening 3306 to the world.

Reverse proxies with TLS certificates (Caddy, nginx, Traefik) let you present https://dash.example.com while upstream still speaks HTTP to 127.0.0.1:9090. Terminate TLS at the proxy, enforce mutual TLS or OAuth at the edge if you need more than a shared Bearer token, and rate-limit aggressive clients. This adds moving parts—certificate renewal, logging, and access control—but is appropriate when multiple operators need a bookmarkable HTTPS URL on an internal domain.

VPN overlay networks (WireGuard, Tailscale, corporate VPN) collapse distance without port-forwarding your home router. Once the VPN interface is up, treat peers like LAN members: you might still bind the controller to a VPN IP only, further shrinking exposure compared with 0.0.0.0 on every NIC.

6 Hardening checklist before you call it production-ready

  • Bind intentionally: Default to 127.0.0.1; expand only with a documented reason.
  • Secret entropy: Use a long random secret; never ship examples verbatim.
  • Host firewall: On Linux, nftables, iptables, or ufw should allow 9090 only from trusted subnets if you must listen broadly.
  • Cloud security groups: If Mihomo runs on a VPS, do not open the management port to 0.0.0.0/0; pair SSH tunnels or VPN with private networking.
  • TLS where users roam untrusted networks: Prefer SSH or HTTPS fronts when the control channel crosses the internet.
  • Monitor logs: Unexpected API chatter may indicate scanning; correlate with firewall drops.
  • Least privilege on disk: Config files containing secret and proxy credentials should use restrictive POSIX permissions, as highlighted in server-oriented guides.
Never expose an unauthenticated controller: If you temporarily blank secret for debugging, bind to loopback only and revert before reconnecting wider networks. An open API on a public IP is effectively remote code–level control over routing policy.

7 What Yacd can and cannot replace

Dashboards excel at operational tasks: picking a node inside a select group, toggling modes where the API exposes them, watching live connections, and sometimes reloading external resources when the core supports hot reload. They are weaker at authoring large rule sets, merging subscriptions, or reviewing Git diffs of YAML—use your editor or a dedicated client feature like mixin overlays for that workflow.

When you need automated selection based on latency or ordered failover, express that logic in proxy-groups rather than clicking manually every hour. The dashboard reflects the outcome but does not substitute for declarative policy. Likewise, DNS architecture and TUN routing remain YAML- or profile-level concerns; fixing a misconfigured dns block through the API is not the durable fix.

8 Troubleshooting common connection failures

  • Connection refused: Mihomo is not running, listens on a different port, or binds to another address than the URL you typed.
  • 401 or unauthorized responses: Mismatched secret, missing Authorization header, or stale browser cache of old credentials.
  • Works from curl but not Yacd: Check CORS, HTTPS mixed content, or browser extensions blocking localhost calls.
  • LAN works, remote does not: Likely missing VPN/tunnel, or upstream NAT without port forwarding—by design if you follow safe defaults.
  • Intermittent timeouts: Wi-Fi power saving on phones, aggressive laptop firewalls, or two processes fighting for the same port after an unclean restart.

9 Wrap-up

The Mihomo external-controller turns a headless core into something you can steer from a browser, and Yacd remains a popular web panel for that job because it stays focused on Clash’s REST vocabulary. Pair a deliberate bind address with a high-entropy secret, add network controls when you leave loopback, and prefer encrypted tunnels or TLS fronts whenever the control channel crosses untrusted paths. Compared with editing cold YAML on a phone SSH session, the dashboard wins on clarity; compared with leaving port 9090 world-readable, disciplined configuration wins on safety.

Graphical clients still deliver the smoothest onboarding for everyday desktop users: integrated updates, subscription import, and tray shortcuts complement—not replace—what you can do from a browser tab. When you want that integrated experience on Windows, macOS, or Linux, grab a maintained build from our downloads hub instead of assembling every component by hand.

→ Download Clash for free and experience the difference

Tags: Mihomo external-controller Yacd secret Clash API Web dashboard
Clash client logo for Mihomo dashboard readers

Clash Verge Rev

Next-gen Clash client · Free and open source

Built on the Mihomo core with a full GUI, subscription import, and optional TUN mode—use a polished client when you want profiles and dashboards in one place instead of wiring Yacd to a raw core by hand.

TUN full traffic takeover Mihomo high-performance core Precise rule routing Built-in profile UI Multi-subscription management

Related reading