Configuration
The full reference is docs/config.yaml in the repository: every key with example values. Its comments are still mostly in Chinese, as upstream.
The config format is shared with mihomo, so the mihomo documentation on keys and the controller API applies too.
Sections
Section titled “Sections”| Keys | What they set |
|---|---|
mixed-port, port, socks-port, redir-port, tproxy-port |
Local proxy ports. |
allow-lan, bind-address, authentication, lan-allowed-ips |
LAN access and authentication. |
mode, log-level, ipv6, find-process-mode |
General behaviour. |
log-format, log-timestamp-format |
Log format: text (the default) or json, one object per line for Loki and ELK. |
tcp-concurrent, tcp-connect-timeout |
Outbound TCP dialing: race all resolved IPs; total connect budget in milliseconds (default 5000, range 100–600000). |
external-controller, external-ui, secret |
The controller API and web UI; a network address needs a secret, see Controller security. |
user-stats |
Per-user traffic accounting for inbounds (on by default), see User traffic. |
user-limits |
Limits of inbound users: speed, connections, addresses, quota, expiry, see User limits. |
metrics, health-without-auth |
Prometheus metrics (GET /metrics) and the /healthz, /readyz probes, see Metrics and health. |
egress-guard |
Keeps the users of the server inbounds away from the private, local and cloud metadata addresses through DIRECT (on by default), see Server without root. |
tun |
The TUN device and network stack. On Windows see TUN on Windows. |
dns, hosts |
DNS servers (DNSCrypt v2 sdns:// stamps included), fake IP, policies for DNS queries. |
dns.serve-expired, dns.serve-expired-ttl, dns.serve-expired-client-timeout |
Serving expired cache entries while they refresh (RFC 8767). By default an expired entry is answered at once and refreshed in the background, as before; a TTL and client timeout give RFC 8767 behaviour. |
sniffer |
Domain detection from traffic (TLS, HTTP, QUIC) and BitTorrent detection for the PROTOCOL rule; skip-threshold sets after how many failures a destination is no longer sniffed. |
proxies, proxy-groups, proxy-providers |
Outbound proxies, selection groups and remote proxy sets; type: chain groups for cascades, see Chains; type: desync for direct connections in networks with DPI filtering, see Desync outbound. |
rules, sub-rules, rule-providers |
Routing rules and remote rule sets. |
listeners, tunnels |
Extra inbounds, including server protocols. TCP listeners can listen on a unix socket: listen: unix:/path, with the mode set by unix-mode. They also take proxy-protocol: true: every TCP connection must then start with a PROXY protocol v1 or v2 header, whose client address becomes the source address (logs, rules, per-user IP limits). Only the peers in proxy-protocol-trusted (loopback by default) may send it; anything else is closed. UDP is not affected. |
geox-url, geo-auto-update |
Where and how often to update geodata. |
TUN on Windows
Section titled “TUN on Windows”With auto-route on Windows, the core’s own connections go out bound to the egress interface: the default interface with auto-detect-interface (on by default), or the interface of interface-name. Windows ignores that binding when Forwarding or WeakHostSend is on for the interface, and the connections loop back into the TUN: nothing works, DNS fails and the log fills with loopback rejects. Internet Connection Sharing and Mobile Hotspot turn Forwarding on for the shared uplink and may leave it on after they stop.
The core does not change these flags, since turning them off stops the sharing. It checks them on the egress interfaces (never on the TUN itself), for IPv4 and IPv6, on every network change and every 5 seconds, and reports when they turn on: one warning in the log, naming the interface, the flags and the family, and a tun.forwarding event with state: on, so an app can offer to fix it. When the flags are off again, or the interface is no longer the egress one, it logs an info line and sends state: cleared. To fix it, turn both flags off as administrator:
Set-NetIPInterface -InterfaceAlias "Wi-Fi" -Forwarding Disabled -WeakHostSend DisabledSharing that connection through Mobile Hotspot or ICS stops working until the flags are turned on again.
Rules at runtime
Section titled “Rules at runtime”PUT /rules with the body {"rules": ["DOMAIN,a.com,DIRECT", "MATCH,PROXY"]} replaces the top level rules without a config reload. The lines are written as in rules: and parsed by the same code: targets are proxies and groups, and RULE-SET, SUB-RULE and logic rules work. The whole list is validated before the swap; a bad line gives 400 with the error and leaves the rules unchanged. Proxies of proxy-providers cannot be targets (as in the config): use a group. sub-rules and rule-providers stay as they are.
The replacement lives in memory only: the next config reload (PUT /configs, systemctl reload, a restart) brings back the rules of the file. The response is {"count": N, "ephemeral": true}.
Testing
Section titled “Testing”citrus -t -f ~/.config/citrus/config.yamlThe command parses the config and exits without starting anything. The packaged service re-reads the config on systemctl reload citrus.
Reloading
Section titled “Reloading”PUT /configs (with or without force=true) and systemctl reload citrus apply the access policy of the new config at once: allow-lan, bind-address, skip-auth-prefixes, lan-allowed-ips, lan-disallowed-ips, authentication, the built-in ss-config and tuic-server. Without force=true the built-in port, socks-port, redir-port, tproxy-port and mixed-port listeners keep the ports they run on (a client may have changed them with PATCH /configs); they are rebound when allow-lan or bind-address changed.
