Misc Options
[General]
ipv6 = true
loglevel = notify
skip-proxy = 127.0.0.1, 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, localhost, *.local
tun-excluded-routes = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
tun-included-routes = 192.168.1.12/32
Common Options
Enable full IPv6 support (Default: true)
ipv6 = true
loglevel (Default: warning)
loglevel = notify
One of none, fatal, warning, notify, info or verbose. It's not recommended to enable verbose in daily use because this will slow down the performance significantly.
skip-proxy
skip-proxy = 127.0.0.1, 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10, localhost, *.local
In iOS and tvOS version, a hostname entry is added to the tunnel's proxy exception list, so connections to it are handled by Chute TUN instead of Chute proxy; an IP address or CIDR entry additionally becomes an excluded route of the tunnel, so traffic to that range leaves the tunnel entirely rather than being handled by Chute TUN. Chute Android does the same in VPN mode: while System HTTP Proxy is on, hostnames, * wildcards and IPv4 addresses join the exclusion list of the proxy the VPN hands to apps, and on Android 13 and later every IP address or CIDR entry becomes an excluded route of the VPN — a bare address as a /32 or /128 route; loopback entries are left out, since loopback traffic never enters the tunnel. In macOS version, these settings will be applied to system when "Set as System Proxy" is enabled. This option is used to fix compatibility problems with some apps.
- To specify a single domain, enter the domain name - for example, apple.com.
- To specify all websites on a domain, use an asterisk before the domain name - for example, *apple.com.
- To specify a specific part of a domain, specify each part - for example, store.apple.com.
- To specify hosts or networks by IP addresses, enter a specific IP address such as 192.168.2.11 or an address range, such as 192.168.2.* or 192.168.2.0/24.
Notice: If you enter an IP address or address range, you will only be able to bypass the proxy when you connect to that host using that address, not when you connect to the host by a domain name that resolves to that address.
Proxy Server Listening
interface = 127.0.0.1
port = 8118
socks-interface = 127.0.0.1
socks-port = 8119
interface / port control the listening address and port of the HTTP proxy server (default 127.0.0.1:8118). socks-interface / socks-port control the SOCKS5 proxy server (default 127.0.0.1:8119).
The HTTP proxy server carries one exchange per connection for a plain http:// request: each response goes out with Connection: close and the connection closes after it, so the client sends its next request on a new connection. A request pipelined behind the first is dropped, and the client sends it again on the new connection. An authentication scheme that needs several round trips on the same connection, such as NTLM or Negotiate, cannot complete through it. A CONNECT tunnel that is not decrypted is not affected. A WebSocket upgrade keeps its connection: after the 101 response, data passes unchanged both ways.
Compatibility aliases:
doh-serveranddoh-serviceare accepted as aliases ofdoh;http-listen/socks5-listen(e.g.0.0.0.0:6152,[::]:6153, or a bare port) are mapped onto the interface/port settings above. A wildcard listen host (0.0.0.0,::, or*) also setsallow-wifi-access = true.
Inbound Proxy Authentication
http-auth = username:password
Requires clients to authenticate before using Chute's HTTP and SOCKS5 proxy servers. The line may be repeated to allow multiple credentials.
The HTTP proxy answers a request without valid credentials — a plain request or a CONNECT — with 407 Proxy Authentication Required and Proxy-Authenticate: Basic realm="KLNEKit", then closes the connection. The SOCKS5 server takes the same credentials through SOCKS5 username/password authentication and closes a connection that does not offer it or fails it.
Bypass System Requests
bypass-system = true
When enabled, Chute appends a fixed list of 18 Apple hostnames — push, iCloud gateway, captive-portal check, OCSP and similar — to skip-proxy, and appends IP-CIDR,17.0.0.0/8,DIRECT,no-resolve after your rules, just before FINAL — so a rule of yours that matches 17.0.0.0/8 first still wins. There is no process-based matching: the option is that fixed host and address list, not a filter on which process made the request.
Default:
true.
Always Real IP
always-real-ip = *.example.com, tracker.example.org
When Chute serves fake IP addresses for hijacked DNS queries (see hijack-dns), hostnames matching this comma-separated list are always answered with their real resolved IP addresses instead. Wildcards are supported. always-ip-address (Shadowrocket's spelling) is read as this key: a boolean value (true, yes, on, 1) means every host and is rewritten to always-real-ip = *; false is dropped; any other value is read as a host list. The configuration is saved with the always-real-ip spelling.
Read the System Hosts File
read-etc-hosts = false
Whether the system hosts file is read into the [Host] table. Set it to false to ignore that file; a change takes effect on reload. See Local DNS Mapping.
Default:
true.
Interrupt Existing Connections
interrupt-exist-connections = true
When enabled, changing the selected policy in any policy group (via URL Test, Fallback, Load Balance, SSID, or manual selection) will gracefully tear down existing connections that were using the old policy. This ensures connections use the newly selected proxy immediately rather than lingering on the old one.
This applies to every group a connection passes: the group its rule names, a group nested in it, the upstream group of a chained policy (underlying-proxy), and a relay group's member groups.
Each affected connection is closed gracefully with a 3-second timeout before being force-closed.
Default:
false. This is a global setting — it affects all policy groups.
Network Framework (macOS / iOS / tvOS)
network-framework = true
Enable Apple Network.framework for outbound connections. Using Network.framework can provide better performance and modern TLS stack integration on supported platforms.
Default:
trueon macOS,falseon iOS and tvOS.
Exclude Simple Hostnames
exclude-simple-hostnames = true
When enabled, requests to simple hostnames (single-label names without a dot, e.g. localhost) bypass proxy rules and are resolved locally. This helps avoid unnecessary DNS lookups for local network names. This is the macOS system proxy's own "Exclude simple hostnames" setting: Chute Mac writes it into the system proxy configuration, so it applies to applications that follow the system proxy and to nothing else — not to Enhanced Mode or TUN traffic. On iOS and tvOS it is written into the tunnel's proxy settings in the same way, so a simple hostname is handled by Chute TUN instead of Chute proxy, as a hostname in skip-proxy is. Chute Android does not use it.
On Chute Mac, a one-time migration enables this option by default on first run; an explicit user off setting is respected.
Default:
false.
Disable Database Record
disable-db-record = true
When enabled, Chute stops writing traffic records to the local database. This can improve performance and reduce storage usage, but traffic history will not be available in Chute Dashboard.
Default:
false.
Menu Bar Speed Display (Mac Only)
menu-bar-show-speed = true
When enabled, Chute Mac shows the current upload and download speed in the menu bar.
Parsed but not effective in the current engine — nothing in Chute Mac reads the key. The menu bar speed is toggled from the menu item Show Connection Speed, which is stored in the app's own settings. The key is still written back when the configuration is saved.
Hijack Other DNS Servers
hijack-dns = 8.8.8.8:53
By default, Chute only returns fake IP addresses for DNS queries sent to Chute DNS address (198.18.0.2). Queries which are sent to standard DNS will be simply forwarded.
Some devices or softwares always use a hardcode DNS server. (For example, Google Speakers always use 8.8.8.8). You may use this option to hijack the query to get a fake address.
You may usehijack-dns = *:53to hijack all DNS queries.
An entry without a port means port 53, so hijack-dns = 8.8.8.8 is 8.8.8.8:53. An entry that cannot be read is skipped with a notice; the rest of the line still applies.
Fake IP is available on Apple TV too (tvOS 17 and later). It had been disabled there; verification on a device is still in progress.
Excluded Routes
tun-excluded-routes = 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12
Chute VIF can only process TCP and UDP protocols. Use this option to bypass specific IP ranges to allow all traffic to pass through.
Notice: This option applies to the iOS and tvOS packet tunnels, where the ranges become excluded routes of the tunnel's IP settings, and to Chute Android on Android 13 and later, where they become excluded routes of the VPN — Android 11 and 12 have no API for excluded routes, so the ranges are ignored there. It has no effect on macOS, in either Enhanced Mode type — neither the helper's utun nor the Mac Network Extension excludes any route. Requests handled by Chute Proxy Server will not be affected. Combine 'skip-proxy' and 'tun-excluded-routes' to make sure that certain HTTP traffic bypasses Chute.
This option might cause a system error ENOMEM (Cannot allocate memory). It seems a bug in iOS system. Please do not use this option if possible.
Included Routes
tun-included-routes = 192.168.1.12/32
By default, the Chute VIF interface will declare itself as the default route. But since the Wi-Fi interface has a smaller route, some traffic may not go through the Chute VIF interface. Use this option to add a smaller route.
The reason a default route is not enough: the system picks a route by prefix length, not by order. The physical interface's own on-link subnet — 192.168.1.0/24, say — is more specific than the tunnel's 0.0.0.0/0, so that traffic never reaches Chute. An entry here installs a still more specific route and wins. The routes are added on top of the default one and never narrow what the tunnel captures.
Note: Avoid listing the private ranges (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) wholesale. It is normally unnecessary and can upset the system's own routing; Chute honours such an entry but logs a warning — except under the Network Extension flavour of Enhanced Mode on macOS, which honours it without one. The usual reason to use this option is reaching a range another VPN app's tunnel owns.Note: An entry that would cut the branch Chute sits on is refused with the reason in the log: loopback, the VIF's own subnets (
198.18.0.0/15andfd12:1:1:1::/64), link-local, multicast, broadcast, and a prefix length of0. The helper-utun flavour of Enhanced Mode on macOS also refuses an entry that covers the current default gateway. Under the Network Extension flavour the reason goes to the system log rather than to Chute's.Note: On iPhone the system decides local-network capture separately. A local subnet listed here is honoured only when Include Local Networks is also on in the app's settings — a switch that can be turned on only while Include All Networks is on; a range owned by another app's tunnel needs no such switch. Chute tvOS shows the same switches but does not apply them to the tunnel. On macOS the routes are installed directly and no such gate applies.
Note: On macOS the helper-utun flavour of Enhanced Mode installs these routes when the interface is attached, so a changed list re-attaches the interface on reload. This option needs helper version 0.8.6 or later — after updating Chute, approve the helper reinstall prompt once, or the helper-utun flavour reports that the helper is not running.
Protocol Sniffing
sniffing-enabled = true
sniffing-timeout = 100
Chute can detect the actual protocol of a connection by inspecting the initial bytes. This enables rules like PROTOCOL,TLS,Proxy to work correctly even for non-HTTP inbound connections.
sniffing-enabled (Default: false)
sniffing-enabled = true
Enable protocol detection for TCP connections. In Enhanced Mode the same switch lets an HTTP/3 (QUIC) flow to a bare IP address be matched by name: Chute reads the server name from the QUIC ClientHello, DOMAIN-type rules see that name instead of the address, and every later packet of the flow follows the verdict of the first. A ClientHello too large for one packet — common with post-quantum key exchange — is reassembled per flow from the Initial packets that carry it, up to a reassembled hello of 16 KiB, so its name is read as well. While the rest of the hello is still coming, the flow's datagrams are held rather than sent by their bare address, and they go out together behind the name once it is read — so the first fragment of a split hello no longer takes a different route from the rest of it. The backstops are a one-second wait, eight datagrams or 16 KiB held per flow, and 64 flows assembling at once; a flow past any of these, or one whose Initial packets never complete the ClientHello, is matched by address as before.
sniffing-timeout (Default: 100ms)
sniffing-timeout = 200
Maximum time in milliseconds to wait for the initial data to determine the protocol. Increase this value if protocol detection fails on slow connections.
Block QUIC
block-quic = auto
Controls whether Chute rejects detected QUIC traffic, including HTTP/3. QUIC runs over UDP and cannot be decrypted by Chute's HTTP MitM. Rejecting QUIC can prompt compatible clients to retry over TCP, allowing TCP-based proxy handling and, when configured, HTTPS decryption to apply.
| Value | Behavior |
|---|---|
off |
Do not apply global QUIC blocking (default). Detected QUIC traffic follows the normal routing rules. |
on |
Reject every detected QUIC flow, including traffic routed to DIRECT. |
auto |
Reject detected QUIC only when the final outbound policy is a proxy. Traffic routed to DIRECT is allowed; traffic routed to REJECT remains rejected by that rule. |
all |
Surge's spelling of on. |
all-proxy, per-policy |
Surge's spellings, both read as auto — Chute has no per-policy QUIC blocking, so per-policy blocks QUIC on every proxied flow. The word you wrote is kept when the profile is saved. |
For traffic entering through TUN, Chute replies to a rejected QUIC flow with an ICMP/ICMPv6 Port Unreachable message so compatible clients can fall back without waiting for a QUIC timeout.
A rejected QUIC flow is recorded with
REJECTas its policy, whichever policy the rule chose.QUIC detection for
block-quicis automatic and does not requiresniffing-enabled;sniffing-enabledcontrols protocol sniffing — TLS on TCP, and the server name of a QUIC flow. Use aPROTOCOL,QUIC,...rule when you need rule-based handling of individual QUIC flows.
Answer ICMP in the Tunnel
icmp-auto-reply = false
An ICMP echo request that enters the tunnel — IPv4 or IPv6 — is answered by Chute itself: the reply is synthesised locally and nothing is sent to the target. Every destination therefore answers, an address a rule would REJECT and a host that is down included, so a ping that succeeds inside the tunnel says nothing about whether the target is reachable. Set this to false to stop answering: the request is then relayed out of the device, but its reply never comes back through the tunnel, so the ping is never answered.
Default:
true. Applies to traffic arriving through TUN; ICMP is never proxied.
Bypass TUN
bypass-tun = 192.168.0.0/16, 10.0.0.0/8
Similar to skip-proxy, but works at the TUN/VIF routing level. Connections to these IP ranges will bypass the TUN interface entirely and go through the system network stack directly.
Note: This option applies to the iOS and tvOS packet tunnels, where its ranges join
tun-excluded-routesas excluded routes of the tunnel, and in the same way to Chute Android on Android 13 and later (Android 11 and 12 ignore it). It has no effect on macOS, in either Enhanced Mode type.
Show Error Page for Reject
show-error-page-for-reject = true
When enabled, Chute returns a user-friendly error page for rejected requests. This applies only to a request that arrived through the HTTP proxy inbound; with the option off, such a request is answered with HTTP/1.1 503 Service Unavailable instead. A rejected request from any other inbound is dropped either way.
Default:
false.
Optimistic DNS
optimistic-dns = false
When enabled, Chute returns the cached DNS result immediately while refreshing the record in the background. This reduces connection latency at the cost of possibly returning stale DNS records. With it off, a record whose TTL has passed is dropped instead of served and the query goes upstream; the refresh still happens, so the next lookup is answered from cache. It also governs, on every platform, whether the tunnel dials several of a name's addresses at once.
Default:
true. Setoptimistic-dns = falseto disable.
Allow Wi-Fi Access
allow-wifi-access = true
Lets other devices on the same network reach Chute's HTTP and SOCKS5 proxy ports, by widening both listeners to 0.0.0.0. Surge-style configurations — and the sing-box importer's allow-lan mapping — carry only this flag and leave interface at its 127.0.0.1 default, which is why the flag widens the bind address itself.
The flag and an explicit wildcard
interfaceare two spellings of the same intent, and Chute takes the union: a configuration that already writes0.0.0.0behaves exactly as before. Changing the flag rebinds both listeners, so withdrawing access takes effect on reload rather than at the next restart.These are the proxy ports, which have no authentication unless
http-authis set. On a network you do not control, set credentials as well.
Managed Configuration
#!MANAGED-CONFIG https://example.com/config.conf interval=86400 strict=false
A configuration whose first line is a #!MANAGED-CONFIG header is re-fetched from the URL automatically. The header's parameters and the update behavior are documented in Managed Configuration.
Replica / Traffic Recording
replica = true
The replica key in [General] (replica = true) enables traffic recording; the [Replica] section filters what the Chute Dashboard displays — its keys are documented in Replica.
On iOS, tvOS and Android an active licence is required as well: without it
replica = truecaptures nothing.
External Controller Access (Chute Dashboard)
external-controller-access = password@0.0.0.0:6155
Starts the remote control server used by Chute Dashboard. The value is password@host:port; a username may optionally precede the password (user:password@host:port). On iOS and tvOS, a companion USB channel is opened on port + 1 (not on macOS). Connecting it is described on the Chute Dashboard page.
HTTP Control API and Web Console
[General]
external-http-controller = 127.0.0.1:9090
external-http-secret = your-secret-token
external-http-ui = true
external-http-cors = false
Chute provides an embedded HTTP Control API and Web Console, similar to Clash and Surge dashboards. The API exposes engine status, traffic, connections, DNS, policy controls, and configuration management through REST endpoints.
Surge's http-api = <secret>@<host>:<port> is read as external-http-controller plus external-http-secret. A profile may also spell the parts separately: http-api-secret, http-api-ui and http-api-cors are read as external-http-secret, external-http-ui and external-http-cors, and http-api-web-dashboard also as external-http-ui; the configuration is saved with the external-http-* keys. http-api-tls is not supported — the controller speaks plain HTTP — and only produces a notice.
external-http-controller (Default: disabled)
external-http-controller = 127.0.0.1:9090
The requests the console keeps resident while serving are capped at 4 MB on macOS and 1 MB on iOS, tvOS and Android.
The address and port for the HTTP control server. Use 127.0.0.1 for local-only access. Binding to any non-loopback address (for example 192.168.1.5:9090) requires an explicit external-http-secret — without one the server refuses to start and logs what is missing.
A wildcard address (0.0.0.0, ::, *) counts as non-loopback for this check, because it listens on every interface — the opposite of local-only. Accepted spellings are 0.0.0.0:9090, *:9090 (the same thing) and, for IPv6, [::]:9090 — a bare :::9090 is bracketed for you; an address that cannot be parsed is reported as a configuration error instead of silently leaving the controller off. The whole of 127.0.0.0/8 counts as loopback, not just 127.0.0.1.
external-http-secret (Default: a generated token)
external-http-secret = your-secret-token
The Bearer token used for API authentication. Requests must include the header Authorization: Bearer <secret> — the token is accepted only in this header, never as a query parameter, and is compared in constant time. Authentication failures return 401 with {"ok": false, "error": {"code": "unauthorized", "message": "missing or invalid token"}}. Every data route is guarded: the /api/* paths, the Clash-compatible aliases, and the older routes that only Chute Android still serves (/status, /policies, /dns, /records and the like), which accept the token only in this header as well. Chute Android's own Remote Dashboard now uses the /api/* paths only. The Web Console's page and static assets (/, /assets/…, and on Chute Android also /ui) need no token (with external-http-ui = false they are gone altogether — the page is a 404).
When the key is absent, Chute generates a token instead of serving without authentication. The generated token is written to a control-token file in Chute's share folder — not beside the configuration: the app-group container on iOS and tvOS, the app's private data directory on Android — with owner-only permissions, is reused across restarts, and the apps show it to you — see the console entry points. The control API can hand out the connection database, whose records carry every URL, header and process name the run has seen, so an open controller is not a reasonable default.
To serve without authentication anyway, say so in the configuration:
external-http-secret = none
That is accepted only on a loopback bind. Off loopback, neither none nor an absent key is enough: the controller refuses to start and says what it wants.
Upgrading: if you relied on an absent
external-http-secretmeaning "no authentication", local scripts start receiving401. Either read the generated token out of the app, or writeexternal-http-secret = noneto keep the old behaviour deliberately.
external-http-ui (Default: true)
external-http-ui = true
When enabled, Chute serves an embedded Web Console at the controller address. The Web Console provides an overview, connection management, DNS inspection, traffic monitoring, policy controls, and configuration editing. Set to false to keep the API enabled while disabling the UI.
Opening the web console
The console is served at the controller's address, and each app can hand you the address and the token — including the generated one, which you would otherwise have no way to read:
- Chute Mac: menu bar → Open Web Console, and Copy Web Console Token. A connection's detail window also has Requests & History, which opens the console — its home page, not that connection.
- Chute iOS: Control Panel → the Web Console row → Open, Copy Address, or Copy Access Token.
- Chute tvOS: the control panel has a separate Web Console Address row, showing host and port. Selecting it shows a QR code when the listener is reachable from other devices — the sign-in token travels inside the code, not on the screen — and, when the controller is bound to loopback, an explanation instead, because a loopback console cannot be opened from a phone.
- Chute Android: Control Panel → Web Console Address → Open or Copy Address, and a Generated access token row that copies on tap. Neither row appears when the token is a secret you configured yourself, which is never displayed.
The address these produce carries the token as a query parameter. The page consumes it on load and removes it from the address bar, so opening the link is a complete sign-in and nobody has to transcribe 32 hex characters.
external-http-cors (Default: false)
external-http-cors = true
Enable CORS (Cross-Origin Resource Sharing) headers on API responses. Useful when the Web Console or third-party tools need to access the API from a different origin.
API Endpoints:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/status |
Runtime status, ports, uptime |
GET |
/api/traffic |
Global and per-policy traffic counters |
GET |
/api/connections |
Current active connections |
DELETE |
/api/connections/:id |
Close a connection |
GET |
/api/connections/history |
Historical connection records |
GET |
/api/connections/processes |
Per-process connection statistics |
GET |
/api/connections/:id/request |
Captured request data for a connection |
GET |
/api/connections/:id/response |
Captured response data for a connection |
GET |
/api/dns |
DNS cache records |
DELETE |
/api/dns/cache |
Purge DNS cache |
DELETE |
/api/dns/records/:domain |
Remove a single DNS record |
GET |
/api/config |
Current configuration |
PUT |
/api/config |
Reload configuration |
POST |
/api/config/validate |
Parse a configuration and report its errors, without loading it |
GET |
/api/policies |
Policy groups and current selection; every entry carries hidden, which is true for a group marked hidden=true |
PUT |
/api/policies/:group |
Change policy group selection |
PUT |
/api/mode |
Set outbound mode |
GET |
/api/features |
Feature switch states |
PUT |
/api/features/mitm |
Toggle MitM |
PUT |
/api/features/record-traffic |
Toggle traffic recording |
GET |
/api/rules |
The rule table the matcher walks, where each rule came from, and which rewrite rules have fired |
POST |
/api/rules/match |
Where a request would be routed, without making it |
GET |
/api/logs |
Recent log entries |
GET |
/api/loglevel |
Current log level, and the sections being written |
PUT |
/api/loglevel |
Change the log level or sections without restarting |
GET |
/api/health |
Engine health: refusals, generations, footprint, previous exit |
GET |
/api/events |
Notable moments of this run |
GET |
/api/tailscale |
Live Tailscale engine state |
GET |
/api/scripts |
The generic scripts that can be run on demand |
POST |
/api/scripts/run |
Run one generic script by name |
POST |
/api/diagnostics/ping |
ICMP or TCP reachability probe |
POST |
/api/diagnostics/dns-query |
Resolve a domain through the running resolver |
POST |
/api/diagnostics/egress-probe |
Check the address: this device's own on the Apple engines, the public egress address on Android |
POST |
/api/diagnostics/internet-test |
The direct Internet test: fetch internet-test-url without a proxy |
POST |
/api/diagnostics/url-test/:policy |
Run a policy's latency test |
POST |
/api/diagnostics/bundle |
Build a redacted diagnostic archive |
GET |
/api/connections/export |
Export connections as HAR 1.2 |
GET |
/api/rewrites |
Every rewrite/Mock Response family, plus the MitM host list |
GET |
/api/rewrites/:family |
One family's rules |
POST |
/api/rewrites/:family |
Add a rule |
DELETE |
/api/rewrites/:family/:id |
Remove a rule |
DELETE |
/api/rewrites/:family |
Clear a family |
GET |
/api/mitm/hosts |
Hosts currently decrypted |
POST |
/api/mitm/hosts |
Add a host |
DELETE |
/api/mitm/hosts/:host |
Remove a host |
DELETE |
/api/mitm/hosts |
Clear the list |
GET |
/api/rules/providers |
Rule providers and rule sets, with the status of each — including why one did not load |
Endpoint notes:
GET /api/connectionsacceptslimit(positive integer, default and maximum 1000) andcursor(returns only connections withidgreater than the cursor). The responsedatacarriesconnections,total,page_size,has_more, and — when more pages exist —next_cursor.GET /api/connections/historyacceptslimit(default 100, maximum 1000) andcursor/before(synonyms; passing both is rejected).GET /api/connections/:id/requestand.../responsereturn{"connection_id": <id>, "data": "<base64>"}. Captures larger than 2 MiB return413.GET /api/configreturns the configuration with sensitive values (http-auth,external-http-secret,ca-p12,ca-passphrase, WireGuard keys, and similar) replaced by<redacted>— do not feed the result straight back intoPUT /api/config, or the placeholders are written into the configuration literally.GETandPUT /api/configare also reachable as/api/configs, and as the Clash alias/configs.PUT /api/configaccepts either JSON{"configuration": "<full text>"}or the raw configuration text as the request body (1 MB limit). On success the engine reloads — or starts, if it was not running.PUT /api/policies/:grouptakes the selection from the first of the body keyspolicy,name,selected,select; the value may be a policy name or a numeric index as a string. The special group nameGLOBALsets the global selected policy.PUT /api/moderequires a JSON number:{"mode": 0}—0rule,1direct,2proxy.PUT /api/features/mitmandPUT /api/features/record-traffictake{"enabled": true}.GET /api/logsacceptssince(Unix seconds); the in-memory buffer keeps the most recent 1000 entries, each{timestamp, wall_time, level, section, message}—timestampandwall_timecarry the same Unix-seconds value, nudged forward when the clock does not advance so entries stay strictly ordered.GET /api/healthreports what the engine is holding and why it last refused something, plus how the previous run ended (clean,unclean, orsuspected_memory— see Troubleshooting).GET /api/eventsserves the in-memory ring;?persisted=1reads this run's stored history instead, and answersavailable: falsewhen record keeping is off.GET /api/tailscaleanswersstate: "idle"when no[Tailscale]section is configured — that is a normal answer, not an error.- The
POST /api/diagnostics/*probes take their arguments from the query string or a JSON body, are bounded to ten seconds, and answer exactly once.url-test/:policyrejects a policy name that is not defined rather than measuringREJECT. GET /api/scriptslists the enabled generic scripts as{name, type};POST /api/scripts/runtakesnamefrom the body or the query string, is bounded like the diagnostics probes, and answers{name, timedOut, result}—timedOuttells a script that never called$doneapart from one that ran to completion. A name that is not an enabled generic script is a404.POST /api/rules/matchanswers where a request would go, without opening a connection. It takeshost(or aurlto take the host and port from; aURL-REGEXrule is tested against thaturlexactly as written, although in real traffic it only ever sees plainhttp://requests),port(default 443), and optionalip,protocol,process,process_path,src_ip,src_port,in_port,in_type,in_user,in_name,network,ssid,bssid,from_tun,user_agent— an unknown field is refused with the accepted list rather than ignored. The reply carries thematchedrule (its line, type, the policy it names and theresolved_policya group currently points at), thepolicy,need_resolve, and therule_countandmatch_generationthe answer was computed against. Because a domain is matched twice — once on the name and again once an address is known —passesholds one entry per pass; without aniponly the pre-resolution pass exists and the reply says so innote. Addexplain=truefor the candidate rules that could also have matched, up to 50, with a count of the rule sets that were not expanded.POST /api/config/validateparses a configuration and throws it away: the running engine adopts nothing. Send it as{"configuration": "<full text>"}or as the raw text. The reply isvalid,error_count,advisory_count,rule_count,policy_countand anerrorslist of{line, severity, content, error}—severityseparates a line that was refused from one accepted with a caveat, andcontentis the offending line redacted, since a bad[Proxy]line usually carries the password that made it bad. Use it beforePUT /api/config, which restarts the run you are debugging.GET /api/loglevelreports the currentlevel, thenslog_level, thesectionsbeing written to file, and theavailable_levels/available_sectionsyou may set.PUT(orPATCH) takeslevel,sections, or both:sectionsis an array of section names or the string"all", and an empty array is refused — to stop logging uselevelnone. Changing the level this way does not restart the run, which is the point:loglevel = verbosein the file needs a reload, and a reload loses the thing you were trying to see.GET /api/rulesalso reportsrewrite_hits— every rewrite or Mock Response rule that has fired this run, with a count. A rule that does not appear there has never matched, which is the usual explanation for a rewrite that seems to do nothing. The table tracks up to 512 distinct rules and reportsrewrite_hit_dropped_rulesfor any beyond that.ruleslists every rule the matcher walks, in order: the automatic Tailscale rules, then module rules, then the configuration's[Rule]section, ending withFINAL;rule_regionsnames each line's origin, index for index (front,module,configuration).POST /api/rewrites/:familytakes{"rule": "<configuration line>"}— the same text you would write in the file. A line that does not parse is refused with400rather than stored as a rule that can never match.:familyis one ofurl-rewrite,header-rewrite,body-rewrite,mock. Rules added this way live in the running engine and are not written back to the configuration file.GET /api/connections/export?format=harreturns a HAR 1.2 document, andPOST /api/diagnostics/bundlereturns a zip — both are files, so unlike every other endpoint they are not wrapped in the{"ok": ..., "data": ...}envelope.exportacceptssource(current, the default, orhistory),limit(default 100, maximum 300),ids, andbodies=1to include the captured payloads. Each entry carries a_klobject with what HAR has no field for: the chosen policy, the matched rule, and the rewrites that fired.- A chained connection's record in
GET /api/connectionsandGET /api/connections/historycarrieschainPath, the path from this device to the exit, as inAirport/HK-01 → Landing; it is empty for a connection that used a single policy, and the HAR export carries it as_kl.chain. In the per-policy counters ofGET /api/traffic, an upstream also counts the bytes it carried for chained connections; the global totals count them once.
Example — read the status, then switch a policy group:
curl -H "Authorization: Bearer your-secret-token" http://127.0.0.1:9090/api/status
{"ok":true,"data":{"running":true,"outbound_mode":0,"mitm":false, ...}}
curl -X PUT -H "Authorization: Bearer your-secret-token" \
-d '{"policy": "ProxyB"}' http://127.0.0.1:9090/api/policies/MainGroup
{"ok":true,"data":{"outbound_mode":0,"selectable_groups":[...], ...}}
Example — ask where a request would go, before making it:
curl -X POST -H "Authorization: Bearer your-secret-token" \
-d '{"url": "https://api.example.com/v1/orders", "explain": true}' \
http://127.0.0.1:9090/api/rules/match
{"ok":true,"data":{"policy":"MainGroup","matched":{"rule":"DOMAIN-SUFFIX,example.com,MainGroup", ...}, ...}}
Narrow the log to one subsystem while reproducing, without restarting the run:
curl -X PUT -H "Authorization: Bearer your-secret-token" \
-d '{"level": "verbose", "sections": ["MitM", "DNS"]}' \
http://127.0.0.1:9090/api/loglevel
Note: This feature is disabled by default. Every response uses the envelope
{"ok": true, "data": {...}}on success and{"ok": false, "error": {"code": "...", "message": "..."}}on errors; request bodies are limited to 1 MB.
PUTendpoints also acceptPATCH. Clash-compatible alias paths are available for third-party dashboards:/version,/traffic,/connections,/configs,/proxies,/rules— the paths are reachable, but responses use Chute's envelope and field names rather than Clash's schema (/versionreturns{"name", "run_id", "egress_probe"}, whereegress_probeisnetwork_addresson the Apple engines andegress_ipon Android), so Clash dashboards will not work out of the box.
Proxy Test URL
[General]
proxy-test-url = http://cp.cloudflare.com/generate_204
test-timeout = 3
proxy-test-url is the probe URL of every url-test, fallback and load-balance group that does not set its own url, and test-timeout (seconds) the timeout of every such group without a timeout. Groups keep following these keys after the configuration is saved; only a url or timeout written on the group line overrides them. An invalid proxy-test-url is a configuration error. internet-test-url is used by the Web Console's direct Internet test, fetched without a proxy; when omitted, the engine uses its built-in success URL.
UDP Through a Policy Without UDP
udp-policy-not-supported-behaviour = DIRECT
What happens to a UDP datagram whose policy cannot relay UDP, such as a plain HTTP proxy: REJECT (the default) drops it; DIRECT sends it directly instead. block-quic is decided first, on the policy the rule chose: with block-quic = auto, QUIC headed for a proxy that cannot relay UDP is rejected rather than sent directly. Other UDP still falls back to DIRECT. UDP of a chained policy that cannot go through its upstream is handled the same way, and so is a DoQ or DoH3 upstream that follows the outbound mode into a policy without UDP: REJECT skips that upstream, DIRECT asks it directly.
Front Proxy
[General]
global-underlying-proxy = Airport
Sends every proxy policy that has no underlying-proxy of its own through the named policy or group — what Shadowrocket calls a front proxy (前置代理), which it sets only in its app. The policies of proxy providers are included. Left as they are: every policy a connection through the front policy itself can pass — its members, their upstreams and the hops of a relay among them — so the front policy never runs through itself; a policy that writes underlying-proxy=DIRECT, which opts out; policy groups, whose members decide; and DIRECT, REJECT and TAILSCALE. Leave the key out, or write DIRECT, to turn it off. A name that is not defined refuses every policy it would cover rather than letting it connect directly.
close-if-proxy-chain-missing (Shadowrocket) is read and written back when the profile is saved. Chute always does what its true does: a chained policy whose upstream is missing is refused. false — Shadowrocket's default, which skips the missing hop and connects to the node directly — is not followed, and a notice says so once.
Client Fingerprint
global-client-fingerprint = chrome
Sets the TLS client fingerprint for every policy that does not carry its own fingerprint, the policies a proxy provider supplies included. A policy's own value always wins, so this is a default rather than an override.
Supported values are chrome, firefox, safari and ios, plus edge, 360,
qq, android and random, which are all handled as Chrome, and the other
names listed under fingerprint.
An unrecognised value is ignored with the warning Ignoring unsupported global-client-fingerprint '<value>',
and the platform TLS stack is used.
Default: empty, meaning the platform TLS stack. It always reaches ShadowTLS policies, and reaches VLESS ones that set
tls=trueorreality=true, Trojan and VMess ones that settls=true, and Shadowsocks ones that set bothws=trueandtls=true; a policy on the gRPC transport needstls=truewritten as well. ShadowsocksR policies never read it.