Blog

Two rule lists can be identical except for order.

A rule file is not a set. It is an ordered list. For each connection the engine walks it once; the first hit supplies the action and later lines are ignored. Correct syntax with reversed order can reverse the result.

When it even runs

The engine is gated by policy, not by the file

Shadowrocket config file default.conf, showing 289 rules
Rules live in the config file. They are asked only when policy is Config. From the App Store listing; your device build may differ.

A complete rule file still sends every outbound to the current record if policy sits on Proxy, and down the system path if it sits on Direct. The file does not “turn itself on.” Only after you switch policy back to Config does the currently selected configuration start matching line by line. What the three policies mean is in Tunnel. So “rules are not working” is two different claims. One: the engine was never asked. Two: the engine was asked, but the first hit is not the line you thought. Isolate the first before you edit the second.

You can keep several configurations. Only the selected one is used while connected. Server records are a different object: changing the highlighted row does not change the file, and changing the file does not change the highlighted row. What a configuration lists, and which exit is highlighted, are independent. See Objects and Config. The domain tester on the product page uses a hard-coded sample list. It only demonstrates “top to bottom.” GEOIP cannot be simulated in the browser. Treating the tester as your phone’s routing will give the wrong conclusion.

Order

Put the narrower condition above the broader one

The usual line is: condition type, argument, action. Actions are DIRECT, PROXY, REJECT. Matching is top to bottom. After a hit, later lines are ignored. Exceptions must therefore sit above the general case. FINAL in the middle of the file voids every line below it — they remain in the file and are never asked. Line format and condition types are in the Rules chapter; below, one swap is enough to show order itself.

# A: name the host first, then the suffix
DOMAIN,api.example.com,PROXY
DOMAIN-SUFFIX,example.com,DIRECT
FINAL,PROXY

# B: suffix first
DOMAIN-SUFFIX,example.com,DIRECT
DOMAIN,api.example.com,PROXY
FINAL,PROXY

For api.example.com: list A hits the first line and uses the record; list B hits the suffix and goes direct. For www.example.com: both go direct at the suffix. For a host named in neither list: both fall through to FINAL and use the record. The difference only appears on names covered by both a specific line and a wide line. KEYWORD is wider than SUFFIX, GEOIP is wider than most domain lines, FINAL is widest. A stable order is: exact host → suffix / keyword → CIDR → GEOIP → FINAL. That is not a syntax rule; it is how exceptions get a chance to be seen.

DOMAIN requires an exact hostname. DOMAIN-SUFFIX matches that suffix or a name ending in “.suffix”. DOMAIN-KEYWORD only requires the string to appear. A suffix line does not cover another registrable domain: google.com does not take gstatic.com. IP-CIDR only sees IPv4; an IPv6 destination misses the line and falls until GEOIP or FINAL. Adding only IPv4 private ranges while the device uses IPv6 for the same service looks like a rule that “sometimes works.” See DNS.

GEOIP classifies the destination IP in a geo database. It is not the domain’s registrar country, and not the site’s language. When a CDN points the same hostname at addresses in different countries, GEOIP follows. The name rules see is also not always the address bar: if an app connects by IP or omits SNI, every domain line misses and the request lands on an address line or FINAL. That is one source of “the same site is sometimes proxied, sometimes direct” — two requests handed the engine different information.

Imported files

Read FINAL before you decide to use it

Authors of imported lists write for their own exit. Whether FINAL is PROXY or DIRECT decides the fate of every unnamed flow, more than another hundred domain lines. If the node sits on the same side of the network as most sites you already reach, and you only want a few hosts through it, FINAL PROXY sends a mass of unrelated requests into the node. If the node is elsewhere and you want unnamed traffic through it, FINAL DIRECT keeps most sites off the node.

Editing a rule that is never reached does not move Data and does not change the page. Isolate with one fixed record and policy only: if the host already opens under Proxy, return to Config and see which counter moves. Changing the record, the file, and FINAL at once lets every change be explained by the other two variables. Refreshing a subscribed rule file and refreshing a server container are the same kind of action on different objects: on failure the last successful file is kept; rules are not wiped because a fetch failed.

Actions are not degrees of “being connected.” DIRECT leaves on the system path, PROXY is sent to the highlighted record, REJECT is not sent. Writing PROXY for a system service that should be direct often slows push or Store updates; writing DIRECT for a host that should use the record makes Data rise only on Direct; writing REJECT makes the app report a network error, which is easy to misread as a dead node. Confirm the line will actually be hit before you change its action.

The same connection can also hand the engine incomplete information. A browser usually has both a hostname and a resolved address, so domain lines and address lines can both hit — still the first one wins. A client that only connects by IP, with no SNI, misses every domain line and can only land on IP-CIDR, GEOIP, or FINAL. Then “Safari is proxied, this app is direct”: the two programs gave the engine different things; the engine is not random. When isolating, do not only edit domain lines; use Data to see which side this visit’s bytes took. The home-page tester only accepts a domain. It cannot simulate a request that has an address and no name, and it cannot simulate the geo database version on the device.

More lines does not mean exceptions are seen

Appending domain lines at the end of the file cannot beat a wide condition already written above. To send a host another way, write it before the line that would intercept it. Wrong order makes even a long list a truncated list.