Mihomo Operations Manual

14 min read
On this page

Trace an actual connection: did it enter Mihomo, which domain/IP was available, which rule matched, and which proxy carried it? Files, runtime configuration and existing connections may reflect different moments.

TaskStart here
Config change has no effectValidation and runtime
Wrong proxy selectedFirst match and connection evidence
Transparent gateway outageIngress, DNS and routing

Identify the loaded file and controller

Match configuration to the running instance

# Inspect ExecStart, working directory, and overrides
systemctl cat mihomo.service

Identify the binary and -d/-f paths. For containers/OpenWrt plugins use their manager, not this systemd example against another configuration.

# Check the main process and recent startup result
systemctl status mihomo.service --no-pager

Use journalctl for fuller logs; file existence does not prove this process uses it.

# Check the deployed core version
mihomo -v

Use the same binary as ExecStart; the PATH command may be another version.

# Validate the candidate configuration
mihomo -t -f /opt/mihomo/mihomo_config/config.yaml

Adjust paths/workdir to the deployment, especially relative provider paths. Fix errors first; runtime APIs, subscriptions, and application access still need checking.

The path and 127.0.0.1:9999 are retained deployment examples, not defaults. Inspect ExecStart -d/-f, external-controller and secret. Do not expose an unauthenticated controller publicly. Validation does not establish live provider, DNS or upstream availability.

Load client authentication before querying

Run on a client that can reach the controller, here localhost:9999. Load MIHOMO_SECRET through your controlled secret mechanism or an interactive terminal read. Each request rejects an empty value.

# Read the controller secret without echoing it
read -r -s MIHOMO_SECRET

Press Enter after input. This is an alternative to a secret manager, not an additional mandatory step. Unset the variable after the session.

# Verify controller identity and API access
curl -fsS -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" http://127.0.0.1:9999/version

For 401 inspect the secret; for refusal inspect the listener. Add -i when response headers/status are needed.

# Read runtime mode and inbound configuration
curl -fsS -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" http://127.0.0.1:9999/configs | jq '{mode,port,"mixed-port": .["mixed-port"],tun}' 

Requires jq. Compare mode, mixed-port, and tun; if files differ from runtime, verify controller identity first.

Back up the source and confirm reload paths are allowed by the process working directory/SAFE_PATHS. Runtime PATCH is not persistence to the source file; recheck after restart. A force query parameter is not permission to ignore invalid configuration.

Separate file reload from temporary PATCH

Preserve the old file and validate the new one first. The path below is on the Mihomo host, not the API client, and must satisfy workdir/SAFE_PATHS rules.

# Reload the validated file in the running instance
curl -fsS -i -X PUT "http://127.0.0.1:9999/configs?force=true" \
  -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" \
  -H 'Content-Type: application/json' \
  --data '{"path":"/opt/mihomo/mihomo_config/config.yaml"}' 

Check HTTP status, then GET configs/rules and test new connections. On failure restore the old file and validate/reload through the same flow; force does not bypass errors.

# Change runtime mode only for an intended temporary adjustment
curl -fsS -i -X PATCH http://127.0.0.1:9999/configs \
  -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" \
  -H 'Content-Type: application/json' \
  --data '{"mode":"rule"}' 

This is an alternative task, not mandatory after reload. Record old mode, GET to verify, persist through managed source configuration if needed, and PATCH the recorded value to undo.

A 204 completes the request; inspect runtime config, rules and new connections. Reload can reconfigure listeners/tunnels and is not a guarantee of uninterrupted existing sessions.

The first matching rule selects the action

Preparing the visual
Does an exception precede the broader rule?

Rules use the first match. api.example.com matches both exact and suffix rules; putting the DIRECT suffix first shadows the REJECT exception.

Correlate active rules with connections

# Read rules and their runtime order
curl -fsS -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" http://127.0.0.1:9999/rules | jq '.rules'

Compare type, payload, and target; first match wins. If this differs from the edited file, inspect reload status.

# Find the request and inspect its actual selected chain
curl -fsS -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" http://127.0.0.1:9999/connections |
  jq '.connections[] | {id,source: .metadata.sourceIP,host: .metadata.host,destination: .metadata.destinationIP,port: .metadata.destinationPort,rule,rulePayload,chains}' 

Correlate source, destination, port, and time. Use id for precise actions later. Short-lived connections may disappear; reproduce with logs or a sustained request.

# Read policy-group selection and available nodes
curl -fsS -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" http://127.0.0.1:9999/proxies | jq '.proxies'

Obtain group PROXY and node NODE from the response. URL-encode path names with spaces/non-ASCII and escape JSON values correctly.

Change only manually selectable groups

# Select a verified member of a Selector group
curl -fsS -i -X PUT http://127.0.0.1:9999/proxies/PROXY \
  -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" \
  -H 'Content-Type: application/json' \
  --data '{"name":"NODE"}' 

Record old now, GET to verify, and inspect a new connection’s chains. Do not treat automatic groups as Selectors; persistence depends on configuration such as profile.store-selected.

# Close only the just-verified connection
curl -fsS -i -X DELETE -H "Authorization: Bearer ${MIHOMO_SECRET:?Load the controller secret in this shell}" http://127.0.0.1:9999/connections/CONNECTION_ID

Use its ID from connections. This interrupts that request. Verify removal and let the application reconnect; omitting the ID targets all connections.

Distinguish selecting a group from that group selecting a node; inspect chains and now. Domain rules need domain metadata, which can come from DNS mapping or sniffing; redir-host is not sniffing-only. A fake IP identifies a mapping, not the remote server’s public address.

Test new connections because old sessions can retain earlier choices. If disconnection is necessary, select a specific ID. DELETE /connections disconnects everything and is not a routine refresh button.

Test explicit proxy and transparent ingress separately

Test the explicit proxy first

# Make an explicit proxy request on the proxy host
curl -v --connect-timeout 5 --proxy http://127.0.0.1:7890 https://example.com/

Replace 7890 with the runtime port and correlate the connection rule/chains. A remote client’s localhost is not the proxy host.

# Inspect policy routing for transparent traffic
ip rule show

Run on the actual gateway and inspect sources/marks; query table contents separately.

# Inspect proxy, DNS, and controller listeners
ss -lntup

Match ports to configuration; loopback listeners serve only the local host directly.

# Read transparent-interception rules and ownership
sudo nft -a list ruleset

Correlate ingress interfaces, ports, marks, and routing tables. Edit plugin-managed rules through that plugin; do not flush the whole ruleset.

ObservationNext check
Explicit proxy works, LAN failsInspect client gateway/DNS and interception/routing
Request absent from connectionsConfirm listener/process/ingress; short connections may have ended, correlate logs
Correct rule, failed destinationCheck node, protocol, DNS, TLS and service; a delay probe is not universal health

Inspect actual Nikki/TUN/TProxy rules and ports, not a fixed 7891 assumption. Custom fail-open exists only if implemented and direct fallback may violate routing intent. After recovery verify DNS, TCP, UDP and LAN exceptions.

Mihomo API · Routing rules · DNS · TUN