Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Config File

Full reference for the config.toml file with all fields, defaults, and accepted values.

File Location

Default: ~/.config/xrat/config.toml

Resolution order:

  1. --config <path> CLI flag
  2. XRAT_PATH/config.toml environment variable
  3. ~/.config/xrat/config.toml

Top-Level Structure

[paths]
[database]
[server]
[runtime]
[routing]
[geo]
[parser]
[dns]
[testing]

[paths]

Binary paths for proxy engines. All fields are optional (defaults to $PATH).

[paths]
# Database file path (deprecated, use [database.sqlite].path)
database = "db.sqlite"

# Binary paths (optional, defaults to PATH lookup)
xray = "/usr/local/bin/xray"
v2ray = "/usr/local/bin/v2ray"
sing_box = "/usr/local/bin/sing-box"
FieldTypeDefaultDescription
databasestring-Database path (deprecated, use [database.sqlite].path)
xraystringxrayXray-core binary path
v2raystringv2rayV2Ray binary path
sing_boxstringsing-boxsing-box binary path

[database]

Database backend selection and connection settings.

[database]
backend = "sqlite"  # "sqlite" | "postgres"

[database.sqlite]
path = "db.sqlite"

[database.postgres]
user = { env = "XRAT_POSTGRES_USER" }
password = { env = "XRAT_POSTGRES_PASSWORD" }
host = "localhost"
port = 5432
db_name = "xrat"
max_connections = 10
min_connections = 1
connect_timeout_secs = 10
FieldTypeDefaultDescription
backendenumsqlitesqlite or postgres
[sqlite].pathstringdb.sqliteSQLite database file path
[postgres].userstring/env-PostgreSQL username
[postgres].passwordstring/env-PostgreSQL password
[postgres].hoststringlocalhostPostgreSQL host
[postgres].portinteger5432PostgreSQL port
[postgres].db_namestring-PostgreSQL database name
[postgres].max_connectionsinteger10Connection pool max size
[postgres].min_connectionsinteger1Connection pool min size
[postgres].connect_timeout_secsinteger10Connection timeout

[server]

HTTP API server configuration.

[server]
enabled = false
host = "127.0.0.1"
port = 18203
key = { env = "XRAT_API_KEY" }
pac_enabled = true
pac_allowed_hosts = ["localhost", "127.0.0.1", "::1"]
FieldTypeDefaultDescription
enabledbooleanfalseEnable daemon-hosted API
hoststring127.0.0.1Bind host
portinteger18203Bind port
keystring/env-API key for authenticated routes
pac_enabledbooleantrueServe /proxy.pac
pac_allowed_hostsstring[]["localhost", "127.0.0.1", "::1"]Allowed Host headers for /proxy.pac

/proxy.pac is unauthenticated because many PAC consumers cannot send auth headers. Keep host = "127.0.0.1" for local use. If you bind the server to 0.0.0.0, add only trusted local DNS names to pac_allowed_hosts.


[runtime]

Runtime engine and proxy process configuration.

[runtime]
engine = "xray"     # "xray" | "v2ray" | "sing-box"
replace_active_session = true
FieldTypeDefaultDescription
engineenumxrayManaged runtime engine. Hy2 configs auto-select sing-box; non-Hy2 configs use Xray/V2Ray unless supported by the selected engine.
replace_active_sessionbooleantrueAuto-disconnect on new connect

[runtime.rotation]

Proxy auto-rotation settings.

[runtime.rotation]
enabled = true
interval_secs = 1800
health_trigger_enabled = true
health_failure_threshold = 3
cooldown_secs = 300
test_concurrency = 0
test_stages = ["icmp", "real_delay"]
refresh_subscriptions = false
FieldTypeDefaultDescription
enabledbooleantrueEnable scheduled and health-triggered rotation
interval_secsinteger1800Rotation interval in seconds
health_trigger_enabledbooleantrueRecover when the active runtime becomes unhealthy
health_failure_thresholdinteger3Consecutive proxied HTTP failures required before recovery
cooldown_secsinteger300Per-config health-failure cooldown in seconds
test_concurrencyinteger0Fresh candidate test workers (0 = auto)
test_stagesstring[]["icmp", "real_delay"]Candidate test stages; ICMP alone does not qualify a config
refresh_subscriptionsbooleanfalseRefresh URL subscriptions before automatic candidate testing

