1. Find Your Config: Back It Up and Confirm Which File Is Active
In your client's configuration list, select the active config and find the option to edit or open its file. Save a copy before changing the YAML. Menu labels vary between clients; the important thing is to confirm whether you're editing the active config, the original subscription, a local override, or a generated runtime file. Reload the config after editing, then check the logs to confirm it loaded successfully.
config.yaml is a common filename, but it isn't required. The core can read YAML from a specified path, and graphical clients may save separate files for different subscriptions. Subscription updates usually download the source file again, so use the client's override or merge feature for settings you want to keep, such as ports, DNS, and custom rules. How merging works depends on the client.
Three YAML Basics to Check
- Indent with spaces: Use two spaces per level and don't mix in tabs. Align top-level
dns,proxies, andrules; indent their child items. - Start list items with a hyphen: Each rule under
rulesis a list item, as is each policy group underproxy-groups. - Quote special strings: Quote passwords and text containing colons or hash signs. Don't define the same top-level key twice, and don't use a full-width colon in place of a regular colon.
A useful way to read the config is from entry point to exit: ports determine how apps hand connections to the core, DNS handles name resolution, proxies and policy groups provide exits, and rules decide which exit each connection uses. The sections below follow that order, but the order of top-level YAML keys does not determine execution order.
2. General Settings: mixed-port, mode, and Log Level
mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
ipv6: false
These settings expose HTTP and SOCKS proxy access on 7890, use rule mode, and write routine activity to the log. The port shown is an example, not a default for every client. If the client overrides the port in its settings, use the port the running core actually listens on.
| Field | Purpose and What to Check |
|---|---|
mixed-port | Accepts both HTTP and SOCKS proxy requests on one port. Set apps to the address and port the core is actually listening on. |
port / socks-port | Provides separate HTTP and SOCKS ports. With a mixed port, you don't need to open another pair of ports for the same purpose. |
allow-lan | Controls whether devices on your local network can access the proxy. For use on your Mac alone, you can start with false. |
mode | rule routes traffic according to rules, global uses the global policy, and direct connects directly. |
log-level | Temporarily switch to debug when troubleshooting, then restore info to avoid accumulating excessive detail in the logs. |
ipv6 | Controls IPv6 use in the core. The DNS module has its own IPv6 setting, so check both. |
A Listening Port Doesn't Automatically Enable the System Proxy
Setting mixed-port: 7890 only makes the port available in the core. To route macOS apps through it, enable the system proxy in your client or check the settings under System Settings → Network → your network service → Details → Proxies. The interface may vary slightly between macOS versions. For local testing, the proxy address is usually 127.0.0.1 and the port is 7890.
The system proxy doesn't cover every app. Some command-line tools, games, and apps with their own network stacks need separate configuration or TUN routing. First verify the proxy with a tool that supports an explicit HTTP proxy, then decide whether to route more traffic through the core.
3. dns: Listen Address, Upstream Resolvers, and Fake-IP
dns:
enable: true
listen: 127.0.0.1:1053
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
default-nameserver:
- 223.5.5.5
nameserver:
- https://dns.alidns.com/dns-query
fake-ip-filter:
- "*.lan"
- "localhost"
enable turns on the core's DNS service, while listen sets its local listen address. Here, 1053 is an example of a high-numbered port, avoiding 53, which typically requires elevated privileges. Setting a listen address alone doesn't make the whole system send queries to it. System DNS settings, TUN DNS hijacking, and an app's own encrypted DNS can all affect the actual path.
What Do default-nameserver and nameserver Do?
default-nameserveris mainly used to resolve the hostnames of upstream DNS servers. For example, it can resolvedns.alidns.combefore a DoH request is made.nameserverlists the upstream resolvers for regular domain lookups. This example uses DoH; whether it works depends on your network and routing configuration.nameserver-policylets you select an upstream for specific domains, useful for clearly scoped cases such as internal domains or company DNS.
If a proxy node's server is a hostname, check mihomo's proxy-server-nameserver too. It resolves proxy server hostnames and can keep their resolution path separate from regular website lookups. Don't treat ordinary queries, upstream bootstrap lookups, and proxy server lookups as the same step.
Fake-IP Addresses Aren't Remote Server Addresses
In fake-ip mode, the core returns a mapped address to the app and stores the address-to-domain mapping for matching and forwarding when a connection arrives. The 198.18.0.1/16 range shown here is an address pool for those mappings. It doesn't indicate where a real website is hosted and isn't a proxy node address.
By default, domains in fake-ip-filter receive real DNS results instead of Fake-IP addresses. Add exceptions as needed for local network discovery or apps that require real addresses, but make sure the upstream DNS can resolve those domains. Adding an internal domain to the filter list doesn't give you access to your company's internal DNS.
4. proxies and proxy-providers: Define Nodes and Their Sources
proxies is a list of nodes, each defined by a mapping with its protocol and connection settings. Common fields include name, type, server, and port, plus the authentication and transport settings required by the protocol. Policy groups refer to nodes by name, so keep names unique. A proxy server's port and the local mixed-port are separate settings and don't need to match.
| Field | What to Check |
|---|---|
name | The local name used to identify the node; policy groups use this name to refer to it. |
type | Protocol type, such as ss, trojan, or vmess. Supported protocols depend on the core. |
server / port | The remote server address and service port, provided by your service provider. |
| Authentication and Transport Fields | Passwords, UUIDs, TLS, SNI, and transport settings must match the server. Changing only the protocol name isn't enough. |
proxy-providers | Loads a set of nodes from a separate source, which policy groups can reference with use. |
A full subscription often includes nodes, policy groups, and rules, while a proxy provider mainly supplies a set of nodes. Putting an arbitrary Base64 node subscription URL in an HTTP-type proxy-providers entry doesn't mean the core will convert its format automatically; the content must still be in a format the provider supports.
If you don't have any nodes yet, proxies: [] defines an empty list and can help you check the config structure. It doesn't create a remote exit. If your subscription already has nodes, keep their actual protocol settings rather than clearing the list to match an example.
5. proxy-groups: Connect Nodes to Rules
proxies: []
proxy-groups:
- name: Manual Exit
type: select
proxies:
- DIRECT
This direct-only policy group can help you check the config structure: Manual Exit is the group name, and select lets you choose a member. Its only member is the built-in DIRECT policy, so it currently connects directly. To use a remote node, add the exact name of a node in your config or reference a proxy provider with use.
Common Policy Group Types
select: Choose a member manually. Useful for a fixed traffic route or troubleshooting.url-test: Tests eligible members against a specified URL and selects an exit based on latency and settings such as tolerance.fallback: Selects an available member in the configured order. It's designed for failover, not for constantly seeking the lowest latency.load-balance: Distributes connections according to the selected strategy. It doesn't combine multiple nodes' bandwidth into one download connection.
In test-based policy groups, url and interval are related to node health checks. A successful test only shows that the test target is reachable; it doesn't guarantee access to every site. If automatic selection behaves unexpectedly, check whether the test URL works well on your network before investigating the nodes.
Groups can reference one another, but they must not form a loop. If a rule specifies Manual Exit, the config must contain a group with exactly that name. An extra space or different punctuation can prevent the target from being found.
6. rule-providers: Separate Rule Data from Routing Actions
rule-providers:
office-domains:
type: file
behavior: domain
format: yaml
path: ./rules/office-domains.yaml
This config defines a local rule provider named office-domains. type: file means it reads from a file; this example doesn't download anything automatically. Relative paths are usually resolved from the core's working directory, not the directory open in your text editor. A graphical client may also restrict which paths it can read, so place the file in a configuration data directory the client allows.
The file can contain:
payload:
- "+.example.org"
- "intranet.example.net"
This uses reserved example domains to demonstrate the format; they don't refer to a real office service. behavior: domain means the file contains domain match entries. +.example.org can match the root domain and its subdomains. With classical, entries include a rule type, such as DOMAIN-SUFFIX,example.org; you can't use the plain-domain format shown above.
domain: Domain data, suitable for maintaining a list of domains.ipcidr: IP range data, suitable for maintaining a list of address ranges.classical: A list expressed as rule types, supporting a wider range of match conditions.
HTTP providers also need url; use interval to set the update interval in seconds. For example, 86400 means one day. format must match the actual file format. If you switch it to text, the file no longer uses the YAML payload wrapper.
7. rules: Match from Top to Bottom; Put MATCH Last
rules:
- DOMAIN-SUFFIX,example.org,Manual Exit
- RULE-SET,office-domains,DIRECT
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- GEOIP,CN,DIRECT
- MATCH,Manual Exit
These rules need to work with the policy group and rule provider defined earlier, and the local rule file must exist. The first matches example.org and its subdomains; the second references office-domains; the third matches the specified private IP range; the fourth attempts to match IP addresses in China; the last catches all remaining connections. GEOIP requires an available geolocation database; adding a rule alone isn't enough.
The First Matching Rule Wins
rules are checked from top to bottom, and the target for the first match is used. In the example above, example.org is also in the office rule set, but the first rule matches it and sends it to Manual Exit. That's why custom exceptions usually go before broader rules. An exception placed after MATCH won't take effect.
no-resolve prevents a rule from triggering a DNS lookup just to match an IP-based rule. If a connection has only a domain name and no destination IP is available, the rule may not match. If the connection is already going to an IP address, the IP range can still be checked. This isn't a global switch that prevents DNS for all connections.
Policy Names and Built-in Actions
DIRECT: Connects directly, subject to your local network, routing, and the destination service being available.REJECT: Rejects matching connections, useful for destinations you explicitly want to block.- Policy group name: Sends the connection to the node or nested group currently selected in that group.
MATCH: A catch-all rule with no additional conditions. Put it at the end of the rule list.
To troubleshoot routing, keep mode: rule and check the connection log for the destination domain, matched rule, and final exit. If you switch to global mode, connection results won't tell you whether the rules are ordered correctly.
8. TUN and the Control API: Enable Only When Needed
tun:
enable: false
stack: mixed
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
TUN is disabled in this example so you can verify the proxy entry point and rules one at a time. Enable it when you need to route apps that ignore the system proxy, and check that your client has installed its service, has the required system permissions, and uses a compatible core. auto-route configures routes automatically, while auto-detect-interface helps select the outbound network interface. After enabling TUN, check the network routes, not just the toggle.
dns-hijack with any:53 intercepts the corresponding regular DNS traffic; it doesn't automatically intercept all DoH or DoT requests. A company VPN, other virtual interfaces, and firewalls can interact with TUN routing. If something goes wrong, disable one routing layer at a time to compare results instead of changing DNS, routes, and every rule at once.
external-controller is the listening address for the control API, separate from the web proxy port. For example, a local management address could be 127.0.0.1:9090. If you enable it, set a strong password with secret and restrict access. Some clients manage these fields themselves, so avoid overwriting the addresses and authentication settings they depend on.
9. Verify Your Changes: Check Syntax, Listening Ports, and Routing
- Check the syntax first. If you have the standalone mihomo command installed, run the check command below from the directory containing your config copy. If you use a graphical client, use its config check or reload feature to look for errors.
- Then check the listener. Make sure no other client is using
7890and confirm that the current process loaded the file you edited. - Test the explicit proxy. Send a request to a fixed proxy address to rule out interference from the system proxy toggle or the app's own settings.
- Finally, check the rules. Review the matched rule and exit in the connection log; disconnect old connections and try again if needed. Existing long-lived connections may continue using the route they established earlier.
mihomo -t -f ./config.yaml
# macOS: Check which process is listening on TCP 7890
lsof -nP -iTCP:7890 -sTCP:LISTEN
# Test a request using the local HTTP proxy explicitly
curl -I --proxy http://127.0.0.1:7890 https://example.com
A successful config check means the current core can parse the file. It doesn't mean a node is online, a rule set downloaded successfully, or a destination site is reachable. An HTTP response from curl shows that the request got a response; check the connection log to confirm whether it went direct or through a proxy. The empty-node example here only provides a direct connection, so it can't test remote proxy access.
Find the Field Behind an Error
- YAML parsing failed: Check the indentation, quotes, colons, and list hyphens near the line mentioned in the error.
- Proxy or policy group not found: Check the rule target, group members, and node names, and confirm that the proxy provider has loaded.
- Failed to load rule set: Check the path, file permissions, and whether
behaviorandformatmatch the file's contents. - Failed to bind port: Check for port conflicts and the listen address. Don't run two clients on the same entry point.
- Changes lost after a subscription update: Move settings you want to keep into a local override layer supported by your client, then inspect the merged runtime config.
Keep a baseline config that loads successfully, and change one category of settings at a time. Check ports first, then DNS, then nodes and policy groups, and finally rules and TUN. This keeps troubleshooting focused on the part you just changed. For a complete overview of the configuration workflow, see this site's advanced configuration guide.