mihomo Configuration Reference
YAML structure, ports, DNS, proxies, and routing rules. Check where each field belongs, then verify references and runtime behavior.
This page is for users who can already start the client and want to understand how its configuration works. For installation, subscription import, and connection checks, start with the Getting Started guide. Return here to look up specific fields, override order, or differences in how services run. Examples assume mihomo runs directly on Linux; GUI clients may add their own port, permission, and override settings.
YAML Structure & Configuration Files
Mappings, Lists & Indentation
config.yaml is a hierarchical configuration file, not a script that runs line by line. Top-level keys such as dns and tun are mappings with their own fields; proxies, proxy-groups, and rules are usually lists, with each entry marked by a hyphen. Indentation determines which fields belong together. Use two spaces consistently and never mix in tabs. A field can be spelled correctly but still fail to parse—or simply have no effect—if it is nested under the wrong parent.
Leave a space after each colon. Write booleans as true or false, and use numbers for ports and intervals. Quote passwords, names with special characters, and text that could be interpreted as a date. In a plain scalar, a hash may start a comment, so passwords containing one must be quoted. Double-quoted strings interpret backslash escapes; single quotes are a better fit when backslashes should remain literal. Don’t wrap everything in double quotes by default, or file paths may be interpreted as escape sequences.
Don’t declare the same key more than once in a mapping. Adding another dns block lower in the file does not mean the two blocks will be merged. Parsers may reject duplicate keys or keep only one, so don’t rely on either behavior. To add rules, append them to the existing rules list instead of creating a second list with the same name. YAML anchors can reduce duplication, but they are not a client override mechanism and don’t replace checking the final configuration.
Start with a Minimal, Testable Configuration
This minimal configuration allows direct connections only. It verifies the file path, parser, and listening port without relying on a subscription or proxy server. TUN is disabled, and both the controller and mixed port are explicitly bound to localhost. A successful test confirms that the local proxy entry point works; it does not mean a proxy connection is available. Keep this file as a troubleshooting baseline when adding proxies. If a more complex configuration fails to start, it helps narrow the problem to the binary, system environment, or configuration.
mixed-port: 7890
bind-address: 127.0.0.1
allow-lan: false
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
tun:
enable: false
rules:
- MATCH,DIRECT
When running the core directly, specify the working directory and config file explicitly so that launching it from different terminals doesn’t select different files. In the commands below, -d sets the data directory, -f sets the config path, and -t tests the configuration. The directory may also contain caches, downloaded provider files, and geodata; these are not all part of the configuration itself. Relative paths are usually resolved from the core’s working directory, not guessed from the terminal’s current directory. Confirm this whenever you move a service.
mkdir -p "$HOME/.config/mihomo"
mihomo -t -d "$HOME/.config/mihomo" \
-f "$HOME/.config/mihomo/config.yaml"
mihomo -d "$HOME/.config/mihomo" \
-f "$HOME/.config/mihomo/config.yaml"
Distinguish Source Files from Runtime Files
A GUI client may keep the original subscription, user overrides, and generated runtime file separately. Changes to a generated file in a cache directory are often overwritten the next time you switch configs or update the subscription. Check the client logs to confirm which path the core actually loads before editing the source. Don’t run the GUI client’s built-in core and a standalone service on the same port. Even if both programs read a file with the same name, they may use different directories, working directories, users, and environment variables.
When a config fails, read the first error and nearby line number rather than guessing from the last line. Missing indentation in an earlier list item can make the parser stop at a later, valid field. After copying an example, check for full-width colons, curly quotes, or invisible spaces. Add one complete section at a time, test, and keep a copy you can restore. To review the file in order, see A Section-by-Section Guide to config.yaml; the final word still comes from the parser on your system.
Listening Ports, Access & Modes
Ports Are How Apps Connect to the Core
port provides an HTTP proxy, socks-port provides a SOCKS proxy, and mixed-port accepts both protocols on one port. For a typical desktop setup, a mixed port is enough; there’s no need to enable all three just to fill in every field. Apps must use the correct protocol and address. Setting an HTTP proxy to the controller port, or pointing an app at a disabled SOCKS port, won’t be fixed just because the core is running.
allow-lan controls whether devices on your local network can connect, while bind-address sets the listening address. For local use, keep the service bound to loopback. Only open it to other devices when needed, and account for the network interface, firewall, and access controls. The bind address is not a universal security setting for every port; in particular, don’t assume the controller or DNS listener follows it. Check the actual listening sockets with system tools after making changes rather than relying on a GUI toggle.
| Setting | Purpose | Local example |
|---|---|---|
mixed-port | Shared HTTP and SOCKS entry point | 7890 |
allow-lan | Allow connections from the local network | false |
bind-address | Proxy listener address | 127.0.0.1 |
external-controller | Controller listener address | 127.0.0.1:9090 |
log-level | Log verbosity | info |
Rule, Global, and Direct Modes
mode: rule routes traffic according to rules and is usually the easiest mode to reason about for everyday use. global sends general traffic through the global policy, while direct is mainly useful for testing direct connections. The mode controls traffic that has already reached the core; it doesn’t make browsers, command-line tools, or background processes use the proxy automatically. After switching to global mode, an app that ignores the system proxy may still connect directly. Check how traffic enters the core before changing more rules.
A GUI client may switch modes immediately through the controller without changing the subscription file. If the old mode returns after a restart, check the client’s saved settings and config-generation logic instead of adding mode to multiple files. Likewise, the selected proxy group may come from a cache rather than the first item in its list. When troubleshooting, compare the file value, GUI value, and actual connection logs; they are separate states.
mixed-port: 7890
allow-lan: false
bind-address: 127.0.0.1
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
secret: "your-controller-secret"
The controller reads status, selects policies, and loads configurations; it is not a browser proxy. If you need controller access, set an access secret and keep its real value on your own machine. Never expose the controller directly to the public internet or include its secret in screenshots, public configs, or support requests. For remote management, use a controlled access channel and review the bind address, authentication, and firewall separately. Choosing an unusual port is not a substitute for access control.
Use Targeted Tests to Rule Out Environment Differences
Check the listening port first, then make one command use the proxy explicitly. Test HTTP and SOCKS separately. With socks5h, the proxy resolves the destination hostname, making it easier to distinguish remote from local DNS resolution. Results vary by target, network, and exit node, so keep the exact error—such as connection refused, handshake failure, or DNS lookup failure. “It won’t open” doesn’t show whether the problem is before or after traffic reaches the proxy.
ss -lntp | grep -E ':(7890|9090)\b'
curl --proxy http://127.0.0.1:7890 \
--connect-timeout 10 --head https://example.com
curl --proxy socks5h://127.0.0.1:7890 \
--connect-timeout 10 --head https://example.com
env | grep -iE '^(http|https|all|no)_proxy='
Environment variables only affect processes that inherit them, and desktop proxy settings aren’t necessarily used by every command-line tool. Uppercase and lowercase variables, as well as NO_PROXY, can all affect the result. To isolate a test, clear variables for that command or pass explicit options instead of changing the whole system permanently. Restore verbose logging when you’re done to avoid exposing excessive destination details. If the port is listening but requests still fail, follow the Troubleshooting steps for the proxy entry point, DNS, rules, and proxies.
DNS Upstreams, Fake IP & Resolution Paths
First, Check Whether DNS Queries Reach the Core
dns.enable enables the built-in resolver but doesn’t change where the system sends DNS queries. A desktop resolver, router-provided address, or browser’s encrypted DNS may continue handling them. DNS settings in the core can take effect only when the system explicitly uses its listening address or a mechanism such as TUN redirects queries into the core. Map the path between the app, system resolver, core, and upstream before choosing an upstream. Otherwise, you may keep changing settings that aren’t being used.
listen sets the DNS listener address; it is separate from the mixed proxy port. This example uses a high port on loopback for isolated testing and doesn’t take over the system’s standard DNS port. System network settings usually accept a server address, not an arbitrary port, so you can’t paste this example directly into a desktop DNS field. To redirect system queries, use an explicit approach such as a local forwarder or TUN hijacking, and check whether a system resolver already occupies the target port.
dns:
enable: true
listen: 127.0.0.1:1053
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- "*.lan"
- "*.local"
default-nameserver:
- 223.5.5.5
nameserver:
- https://dns.alidns.com/dns-query
proxy-server-nameserver:
- 223.5.5.5
Understand the Three DNS Roles
nameserver resolves ordinary destination hostnames. default-nameserver is mainly used to resolve the hostnames of DNS upstreams, so encrypted DNS services can find their own addresses. proxy-server-nameserver resolves proxy server hostnames. Treating these as separate jobs helps avoid a loop where the proxy needs DNS to connect, but DNS depends on a proxy that isn’t connected yet. The public resolvers in the example are only one possible configuration; check reachability on your network and choose an approach that fits your DNS policy.
fake-ip returns synthetic addresses mapped to domain names. When the connection reaches the core, it restores the hostname and applies routing rules. These addresses aren’t the website’s real server addresses, and seeing them doesn’t by itself mean DNS is polluted. The address range used for the mapping must not conflict with your local network. If an app receives a synthetic address but its connection bypasses the core, it may not reach the destination. Fake IP therefore depends on DNS and connection traffic following the same path; a successful lookup alone isn’t enough to validate the whole setup.
fake-ip-filter returns real DNS results for specified domains, often for local network discovery or apps that depend on address behavior. It doesn’t route traffic directly and isn’t a substitute for rules. Connections to filtered domains still follow your routing rules. When troubleshooting local devices, check name resolution, the real address, local network routes, and rules together instead of adding every failing domain to the filter. A broad exception list makes the configuration harder to understand.
DNS Policy and Cache Limits
Use nameserver-policy when certain domains need a specific upstream. Consider respect-rules when DNS upstream connections need to follow routing rules. The latter changes how DNS connections are routed, so also decide how proxy server hostnames will be resolved to avoid a dependency loop. Don’t assume fallback is simply a backup address used only when the primary server fails; it works with filtering to determine the result. For an initial setup, use one clear, testable path and add more complex policies one at a time.
dig @127.0.0.1 -p 1053 example.com A
dig @127.0.0.1 -p 1053 example.com AAAA
resolvectl status
resolvectl query example.com
Querying the core directly and querying the system resolver are two different tests. If the first works but the second doesn’t, check the system’s DNS settings and forwarding first. If both work but the browser doesn’t, check its encrypted DNS, cache, and proxy settings. App caches, system caches, and core mappings may all retain old results after a config change, so identify which layer you’re clearing. Long-lived connections may also keep using the old address; reloading a page doesn’t always create a new connection.
dns.ipv6 and the top-level ipv6 setting apply to different scopes. Changing only one doesn’t guarantee that every app will stop using IPv6. For problems that occur only on dual-stack networks, test A and AAAA responses separately and check the actual connection address and whether the exit supports it. For more targeted checks, see Connected but Can’t Browse? A Troubleshooting Checklist. Make DNS changes for a specific reason; changing upstreams, enhanced mode, and routing all at once makes the result hard to diagnose.
Proxy Fields & Subscription Providers
A Proxy Defines the Connection to an Exit
proxies contains individual proxy definitions. Each entry needs a name that can be referenced and a protocol type, along with the other fields required by that protocol. Names are used in the interface and referenced by groups and rules, so make them unique, stable, and easy to recognize. Similar names don’t mean two proxies are the same; the server address, port, and authentication settings determine the actual connection. After renaming a proxy, update every list that references it. A successful parse of the proxy entry alone isn’t enough.
This Shadowsocks example shows the basic relationships between fields. Its server uses a reserved example domain, and its password is instructional text; replace both with the actual details from your provider before connecting. cipher must match the server’s setting. Don’t change it just because another algorithm seems newer. udp enables the related capability, but it can’t make a server or network that doesn’t support UDP start supporting it. The application protocol, core, server, and network path all need to support it.
proxies:
- name: SS-Example
type: ss
server: proxy.example.com
port: 443
cipher: aes-128-gcm
password: "your-password"
udp: true
Different protocols may require fields such as a password, UUID, TLS name, transport path, or public key. Don’t copy one protocol’s settings unchanged into another. The connection address and TLS verification name may differ; using an IP address doesn’t mean you can omit the certificate name. If you see a certificate error, first check the system clock, server certificate, and name match. Skipping verification is not a general fix. Before sharing logs publicly, remove credentials and sensitive subscription URL parameters, keeping only the protocol structure and relevant error.
Providers Keep Node Updates Separate from the Main Config
Use proxy-providers to keep policies and rules in the main config while updating a changing proxy list from an external file or subscription. Provider names and proxy names are separate levels: a group references a Provider with use, and references specific proxies or other groups with proxies. Declaring a Provider doesn’t automatically route traffic through it. You still need a group and a rule that sends traffic to that group. This separation makes updates easier, but it also means references need to be checked.
proxy-providers:
local-nodes:
type: file
path: ./providers/local-nodes.yaml
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 600
proxy-groups:
- name: PROXY
type: select
use:
- local-nodes
A local Provider file must use the expected format for a proxy collection, such as a top-level proxies list. Relative paths in the main config are resolved from the working directory. The directory must exist, and the service user must be able to read it. Don’t use a complete client config with ports, DNS, and rules as a proxy list. Even if some input formats happen to work, don’t rely on undocumented conversion behavior. If the list is empty, open the actual file and check the read path, format errors, and proxy filters in the logs.
Remote Updates, Health Checks & Error Types
An HTTP Provider also needs a url, a cache path, and an update interval. The update interval and health-check interval control different tasks: the former fetches proxy definitions again; the latter tests existing proxies against a URL. Shortening one doesn’t speed up the other. If the test URL is unreachable, many health checks may fail at once without meaning every proxy has invalid credentials. Verify against an actual service you need instead of deleting proxies based on a single probe.
A subscription update timeout, an unusual HTTP status, a response that isn’t YAML, and insufficient file permissions are different failure cases. First check whether the request got a response, then inspect its contents and the saved file. Subscription URLs may grant access to an account, so restrict permissions on backups and logs and never upload them to a public repository. If updates require an existing proxy, plan a working startup path so a fresh install can fetch its first proxy list. A local cache can help with recovery, but it’s not a reliable update strategy by itself.
A successful update can still change proxy names, region labels, and supported protocols, which may affect group filters and manual selections. Compare the proxy list before and after an update; a successful download doesn’t mean the policy chain is intact. Subscription providers and this site’s client documentation have different roles: a client doesn’t create a working proxy service. To install a client, choose your platform on the client downloads page. For Windows, macOS, Android, or iOS, see Clash Plus; for a Linux GUI, see Clash Verge Rev. For specific update failures, see Subscription Update Failures and Automatic Refresh Settings.
Proxy Group Types, References & Selection State
Groups Connect Rules to Actual Exits
proxy-groups defines exit selection. A rule can point to a group, which then selects a proxy, another group, or a built-in option. This lets rules reference stable names for services instead of needing edits whenever subscription proxies are renamed. A proxy group isn’t just a folder: its type determines how selection works. Adding every proxy to one group doesn’t mean the core will use them all at once. Before choosing a type, decide whether you need manual selection, probe-based selection, ordered failover, or connection distribution.
select lets a user or controller choose an item from the list. url-test chooses a candidate based on test results; fallback checks candidates in order for an available exit; and load-balance distributes connections according to a policy. Automatic selection uses a specific test URL, not a combined measure of every service’s quality. A proxy with a fast probe may still be unable to reach your destination. When reviewing test results, keep the test URL and method separate from the actual service you’re trying to use.
proxy-groups:
- name: AUTO
type: url-test
use:
- local-nodes
url: https://www.gstatic.com/generate_204
interval: 600
tolerance: 50
- name: PROXY
type: select
proxies:
- AUTO
- DIRECT
use:
- local-nodes
This configuration depends on the local-nodes Provider from the previous section. AUTO gets candidates from the Provider. PROXY lets you choose the automatic group, a proxy manually, or direct access. Keeping DIRECT in the group offers an explicit manual choice; it doesn’t guarantee traffic will go direct if a proxy fails. If the requirement is to stop on failure rather than switch exits, remove candidates that don’t meet that requirement and check the full fallback chain. Names like “safe” or “fixed” don’t guarantee how a group behaves.
Name References Form a Directed Graph
Every name in proxies must exist, with matching capitalization and spaces. A group can reference another group, but the references must not form a loop—for example, an entry group that references an automatic group that points back to the entry group. A loop prevents selection from reaching a real proxy and may be rejected during testing or cause problems at runtime. Draw the config as arrows from rules to groups to proxies; every path should end at a specific proxy or a clearly chosen built-in option.
Don’t give a group and a proxy the same name, or name a custom group after a built-in policy. Quoting a complex name can make its string boundary explicit, but it won’t fix a mismatch. After a Provider update removes a proxy, a manual selection may stop working or fall back to another candidate. Check the currently selected item in the client. Cached selection state can help restore a choice after restart, but it can also make reordering the list look like it had no effect. The first item and the currently selected item are not the same thing.
| Type | Selection method | What to watch for |
|---|---|---|
select | Chosen manually or through the controller | Check the currently saved selection |
url-test | Candidates’ probe results for the test URL | Test results don’t cover every service |
fallback | Candidate order and availability | Order affects exit priority |
load-balance | Configured connection distribution policy | Doesn’t combine bandwidth for one connection |
Limit the Impact of Automatic Switching
interval sets how often to run probes. tolerance can reduce frequent switching caused by small differences, but it doesn’t guarantee connection quality. Probing too often adds network requests, especially with many proxies. Existing long-lived connections usually won’t move seamlessly to another exit just because the group selection changes. To test a switch, create a new connection and make sure the app isn’t reusing an existing session. For sign-ins, location-sensitive services, and long-running tasks, decide first whether changing exits is acceptable.
Load balancing usually distributes connections or destinations; it doesn’t split a single download across multiple proxies to combine their bandwidth. Different connections leaving through different exits may trigger session limits on a service. Create groups for specific workloads rather than sending all traffic through one random pool. Services that need a fixed exit can use a dedicated selection group matched earlier in the rules; general browsing can use an automatic group. Fewer groups are easier to maintain, but combining unrelated traffic makes each switch affect more than it should.
If a group is empty, check in order whether the Provider loaded, filters removed all candidates, references point to the right Provider, and protocol support excluded any proxies. If candidates are available but a service fails, choose one proxy manually, start a new connection, and check the matched rule, selected group, and exit error. Don’t change the group type, test URL, and proxy filters at the same time; you won’t know which change mattered. A well-maintained group has a clear purpose, an explainable selection method, and an explicit failure policy.
Rule Order, Matching Syntax & Rule Providers
Rules Run Top to Bottom; First Match Wins
rules is an ordered list, usually evaluated from top to bottom. Once a rule matches, its specified policy is used. The position of a rule directly affects the result; a list of keywords won’t automatically make the most specific rule win. A common layout puts explicit exceptions first, service domains and rule providers in the middle, broader address checks later, and MATCH at the end. If the catch-all rule comes first, later rules generally won’t be reached.
DOMAIN matches one exact domain, DOMAIN-SUFFIX matches the domain and its subdomains, and DOMAIN-KEYWORD matches a string. Keyword rules can match more than expected, so use them cautiously; a short word is not a reliable boundary for a service. Standard domain rules don’t include URL paths, so they can’t distinguish directories on the same domain. The destination information available on encrypted connections also depends on app behavior, DNS routing, and sniffing settings. Don’t assume every connection exposes a full URL.
rules:
- DOMAIN,example.com,DIRECT
- DOMAIN-SUFFIX,example.org,PROXY
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- IP-CIDR6,::1/128,DIRECT,no-resolve
- IP-CIDR6,fc00::/7,DIRECT,no-resolve
- MATCH,PROXY
This example relies on the PROXY group defined earlier; its domain is only there to show the match scope. A rule that sends private addresses direct works for common local networks, but check your actual routes to see whether it should include tunnel, container, or enterprise subnets. no-resolve means this address rule won’t actively resolve the destination hostname; it doesn’t disable DNS. If the connection already has a destination IP, the rule can still match by address. Removing the option may add a DNS dependency, so understand why before changing it.
Match the Rule Provider Format to Its Contents
rule-providers stores larger rule collections separately, and the main rules reference them with RULE-SET. A Provider’s behavior identifies whether its entries are domains, IP addresses, or legacy rules; format describes how the file is encoded. They are different settings. Declaring a domain set but supplying full rule lines, or declaring a text file but supplying YAML with a top-level mapping, can cause loading failures or unexpected matches. Check both the declaration and the downloaded content.
rule-providers:
business-domains:
type: file
behavior: classical
format: yaml
path: ./rules/business.yaml
rules:
- RULE-SET,business-domains,PROXY
- MATCH,DIRECT
The corresponding business.yaml file looks like this. Entries in a rule set describe match conditions; the RULE-SET line in the main file determines which exit to use. This lets the same match set use different policies in different deployments without changing the rule file. Give local and remote files separate paths so an update cannot overwrite your manual edits. If the file exists but nothing matches, also check where it appears in the main rules; an earlier, broader rule may be matching first.
payload:
- DOMAIN-SUFFIX,example.org
- DOMAIN,api.example.net
Verify Matches Before Changing the Set
A failed update of a remote rule set may not immediately change current behavior because the core may still have a cached copy. On a first run, there may be no cache, and a failed set could affect config loading or matching. Check the Provider status and error logs rather than relying on the local file’s modification time. For geodata-based rules, confirm that the required data files are available, supported, and covered by an update mechanism. Valid rule syntax doesn’t guarantee the external data is ready.
Process rules on Linux may require process discovery and appropriate permissions. Containers, forwarded traffic, and programs running as different users may not expose the same process information. If a process rule doesn’t match, first confirm the core can see the process instead of repeatedly changing how its name is spelled. Source address rules also need to distinguish local apps from forwarded LAN traffic. Rules depend on the information the core can actually see; routing or NAT may change the source and invalidate the assumptions behind an existing rule.
When troubleshooting incorrect routing, note the destination domain, destination IP, matched rule, final group, and actual exit. Add a temporary rule with a clearly limited scope at the top of the list, verify it, then move it to the correct position. Don’t delete a large set of rules at once to “restore the network.” Test with a new connection after updating rules and clear relevant caches if needed. A site may use multiple domains, so check each connection rather than fixing only the homepage and missing APIs, static assets, or authentication domains.
TUN Routing, Linux Permissions & Persistent Services
Transparent Proxying Requires More Than the Core
tun.enable enables traffic capture through a virtual network device, auto-route requests the relevant routes, and auto-detect-interface attempts to identify the outbound network interface. These settings control how traffic reaches the core; they don’t configure proxies, DNS, or rules. First confirm that explicit proxying works, then enable TUN. Otherwise, the entry point, routes, and exit all change at once, making failures harder to isolate. If another VPN or transparent proxy is running on the desktop, decide which one owns the default route and DNS first.
tun:
enable: true
stack: mixed
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
stack selects the protocol stack used by TUN. Support depends on the platform, build, and core implementation. This example uses mixed, but that isn’t necessarily the best choice everywhere. Common dns-hijack entries target regular DNS queries; they don’t decrypt DNS over HTTPS used by browsers or apps. After enabling TUN, verify the actual query path, especially for the system resolver, containers, and apps with their own upstreams. A TUN toggle in the interface isn’t enough to confirm DNS is being intercepted.
Insufficient permissions may prevent the core from creating a device or adding routes. Check whether /dev/net/tun is available, then review the service user and its capabilities. Containers are also limited by device mappings, network namespaces, and permissions granted by the host. Running as root inside a container doesn’t mean the host has granted the required network capabilities. Changing routes on a remote server can disconnect your management session, so prepare a console or rollback path and confirm that management subnets and existing VPN routes won’t be captured by mistake.
Grant Capabilities to the Binary That Actually Runs
For a standalone core, you can grant the required network capabilities after reviewing the security implications. The example below uses a fixed binary in the user’s home directory; first confirm that it’s the file you’ll actually run. Capabilities are attached to that file and may need to be reapplied after replacing or reinstalling it. Don’t grant permissions to software from an unknown source, and don’t treat network capabilities as a complete security boundary. Directory write permissions matter too: another process could replace an authorized binary.
test -c /dev/net/tun
install -d "$HOME/.local/bin"
sudo setcap cap_net_admin,cap_net_bind_service=+ep \
"$HOME/.local/bin/mihomo"
getcap "$HOME/.local/bin/mihomo"
cap_net_admin is used for network administration; cap_net_bind_service allows binding to low-numbered ports and is only needed if you actually use one. Some filesystems or service security settings can prevent file capabilities from working as expected. Checking capability output is only the first step; review the startup logs too. A GUI client may manage permissions through its own service component, so don’t apply a second permission setup on top. If an explicit HTTP proxy is enough, keep TUN disabled to minimize permission and routing changes.
Keep User Services’ Paths and Logs Easy to Trace
This user service uses a fixed working directory and an absolute config path, and tests the config before starting. It’s intended for the core installed in a user directory earlier in this guide. systemd doesn’t run ExecStart in an interactive shell, so it won’t expand your terminal aliases, temporary variables, or tilde notation. systemd expands %h to the user’s home directory. A system service needs a separate plan for its run user, directory permissions, and capability boundaries; simply moving the file to a system directory isn’t enough.
# Save as ~/.config/systemd/user/mihomo.service
[Unit]
Description=mihomo user proxy service
[Service]
Type=simple
WorkingDirectory=%h/.config/mihomo
ExecStartPre=%h/.local/bin/mihomo -t -d %h/.config/mihomo -f %h/.config/mihomo/config.yaml
ExecStart=%h/.local/bin/mihomo -d %h/.config/mihomo -f %h/.config/mihomo/config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
Create the service directory and save the file, then reload the unit. After the service starts, check the logs for the actual paths, permission errors, and route errors—not just whether the process exists. User services usually follow the user manager’s lifecycle. If the service must keep running after logout, consider enabling lingering. This changes the lifecycle of background tasks, so choose it based on how the machine is used rather than making it the default for every desktop setup.
mkdir -p "$HOME/.config/systemd/user"
systemctl --user daemon-reload
systemctl --user enable --now mihomo.service
systemctl --user status mihomo.service
journalctl --user -u mihomo.service -n 80 --no-pager
ip route
ip rule
If enabling TUN disconnects your LAN or another VPN, stop the service first and check whether routes recover. Compare the route table and policy routes before and after enabling it. Don’t delete a batch of routes without knowing where they came from, or you may disrupt the system network manager. If the problem persists after the service stops, check for another core instance, leftover DNS settings, or a client background service. Router setups also involve forwarding, firewalls, and gateway relationships. See the OpenWrt Bypass Router Setup Guide; the local TUN example is not a complete bypass-router configuration.
Config Merging, Change Testing & Rollback
Separate YAML Syntax from Client Config Generation
An override is a transformation performed by a client or external tool; it isn’t a feature that every YAML file provides by default. Subscription sources, local overrides, and GUI settings may be combined in different orders, and each client may have its own merge rules. Don’t assume that “the later field always wins,” and don’t pass a client-specific extension key directly to a standalone core. First identify what is doing the processing, which files are inputs, and where the final output is written. Then investigate why a setting was overridden.
Check scalars, mappings, and lists separately. A port number is usually straightforward to replace, but a DNS mapping may be merged recursively or replaced wholesale. Rule and proxy group lists may be appended, prepended, or rebuilt entirely. If a tool replaces the main config’s rules list with the override, an override containing only a LAN rule will remove the existing proxy rules. Conversely, appending after the catch-all rule may leave new entries unable to match. A successful merge doesn’t mean the order is right.
When maintaining a subscription-based config, let the subscription manage proxies and the local config manage stable ports, security boundaries, and service policies. Where Providers can keep proxy updates separate, use them to reduce conflicts caused by replacing the entire file. Not every client supports the same workflow, though. A GUI may regenerate the relevant sections, so use its override or extension mechanism and verify the final runtime file. A success message in the editor only confirms that the edit was saved.
Test One Behavior Change at a Time
Keep a rollback copy before making changes, and record the current mode, group selection, and traffic capture setup. A YAML backup alone may not restore the runtime state: client caches, Provider files, and system proxy settings can all matter. For DNS or TUN changes, also record the original DNS settings and routes. Store backups containing subscriptions or credentials in a restricted directory, not on a website, shared sync folder, or in a public support attachment. Remove sensitive information before sharing a diff.
cd "$HOME/.config/mihomo"
cp -p config.yaml config.yaml.before-change
cp -p config.yaml config.candidate.yaml
# After editing the candidate file, check the candidate file itself.
mihomo -t -d "$HOME/.config/mihomo" \
-f "$HOME/.config/mihomo/config.candidate.yaml"
diff -u config.yaml.before-change config.candidate.yaml
Test the candidate file in the same working directory so its relative paths resolve as expected. However, a config test may still access Providers or generate caches; it isn’t a fully isolated sandbox. Testing mainly catches syntax, field, and reference problems. It doesn’t prove that remote proxies work, DNS has no dependency loop, or routes won’t conflict. After a successful test, review the diff for accidentally removed groups, a wider listening address, or a controller exposed beyond localhost before switching over.
Once the candidate file checks out, replace the production file and reload it using your deployment’s normal process. For the user service in this guide, restarting the service runs the startup check again. If you use a client’s hot reload, confirm it loads the intended config rather than a different subscription. After reloading, test with a new connection; existing connections may keep their previous exit and DNS results. Before disconnecting active connections, consider ongoing downloads, remote sessions, and other users’ work to avoid unnecessary disruption.
cd "$HOME/.config/mihomo"
cp -p config.candidate.yaml config.yaml
systemctl --user restart mihomo.service
journalctl --user -u mihomo.service -n 80 --no-pager
curl --proxy http://127.0.0.1:7890 \
--connect-timeout 10 --head https://example.com
# To roll back, run:
cp -p config.yaml.before-change config.yaml
systemctl --user restart mihomo.service
Break Validation into Isolated Layers
Layer one checks parsing and process startup; layer two checks the actual listeners for the proxy, DNS, and controller; layer three checks proxy handshakes and destination access; layer four checks rule matching and transparent traffic capture. The results of one layer need to be clear before the next is easy to interpret. If explicit proxying works but TUN doesn’t, focus on traffic capture and permissions. If a proxy hostname won’t resolve, check bootstrap DNS. If access works but uses the wrong exit, check rule order and group selection instead of reinstalling the client again.
Verify the rollback too. After restoring the file, check that the service actually reloaded, Providers still contain the updated content, and the client didn’t override the ports or DNS again. If the problem started after a subscription update, save a redacted structural diff and inspect proxy names, protocol fields, and group filters rather than permanently disabling updates. When you’ve found the specific change, a maintenance note only needs the reason, affected fields, test result, and recovery steps—not full logs containing account information.
For long-term maintenance, keep the configuration focused on actual needs: leave unused entry points disabled, keep exceptions narrowly scoped, track the source of remote rule sets, and assign a single owner to the startup service. Before adding a field, check that the current core supports it and that the client won’t remove it during config generation. If behavior is still unclear, return to the shortest validation path in Getting Started or use Troubleshooting to organize the error details. This guide explains fields and dependencies; the final evidence is always the file loaded on your machine, its logs, and the result of a new connection.