Process exit and configured-inbound loss trigger immediate recovery. Proxied HTTP failures use the threshold above and the request behavior configured under [testing.real_delay], even when the real-delay test stage is disabled. The settings modal exposes this field as Failure threshold with the same help and validation as config.toml.


[runtime.log]

Proxy process logging.

[runtime.log]
enabled = true
mask = "none"     # "quarter" | "half" | "full" | "none"
dir = "logs"
dns_log = false
level = "warning" # "debug" | "info" | "warning" | "error"
keep = true
FieldTypeDefaultDescription
enabledbooleantrueEnable logging to files
maskenumnoneIP address masking
dirstringlogsLog directory
dns_logbooleanfalseEnable DNS query logging
levelenumwarningLog level
keepbooleantrueKeep logs after session stop

[runtime.socks]

SOCKS5 inbound configuration.

[runtime.socks]
enabled = true
host = "0.0.0.0"
port = 18200
udp = true
auth = { enabled = true, username = "xrat", password = { env = "XRAT_SOCKS_PASSWORD" } }
FieldTypeDefaultDescription
enabledbooleantrueEnable SOCKS inbound
hoststring0.0.0.0Bind address
portinteger18200Bind port
udpbooleantrueEnable UDP support
auth.enabledbooleanfalseEnable authentication
auth.usernamestringxratSOCKS username
auth.passwordstring/env-SOCKS password

[runtime.http]

HTTP proxy inbound configuration.

[runtime.http]
enabled = false
host = "0.0.0.0"
port = 18201
FieldTypeDefaultDescription
enabledbooleanfalseEnable HTTP inbound
hoststring0.0.0.0Bind address
portinteger18201Bind port

[runtime.shadowsocks]

Shadowsocks inbound configuration.

[runtime.shadowsocks]
enabled = false
host = "0.0.0.0"
port = 18202
method = "aes-128-gcm"
password = { env = "XRAT_SHADOWSOCKS_PASSWORD" }
network = "tcp,udp"
FieldTypeDefaultDescription
enabledbooleanfalseEnable Shadowsocks inbound
hoststring0.0.0.0Bind address
portinteger18202Bind port
methodstringaes-128-gcmEncryption method
passwordstring/env-Shadowsocks password
networkstringtcp,udpNetwork type

[runtime.sniffing]

Traffic sniffing configuration.

[runtime.sniffing]
enabled = true
dest_override = ["http", "tls", "quic"]
route_only = true
metadata_only = false
domains_excluded = []
ips_excluded = []
FieldTypeDefaultDescription
enabledbooleantrueEnable traffic sniffing
dest_overridestring[]["http", "tls", "quic"]Protocols for destination override
route_onlybooleantrueOnly sniff for routing
metadata_onlybooleanfalseOnly sniff metadata
domains_excludedstring[][]Excluded domains
ips_excludedstring[][]Excluded IPs

[runtime.stats]

Traffic-stats endpoint exposed by the managed runtime and sampled by the TUI stats tab. For xray/v2ray this enables the gRPC StatsService behind an api inbound; for managed sing-box it binds the Clash API controller (experimental.clash_api). Both bind an extra localhost port, gated by enabled. Probe and stats-disabled runtime configs are unchanged.

[runtime.stats]
enabled = true
host = "127.0.0.1"
port = 10085
FieldTypeDefaultDescription
enabledbooleantrueEnable the stats endpoint and TUI stats poller
hoststring"127.0.0.1"Listen host for the stats controller
portinteger10085Listen port for the stats controller

[runtime.mux]

Client-side Mux (multiplexing) for generated Xray outbounds, applied to the proxy outbound of both runtime and probe configs. Disabled by default: Mux reduces TCP handshakes but commonly hurts throughput (downloads, video, speed tests), so enable it only for workloads dominated by many short-lived requests.

