Mihomo Dashboard, The Official One, XD
- 📊 Real-time traffic monitoring and statistics
- 🔄 Proxy group management with latency testing
- 📡 Connection tracking and management
- 📋 Rule viewer with search functionality
- 📝 Live log streaming
- 🎨 Beautiful UI with light/dark theme support
- 📱 Fully responsive design for mobile devices
- 🌐 Multi-language support (English, 中文, Русский)
| Platform | URL |
|---|---|
| GitHub Pages | https://metacubex.github.io/metacubexd |
| Cloudflare Pages | https://metacubexd.pages.dev |
metacubexd ships in three forms from one codebase:
| Form | Who hosts the UI | Who runs the kernel | Best for |
|---|---|---|---|
| Hosted panel (classic) | gh-pages / your static host | your own remote mihomo | pointing a browser at an existing mihomo |
| Desktop app | the app (bundled) | the app supervises a bundled mihomo | a single machine, zero setup |
| All-in-one server | the Docker image | the container's bundled mihomo | a router / NAS / VPS |
The classic pure-panel mode still works against any remote mihomo — the kernel-control and profile features simply stay hidden when the dashboard isn't talking to a bundled agent.
Enable the external-controller in your mihomo config.yaml:
external-controller: 0.0.0.0:9090Then either open the hosted dashboard and enter your mihomo {url, secret}:
| Platform | URL |
|---|---|
| GitHub Pages | https://metacubex.github.io/metacubexd |
| Custom domain | https://d.metacubex.one |
…or self-host the static assets via external-ui:
# Clone the prebuilt gh-pages branch
git clone https://github.com/metacubex/metacubexd.git -b gh-pages /etc/mihomo/ui
# Set external-ui in your config:
# external-ui: /etc/mihomo/ui
# Update to the latest build later:
git -C /etc/mihomo/ui pull -rDownload the installer for your platform from the latest release:
| OS | Arch | File |
|---|---|---|
| macOS (Apple Silicon) | arm64 | MetaCubeXD-<version>-mac-arm64.dmg |
| macOS (Intel) | x64 | MetaCubeXD-<version>-mac-x64.dmg |
| Windows | x64 / arm64 | MetaCubeXD-<version>-win-<arch>.exe |
| Linux | x64 / arm64 | MetaCubeXD-<version>-linux-<arch>.AppImage or .deb |
The desktop app bundles its own mihomo kernel and auto-configures the local endpoint — you don't enter any address. Manage profiles, edit configs, and start/stop the kernel directly inside the app.
These builds are unsigned. Each OS warns about unidentified developers; the steps below are expected and safe.
macOS — Gatekeeper blocks unsigned apps ("MetaCubeXD is damaged and can't
be opened"). After dragging the app to /Applications, strip the quarantine
attribute:
xattr -dr com.apple.quarantine /Applications/MetaCubeXD.app…or right-click the app → Open → Open in the confirmation dialog.
Windows — SmartScreen shows "Windows protected your PC". Click More info → Run anyway.
Linux — make the AppImage executable, then run it:
chmod +x MetaCubeXD-*-linux-*.AppImage
./MetaCubeXD-*-linux-*.AppImage
# or install the .deb:
sudo dpkg -i MetaCubeXD-*-linux-*.debSystem proxy / TUN are advanced features. The app defaults to a mixed proxy port (no elevation). System proxy and TUN both require privileges; see TUN mode (desktop) for the one-time privileged-helper install and what the unsigned build means for it.
TUN takes over all machine traffic at the network layer, so every app is routed through mihomo without per-app proxy settings (unlike the mixed proxy port, which only catches apps you point at it). Because routing the whole system needs root/admin, the desktop app installs a small privileged helper the first time you enable TUN — a background service that performs the privileged network setup on the app's behalf. Enabling TUN therefore prompts for an administrator authorization the first time (the OS elevation dialog). The helper persists across sessions, so later enables don't re-prompt for install.
These builds are unsigned, so you hit the unidentified-developer warnings twice: once launching the app (see above — macOS Gatekeeper / Windows SmartScreen / Linux "unknown publisher"), and again when the helper install asks for administrator authorization. Both are expected. Allow them the same way you allowed the app itself.
Per-OS notes
- macOS — the helper is registered as a root LaunchDaemon; you approve
it through the standard
osascript"wants to make changes" administrator prompt. The kernel binds the system'sutuninterface. - Windows — the helper is registered as an auto-start Windows service
(UAC prompt). The build ships
wintun.dllalongside the kernel, which mihomo's wintun backend needs — no separate download. - Linux — the helper is registered as a root systemd unit; the elevation
prompt is
pkexec(the GNOME/PolicyKit authorization dialog). On a headless box without a Polkit agent you may need to install/enable one.
If TUN takes down your network
TUN reroutes default traffic, so a bad config or a stuck tunnel can drop connectivity. Two recoveries:
- Click "Recover network" in the app — it tears down the TUN routing and restores normal networking without quitting.
- Quit the app — exiting automatically tears down TUN (the helper restores routing), so a force-quit is a safe last resort.
Status: TUN is new. It's verified at the unit-test / command-generation / packaging level, but the real install, elevation, and per-OS tunnel behavior still need smoke testing on actual macOS / Windows / Linux machines. If you hit a problem, please open an issue with your OS, the exact prompt you saw, and whether "Recover network" restored connectivity.
The metacubexd-server image bundles the dashboard UI, the control agent, and
a per-arch mihomo kernel. One container serves the panel, supervises the
kernel, and exposes the proxy.
# compose.yaml — proxy-only by default; TUN is an advanced override
services:
metacubexd:
image: ghcr.io/metacubex/metacubexd-server:latest
restart: unless-stopped
environment:
CONTROL_TOKEN: 'change-me-control'
CLASH_SECRET: 'change-me-clash'
CONTROL_PORT: '8080'
CLASH_API_PORT: '9090'
MIXED_PORT: '7890'
TZ: 'Asia/Shanghai'
ports:
- '8080:8080' # dashboard UI + /api/control agent API
- '9090:9090' # mihomo Clash API + WebSocket (UI endpoint target)
- '7890:7890' # mixed proxy port
volumes:
- 'metacubexd-data:/data'
volumes:
metacubexd-data: {}docker compose up -d
# Update
docker compose pull && docker compose up -dOpen http://<host>:8080 for the dashboard. The control agent unlocks the
kernel/profile UI automatically: the server injects CONTROL_TOKEN into the
same-origin page, so the dashboard authenticates its /api/control probe
without you entering the token anywhere.
Point the UI endpoint at the kernel. The dashboard talks to mihomo's Clash API directly (never proxied), so set the endpoint to:
| Field | Value |
|---|---|
| URL | http://<host>:9090 |
| Secret | the CLASH_SECRET you set above |
| Variable | Default | Purpose |
|---|---|---|
CONTROL_TOKEN |
(none) | Bearer token guarding the control agent (/api/control/**); also accepted as ?token= for the SSE log stream. Set a strong value. |
CLASH_SECRET |
(none) | Secret for mihomo's Clash API (external-controller). Use this as the UI endpoint's Secret. |
CONTROL_PORT |
8080 |
Port serving the dashboard UI + control agent API. |
CLASH_API_PORT |
9090 |
Port for mihomo's Clash API + WebSocket. The UI endpoint targets this port. |
MIXED_PORT |
7890 |
mihomo mixed (HTTP + SOCKS) proxy port. |
TZ |
(container default) | Timezone for logs/scheduling, e.g. Asia/Shanghai. |
The named volume mounts /data, which holds your profiles, the active config,
and the kernel's geo / fake-ip caches. It must be writable — a read-only
data dir makes the kernel exit non-zero.
TUN needs NET_ADMIN, the /dev/net/tun device, and host networking. Override
the service:
services:
metacubexd:
image: ghcr.io/metacubex/metacubexd-server:latest
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
devices:
- '/dev/net/tun:/dev/net/tun'
environment:
CONTROL_TOKEN: 'change-me-control'
CLASH_SECRET: 'change-me-clash'
volumes:
- 'metacubexd-data:/data'
volumes:
metacubexd-data: {}With network_mode: host the ports: mapping is ignored — the container
binds 8080/9090/7890 directly on the host. Enable a tun: block in your
profile's mihomo config for the tunnel to come up.
ghcr.io/metacubex/metacubexd is a dashboard-only image — it serves the
UI over plain HTTP on port 80 and you point it at a mihomo you run yourself
(often a separate mihomo container). One panel container can drive several
mihomo backends, and because it speaks HTTP there is no HTTPS mixed-content block
like the hosted gh-pages page hits when you aim it at a local backend.
docker run -d --name metacubexd -p 80:80 ghcr.io/metacubex/metacubexd:latestThis image was briefly frozen during the monorepo migration; it is published again, so
docker pull ghcr.io/metacubex/metacubexd:latestresumes updating. Add your dashboard origin toexternal-controller-cors(see CORS).
Prefer one container that runs everything? Use
metacubexd-server instead — it bundles its own
mihomo. When porting an old standalone compose.yaml to it, the old
# Optional: mihomo instance service is no longer needed (delete it), the
dashboard moves from port 80 to 8080 (map '80:8080' to keep port 80), and
the image name becomes ghcr.io/metacubex/metacubexd-server:latest.
When connected to a bundled agent (desktop app or the server image), the dashboard gains a profile manager:
- Create, duplicate, rename, and delete multiple profiles.
- Import a subscription by URL (fetched with
User-Agent: clash.meta); theupload / download / total / expireusage fromSubscription-Userinfois shown on a usage card. - Edit any profile in an in-browser Monaco editor with mihomo YAML schema completion and validation.
- Activate a profile to validate it and hot-reload the kernel.
Schema diagnostics are advisory and never block saving — the kernel's reload is the final validation. A "disable validation" toggle is available for bleeding-edge mihomo keys the bundled schema doesn't know yet.
Both the desktop app and the server bundle a pinned mihomo
(v1.19.27). To use your own kernel build, set a custom path in
Settings (desktop) or the MIHOMO_BIN environment variable (server) — a
user-supplied path always takes precedence over the bundled binary.
If the dashboard loads but cannot connect to the Mihomo backend — even though the External Controller is reachable directly in your browser — the cause is usually CORS.
When you host the dashboard on your own address (e.g. http://192.168.1.2:8080),
the browser treats requests to the External Controller (e.g.
http://192.168.1.2:9090) as cross-origin. Mihomo only answers cross-origin
requests from origins listed in its external-controller-cors allow-list, so
requests from your dashboard are rejected and the connection fails.
Add your dashboard's origin to the allow-list in your Mihomo config.yaml:
external-controller-cors:
allow-private-network: true
allow-origins:
- 'http://192.168.1.2:8080' # your dashboard's address
# or, for trusted local networks only:
# - '*'Tip: if you still cannot connect, open your browser's DevTools (F12) → Console and look for CORS-related errors to confirm the cause.
When the bundled kernel fails to reach running:
- Port
9090already in use. Another mihomo (or a previous orphaned instance) is holding the Clash API port. Stop it, or changeCLASH_API_PORT(server) — the app picks a free port and writes the real URL back to the UI. - macOS quarantine kills the bundled binary. An unsigned
.appdownloaded from the internet has its bundled mihomo quarantined. Runxattr -dr com.apple.quarantine /Applications/MetaCubeXD.app(the app also strips quarantine + sets the executable bit on first run). SIGILL/ "illegal instruction" on amd64. Old or virtualized CPUs choke on the generic amd64 build. metacubexd ships the-compatible(GOAMD64=v1) asset specifically to avoid this; if you supplied a custom kernel path, use the-compatiblemihomo build.
- Clash logs (the existing Logs page): mihomo's own log feed over the Clash API WebSocket — proxy decisions, rule matches, DNS, etc.
- Kernel logs (control agent, SSE): the mihomo subprocess stdout/stderr captured by the supervisor — startup banners, crashes, config-parse errors. Use these when the kernel won't start or exits unexpectedly.
The kernel needs a writable home for profiles/, the active config, and
geo / fake-ip caches. A read-only mount (server) or a read-only
resources/ path (desktop) makes the kernel exit non-zero. On the server,
ensure the /data volume is writable; the desktop app uses the per-user
userData directory automatically.
The container's health endpoint is GET /api/control/health on
CONTROL_PORT (default 8080). A 200 means the dashboard + control agent
are up; it does not assert the kernel is running (check the kernel panel
for that).
This repo is a pnpm 10 workspace (packages/ui, packages/agent,
apps/server, apps/desktop).
# Install all workspace dependencies
pnpm install
# Run the dashboard UI dev server (pure-panel mode against a remote mihomo)
pnpm dev
# Build the static UI for hosting (gh-pages, external-ui, etc.)
pnpm build:ui
# Build the all-in-one Nitro server (UI + agent), output in apps/server/.output
pnpm build:server
# Build the Electron desktop app (electron-vite + electron-builder)
pnpm build:desktop
# Typecheck / lint every package
pnpm typecheck
pnpm lintTo work on the desktop app with hot reload, run electron-vite from its package:
pnpm --filter @metacubexd/desktop dev- Nuxt - The Intuitive Vue Framework
- Vue.js - The Progressive JavaScript Framework
- daisyUI - Tailwind CSS components
- Tailwind CSS - Utility-first CSS framework