[runtime.mux]
enabled = false
concurrency = 8
xudp_concurrency = 0
xudp_proxy_udp443 = "reject"
FieldTypeDefaultDescription
enabledbooleanfalseEmit a mux object on the proxy outbound
concurrencyinteger8Logical connections per Mux session. 0 = Xray default (8); 1..=128; -1 disables TCP Mux
xudp_concurrencyinteger0XUDP aggregation concurrency. 0 = legacy path; 1..=1024; -1 opts UDP out of Mux
xudp_proxy_udp443string"reject"QUIC/UDP 443 handling under XUDP: reject, allow, or skip

[runtime.fragment]

TCP fragmentation for generated Xray outbounds. When enabled, the proxy outbound is chained through a freedom outbound (sockopt.dialerProxy) that splits early outgoing TCP writes (typically the TLS ClientHello). This is a network-circumvention feature whose effect depends on network, transport, and destination — it can help against some SNI-based filtering but may also hurt. Disabled by default.

[runtime.fragment]
enabled = false
packets_mode = "tlshello"
packets = [1, 3]
length = [100, 200]
interval = [10, 20]
FieldTypeDefaultDescription
enabledbooleanfalseEmit the freedom fragment outbound and chain the proxy through it
packets_modestring"tlshello""tlshello" (fragment the TLS ClientHello) or "range" (use packets)
packetsinteger[][1, 3]Write range [min, max] (min ≥ 1, min ≤ max). Used only in range mode
lengthinteger[][100, 200]Byte length range [min, max] (min ≥ 1, min ≤ max)
intervalinteger[][10, 20]Millisecond delay range [min, max] (min ≤ max)

[runtime.network]

Interface and source binding for managed runtime traffic.

[runtime.network]
interface = ""
bind_address = ""
mark = 0
listen_interface = ""
FieldTypeDefaultDescription
interfacestring""Outbound interface to bind egress to (Xray sockopt.interface, SO_BINDTODEVICE on Linux)
bind_addressstring""Outbound source IP. The Xray engine cannot bind a source address and ignores this (a warning is logged); validated for shape only
markinteger0fwmark applied to outbound sockets (Xray sockopt.mark). 0 = unset
listen_interfacestring""Bind managed inbounds (socks/http/shadowsocks) to this interface’s address instead of their host

Interface binding (interface, mark) and listen_interface are Linux-focused. interface requires a real device name; listen_interface must resolve to a bindable address or the runtime fails to launch. System-wide TUN capture is tracked separately and not provided here.


[routing]

Routing configuration.

[routing]
domain_strategy = "IPIfNonMatch" # "AsIs" | "IPIfNonMatch" | "IPOnDemand"

[routing.direct]
domain = []
ip = []
geosite = []
geoip = []

[routing.block]
domain = []
ip = []
geosite = []
geoip = []
FieldTypeDefaultDescription
domain_strategyenumIPIfNonMatchXray/V2Ray domain resolution strategy
[direct].domainstring[][]Domains routed without the proxy
[direct].ipstring[][]IP addresses/CIDRs routed without the proxy
[direct].geositestring[][]Xray/V2Ray geosite categories routed directly
[direct].geoipstring[][]Xray/V2Ray GeoIP categories routed directly
[block].domainstring[][]Domains rejected by the runtime
[block].ipstring[][]IP addresses/CIDRs rejected by the runtime
[block].geositestring[][]Xray/V2Ray geosite categories rejected
[block].geoipstring[][]Xray/V2Ray GeoIP categories rejected

These rules apply to managed sessions started by connect, rotation, or the daemon. Probe and test configs remain proxy-only. Xray/V2Ray receives separate domain and IP rules, followed by freedom and blackhole outbounds as needed. Direct rules precede block rules, so direct wins when both lists match.

For sing-box, domain supports bare keyword rules and the full:, domain:, keyword:, and regexp: forms; ip supports addresses and CIDRs. sing-box geosite/geoip rule-set translation is not yet available, so xrat rejects those entries instead of silently omitting them. domain_strategy is Xray/V2Ray-only.

The generated PAC file inlines only curated domain entries and IPv4 CIDRs from ip lists. geosite and geoip lists stay in the proxy engine config and are not expanded into PAC. PAC support remains a subset of managed-runtime routing.


[geo]

GeoIP/geosite asset management.

[geo]
auto_update = false
update_interval_hours = 168

[[geo.profiles]]
name = "default"
geosite = "https://example.com/geosite.dat"
geoip = "https://example.com/geoip.dat"

[[geo.profiles]]
name = "local"
geosite = "geo/local/geosite.dat"
geoip = "geo/local/geoip.dat"
FieldTypeDefaultDescription
auto_updatebooleanfalseEnable periodic geo asset updates
update_interval_hoursinteger168Update interval in hours
[[profiles]].namestring-Profile name
[[profiles]].geositestring-Geosite file path or URL
[[profiles]].geoipstring-GeoIP file path or URL

[parser]

Xray JSON schema validation mode.

[parser]
parse_mode = "strict" # "strict" | "lenient" | "auto"
FieldTypeDefaultDescription
parse_modeenumstrictXray JSON validation mode

[dns]

DNS configuration for generated managed-runtime configs and Xray probe configs used by xrat test/scan. Xray/V2Ray receives the complete Xray DNS object. sing-box receives modern typed DNS servers and only the options that have a faithful equivalent. Probe configurations remain proxy-only for routing; their Xray configs include this DNS object when the settings are non-default.

[dns]
query_strategy = "UseIPv4" # "UseIP" | "UseIPv4" | "UseIPv6" | "UseSystem"
servers = [
    "8.8.8.8",
    "https://1.1.1.1/dns-query",
]
use_system_hosts = true
disable_cache = false
disable_fallback = false
enable_parallel_query = true

[dns.hosts]
"full:example.test" = "127.0.0.1"
"full:lan.test" = ["192.168.1.10", "192.168.1.11"]
FieldTypeDefaultDescription
query_strategyenumUseSystemDNS query strategy
serversstring[]-DNS server list
use_system_hostsbooleantrueUse system hosts file
disable_cachebooleanfalseDisable DNS cache
disable_fallbackbooleanfalseDisable fallback DNS
enable_parallel_querybooleantrueEnable parallel queries
[dns.hosts]map-Static DNS entries

Xray/V2Ray accepts the four documented query_strategy values and the documented Xray server URI forms. The generated JSON uses Xray field names such as queryStrategy, useSystemHosts, and disableFallback.

sing-box uses typed local, udp, tcp, tls, quic, https, h3, and hosts servers. A generated sing-box DNS block requires UseIPv4 or UseIPv6; UseIP and UseSystem have no exact modern sing-box equivalent and are rejected when custom DNS settings would be emitted. Plain and full: host keys are supported; domain:, keyword, regexp, geosite, and other advanced host keys remain Xray/V2Ray-only. disable_fallback and enable_parallel_query = false are also Xray/V2Ray-only. Unsupported sing-box input fails before the managed process is started.


[mmdb]

Dedicated MaxMind MMDB asset configuration, separate from [geo] routing assets.

[mmdb]
dir = "mmdb"
download_url = "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/{edition}.mmdb"
timeout_secs = 60
default_editions = ["country", "city", "asn"]
auto_update = false
update_interval_hours = 168
FieldTypeDefaultDescription
dirstringmmdbMMDB directory (absolute, or relative to the xrat runtime root)
download_urlstringhttps://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/{edition}.mmdbDownload URL template. {edition} is replaced with edition name
timeout_secsinteger60HTTP request timeout for downloads
default_editionsstring[]["country", "city", "asn"]Editions downloaded when no --edition or --all flag given
auto_updatebooleanfalseEnable periodic update checks
update_interval_hoursinteger168Update interval in hours

The dir field is resolved relative to the xrat runtime root (XRAT_PATH when set, otherwise the default app root). Absolute paths are used as-is. The default per-edition MMDB paths under [testing.geoip] also resolve through this MMDB directory; custom relative per-edition paths are resolved relative to the config file directory.


[testing]

Connection testing configuration.

[testing]
concurrency = 0  # 0 = auto
order = ["icmp", "real_delay", "download"]
failure_policy = "continue"  # "continue" | "skip_remaining" | "mark_failed"

[testing.real_delay]
enabled = true
url = "https://www.gstatic.com/generate_204"
timeout = 10_000
# Omit both acceptance fields to accept 200-299.
# accepted_status_codes = [200, 204]
# accepted_status_ranges = ["300-399"]
follow_redirects = true

[testing.icmp]
enabled = true
timeout = 3000
attempts = 3

[testing.download]
enabled = false
url = "https://cachefly.cachefly.net/50mb.test"
timeout = 30_000

[testing.tcp]
enabled = true
timeout = 5000

[testing.geoip]
enabled = false
backend = "mmdb"
fallback = "none"
country_path = "mmdb/GeoLite2-Country.mmdb"
city_path = "mmdb/GeoLite2-City.mmdb"
asn_path = "mmdb/GeoLite2-ASN.mmdb"

[testing.geoip.remote]
provider = "ipwhois"
endpoint = ""
timeout_ms = 5000
api_key = ""
rate_limit_per_minute = 30

[testing.geoip.cache]
enabled = true
ttl_secs = 86400
max_entries = 10000

Real-delay status codes and inclusive ranges are combined with OR semantics. Setting either acceptance field replaces the default 200-299 range. Valid codes and range endpoints are 100-599. When follow_redirects is enabled, xrat follows at most 10 redirects and checks the terminal response; when it is disabled, xrat checks the initial response so configured 3xx statuses can pass.

SectionFieldTypeDefaultDescription
[testing]concurrencyinteger0Test workers (0 = auto)
[testing]orderstring[]["icmp", "real_delay", "download"]Stage execution order; accepted: icmp, tcp, real_delay, download
[testing]failure_policyenumcontinueBehavior on stage failure
[icmp]enabledbooleantrueEnable ICMP stage
[icmp]timeoutinteger3000ICMP timeout (ms)
[icmp]attemptsinteger3ICMP attempt count
[tcp]enabledbooleantrueEnable TCP stage
[tcp]timeoutinteger5000TCP timeout (ms)
[real_delay]enabledbooleantrueEnable real-delay stage
[real_delay]urlstringhttps://www.gstatic.com/generate_204Test URL
[real_delay]timeoutinteger10000HTTP request timeout (ms)
[real_delay]accepted_status_codesinteger[]-Exact accepted HTTP status codes
[real_delay]accepted_status_rangesstring[]- (effective 200-299)Inclusive accepted ranges in START-END form
[real_delay]follow_redirectsbooleantrueFollow up to 10 redirects before checking status
[download]enabledbooleanfalseEnable download stage
[download]urlstring-Download URL
[download]timeoutinteger30000Download timeout (ms)
[testing.geoip]enabledbooleanfalseEnable GeoIP enrichment
[testing.geoip]backendenummmdbLookup backend: mmdb, ipwhois, ip-api, chain
[testing.geoip]fallbackenumnoneFallback backend when primary is chain: ipwhois, ip-api, none
[testing.geoip]country_pathstringmmdb/GeoLite2-Country.mmdbCountry MMDB path (relative to config)
[testing.geoip]city_pathstringmmdb/GeoLite2-City.mmdbCity MMDB path (relative to config)
[testing.geoip]asn_pathstringmmdb/GeoLite2-ASN.mmdbASN MMDB path (relative to config)
[remote]providerenumipwhoisRemote provider: ipwhois, ip-api
[remote]endpointstring"" (uses provider default)Remote API endpoint override
[remote]timeout_msinteger5000Remote request timeout in milliseconds
[remote]api_keystring""API key (provider-specific)
[remote]rate_limit_per_minuteinteger30Max remote requests per minute
[cache]enabledbooleantrueEnable in-memory caching
[cache]ttl_secsinteger86400Cache entry TTL in seconds
[cache]max_entriesinteger10000Maximum cache entries

Upload tests are enabled per invocation with xrat test --upload-url <url>. There is no [testing.upload] config section; --upload-timeout overrides the default 30-second upload timeout.


Environment Variable References

Sensitive fields accept environment variable references:

# Literal value
password = "my-secret-password"

# Environment variable
password = { env = "XRAT_SOCKS_PASSWORD" }

Supported on these fields:

SectionField
[server]key
[runtime.socks]auth.password
[runtime.shadowsocks]password
[database.postgres]user
[database.postgres]password

Example Config

See testdata/config.example.toml in the repository for a complete example with all sections and comments.