Skip to content

etemenanki-app configuration

Every table of the etemenanki-app configuration file on one page. The same tables appear on the guide pages, which explain how the keys interact; follow the link under each heading.

config.toml · Explained in The configuration file

KeyTypeRequiredDefaultDescription
logtableno{}Logging, written as [log]. Holds only level. See the [log] section of the configuration file page.
dnstableno{}Name resolution for outbounds and balancer probes, written as [dns]. Without it, etemenanki-app uses the host resolver behind an always-on cache.
inboundarray of tablesno[]The listeners, one [[inbound]] block each. Written in the singular: [[inbounds]] is an unknown field. Zero inbounds is valid, although such a process accepts nothing. Tags must be unique among inbounds.
outboundarray of tablesyes—Where flows leave, one [[outbound]] block each. Written in the singular. At least one is required, otherwise the build fails with config defines no outbounds. The first [[outbound]] in the file is the default route unless [route] sets default. Tags must be unique among outbounds.
balancerarray of tablesno[]Groups of interchangeable outbounds, one [[balancer]] block each. Written in the singular. A balancer tag can be used wherever an outbound tag is accepted, so it must not repeat an outbound tag or another balancer tag.
routetableno{}Routing, written as [route], with its rules as [[route.rule]] blocks (singular; [[route.rules]] is an unknown field). The first matching rule wins. Without [route], every flow goes to the first [[outbound]].

[log] · Explained in The configuration file

KeyTypeRequiredDefaultDescription
levelstringno"info"A tracing EnvFilter directive, the same syntax as RUST_LOG: a level (off, error, warn, info, debug, trace, or a number from 0 to 5, case-insensitive), optionally followed by per-target overrides such as "warn,etemenanki_protocols=debug". A RUST_LOG variable that parses as a filter replaces this value, even an empty one, which enables nothing. The value is not validated: a word that is not a level, such as "warning", is read as a target name and silences every log line, errors included. An empty value, or one in which no directive parses, logs errors only. Read once at startup and not applied on reload.

[[inbound]] · Explained in Inbounds

KeyTypeRequiredDefaultDescription
tagstringyes—Name of the inbound. It must be unique among inbounds, otherwise the config fails with duplicate inbound tag. Routing rules match it with inbound_tag, and log lines use it. Inbound and outbound tags are separate namespaces.
protocolstring (enum)yes—The protocol clients speak: socks, http, trojan, vless, vmess, shadowsocks, hysteria2 (aliases hysteria and hy2) or tun. Values are case-sensitive. wireguard is refused with wireguard cannot be used as an inbound (no server implementation); anything else fails with unknown protocol.
listenstringno"127.0.0.1"Address to bind. Defaults to loopback on purpose; write 0.0.0.0 or :: to accept connections from the network. Write IPv6 addresses bare (::, not [::]). A host name is resolved when the listener binds. A value starting with / is a Unix socket path; a value starting with @ (abstract socket) is refused. Must be absent for tun.
portu16depends—Port to bind, from 0 to 65535. Required when listen is absent, an IP address or a host name (port is required). Must be absent for a Unix socket (a unix socket listen has no port; remove port) and for tun. 0 is accepted and lets the kernel pick a free port. For hysteria2 this is a UDP port.
streamtableno—Transport under the protocol: network (tcp, tls, ws, grpc), security and the tls, ws and grpc subtables. Only http, trojan, vless and vmess on an IP listener use it. In every other case a network other than tcp or a security other than none is refused.
address_familystringno—Accepted so that inbound and outbound tables share a shape, but it has no effect on an inbound. The value is not validated.
sniffingboolnotrueRead a TLS SNI or HTTP Host from the first bytes of a flow whose destination is a bare IP, so that domain rules can match it. Flows addressed by domain are never inspected.
settingstableno{}Protocol-specific settings, described on each protocol page. Unknown keys are refused with inbound TAG: invalid settings: .... These errors carry no line number.

[inbound.settings] · protocol = "socks" · Explained in SOCKS

KeyTypeRequiredDefaultDescription
authstring (enum)no"none"How clients authenticate. "none" accepts anyone and allows SOCKS4, SOCKS4a and SOCKS5. "password" requires SOCKS5 username/password authentication (RFC 1929) and refuses SOCKS4. The match is exact and case-sensitive; any other value, including Xray's "noauth", fails with unknown socks auth.
accountsarray of tablesdepends[]The accounts accepted when auth = "password", each written as { user = "…", pass = "…" }. Both keys are required in every entry. Needed for auth = "password" to let anyone in: an empty list is accepted by the parser, but then every client is refused. If a user appears twice, the last entry wins. Ignored when auth = "none".
udpboolnotrueAccept the SOCKS5 UDP ASSOCIATE command. When false, the server answers UDP ASSOCIATE with reply code 0x07 (command not supported) and closes the connection.
udp_bindstringdepends—The IP address (IPv4 or IPv6, not a hostname) on which each association binds its UDP relay socket, and which the server reports to the client as the relay address. Without it, the relay uses the local address of the TCP connection the client opened. The relay hears only the address the client's control connection came from, so udp_bind must be in the address family clients connect over: an IPv4 address, or an IPv4-mapped IPv6 one such as ::ffff:192.0.2.10, hears only IPv4 clients, :: hears both, and any other IPv6 address hears only IPv6 clients. A UDP ASSOCIATE from a client the relay could not hear is refused with reply code 0x02 (connection not allowed by ruleset). Required when listen is a Unix socket path and udp is true, because a Unix socket has no local IP. On a Unix socket, each client's UDP ASSOCIATE must also name the exact IP address and port its datagrams will come from, in a family udp_bind hears; a request that leaves either at zero or names a domain is refused with 0x02.

[inbound.settings] · protocol = "http" · Explained in HTTP proxy

KeyTypeRequiredDefaultDescription
accountsarray of tablesno[]Accounts checked against the client's Proxy-Authorization: Basic header. Empty means an open proxy: every client is accepted without credentials. When set, a request without a matching account is answered 407.
accounts[].userstringyes—Username, case-sensitive. Must not contain :, because the Basic credential is split at its first colon; such a user can never log in. If two entries share a username, the later one wins.
accounts[].passstringyes—Password. It must match exactly, including case, and it may contain :. Both user and pass are required in every entry; a missing one fails the config with missing field.
allow_transparentboolnofalseAccept plain requests in origin-form (GET /path) and take the target from the Host header, port 80 if it has none. When false, such a request is answered 400. CONNECT requests and absolute-form http:// or https:// requests are served either way.

[inbound.settings] · protocol = "shadowsocks" · Explained in Shadowsocks

KeyTypeRequiredDefaultDescription
methodstring (enum)yes—The cipher. A value that starts with 2022- selects Shadowsocks 2022 and must be exactly 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm or 2022-blake3-chacha20-poly1305. Anything else is a legacy AEAD method, matched case-insensitively: aes-128-gcm, aes-256-gcm, chacha20-poly1305, xchacha20-poly1305, or one of their aliases. The value is not trimmed. An unknown value fails with unknown shadowsocks method or unknown shadowsocks-2022 method.
passwordstringyes—Legacy methods: the shared password, from which the key is derived. It is ignored when users is not empty, but the key must still be present. Shadowsocks 2022: a base64 pre-shared key (standard alphabet with padding, surrounding whitespace trimmed) of 16 bytes for 2022-blake3-aes-128-gcm and 32 bytes otherwise. When users is not empty, this is the identity PSK (iPSK) that every client puts first in its password. A key that is too short fails with shadowsocks-2022: PSK too short; a longer key is reduced to the first 16 or 32 bytes of its SHA-256 digest.
usersarray of tablesno[]Users on one port, each written as { password = "…", email = "…" }. clients is accepted as an alias, but not together with users. Empty means a single shared password or PSK. For Shadowsocks 2022 multi-user needs an AES-GCM method; with 2022-blake3-chacha20-poly1305 the config fails with shadowsocks-2022: multi-user requires an aes-gcm method.
users[].passwordstringyes—Legacy methods: this user's password. Shadowsocks 2022: this user's base64 PSK (uPSK), with the same length rules as password. Give every user a different value.
users[].emailstringno""A label for the user. etemenanki-app accepts it so that configs copied from Xray work as they are, but does not use it: no route rule matches on it, and no log line prints it.

[inbound.settings] · protocol = "trojan" · Explained in Trojan

KeyTypeRequiredDefaultDescription
usersarray of tablesno[]The accounts this inbound accepts, one table per user. An empty or missing list still passes --test, but then every connection is refused with trojan: invalid user. There is no top-level password key, and Xray's clients and fallbacks keys fail the config as unknown fields.
users[].passwordstringyes—The user's secret. The client sends the lowercase hex of SHA224(password), and the server looks that hash up among its users. Any string is accepted, including an empty one, so use a long random value. If two users share a password, the one listed last wins. flow and level are rejected as unknown fields.
users[].emailstringno""A label for the user, carried with each of the user's flows as its user name. It never goes on the wire and need not be an e-mail address. etemenanki-app has no route rule and no traffic counter that reads it. katana, which builds its users from the panel instead of from this table, fills the same label itself and uses it to attribute traffic to panel users.

[inbound.settings] · protocol = "vless" · Explained in VLESS

KeyTypeRequiredDefaultDescription
usersarray of tablesno[]The users allowed to connect, each identified by a UUID. An empty or missing list still builds, but then every client is refused. Xray's clients, decryption and fallbacks keys do not exist here and fail the config as unknown fields.
users[].idstringyes—The user's UUID. Accepted forms: hyphenated (11111111-2222-3333-4444-555555555555), 32 hex digits without hyphens, or the hyphenated form wrapped in braces or prefixed with a lowercase urn:uuid:; hex digits in either case. Anything else fails with invalid uuid. It is the only key of an entry: flow, email and level are rejected.

[inbound.settings] · protocol = "vmess" · Explained in VMess

KeyTypeRequiredDefaultDescription
usersarray of tablesno[]The accounts allowed to connect, one table per user. Every user has the same access. An empty list is accepted, but then every connection fails authentication. The Xray key clients is rejected as an unknown field.
users[].idstringyes—The user's UUID. Accepts the hyphenated form, 32 hex digits, {…} braces or a urn:uuid: prefix, in any letter case. Any other string fails with inbound <tag>: invalid uuid "…". This is the only key a user table accepts, so alterId, email and level are errors.

[inbound.settings] · protocol = "hysteria2" · Explained in Hysteria 2

KeyTypeRequiredDefaultDescription
cert_filepathyes—PEM certificate chain the QUIC listener presents, leaf first. It is read and parsed with rustls when the config is built, so --test catches a missing or unreadable file. The listener itself accepts a certificate without a subjectAltName, but rustls-based clients (including the hysteria2 outbound) and current upstream clients refuse one, so give it a DNS or IP SAN.
key_filepathyes—PEM private key for cert_file (PKCS#8, PKCS#1 or SEC1). It may be the same file as cert_file. Missing either key gives hysteria2 needs both cert_file and key_file.
passwordstringdepends—One credential shared by every client. Set exactly one of password and users. It must not be empty, and should be printable ASCII, because a credential with other characters never matches. The client sends it unchanged as its auth string.
usersarray of tablesdepends—Per-user credentials, { user, pass, email }. Set exactly one of password and users; an empty list counts as unset. The client sends user:pass as its auth string.
users[].userstringyes—User name. Must be non-empty, should be printable ASCII, and must not contain :, which separates name and password on the wire. It is compared case-insensitively (ASCII), so two names that differ only in case are refused.
users[].passstringyes—The user's password. Must be non-empty and should be printable ASCII. Compared exactly, and it may contain :, because the server splits the credential at the first colon.
users[].emailstringno""A label carried with the user's flows as their user name. When empty, user is used instead. It is never sent on the wire; etemenanki-app has no route rule that reads it.
udpboolnofalseRelay UDP over QUIC datagrams as well as TCP. Off by default, unlike the socks inbound. The server tells each client whether UDP is available when it authenticates.
udp_idle_timeoutu64no60Seconds a UDP association may stay silent in both directions before it is closed. Accepted range is 2 to 600. Setting it while udp is false is an error, not a no-op.
max_connectionsintegerno4096Concurrent QUIC connections this listener serves. A client beyond the limit has its handshake refused at once. Must be at least 1.
max_circuitsintegerno65536Live circuits across the whole listener, where each TCP stream and each UDP association counts as one. A stream beyond the limit is reset and a new association is dropped. Must be at least 1.
obfsstring (enum)no—Packet obfuscation beneath QUIC. The only accepted value is salamander, exact and lowercase; anything else fails with unknown obfs. Clients must use the same obfs and obfs_password.
obfs_passwordstringdepends—Pre-shared key for salamander, at least 4 bytes. Required when obfs is set; setting it without obfs is an error, so a typo in obfs cannot silently turn obfuscation off.
masqueradetableno—The HTTP/3 response given to any request that is not a valid authentication, including a wrong credential. See the masquerade table below.

[inbound.settings.masquerade] · Explained in Hysteria 2

KeyTypeRequiredDefaultDescription
statusu16no404HTTP status code of the response. Any code from 100 to 999 is accepted except 233, which is the Hysteria authentication success status and would tell the prober it had authenticated. Anything below 100 or above 999 fails with is not an HTTP status code.
bodystringno"404 page not found\n"Response body, sent as is with a matching Content-Length. The default is byte for byte what Go's http.NotFound returns.
content_typestringno"text/plain; charset=utf-8"Value of the Content-Type response header.

[inbound.settings] · protocol = "tun" · Explained in TUN

KeyTypeRequiredDefaultDescription
namestringno—Name of the interface to create, for example "etm0". When absent, the kernel picks one (tun0, tun1, …) and the start-up log names it. Linux allows at most 15 bytes; a longer name passes --test and then fails at start with device name too long. Set a fixed name if firewall rules or scripts refer to the interface.
mtuu16no1500MTU of the interface and of the userspace IP stack behind it, in bytes. The minimum is 1280, the smallest MTU IPv6 allows; a lower value fails with tun mtu must be at least 1280. Values above 65535 fail to parse.
addressarray of stringsno[]Addresses assigned to the interface, each written as "address/prefix", IPv4 or IPv6, for example "10.77.0.1/32" or "2001:db8:77::1/64". Without a prefix, the address is a host address (/32 or /128). The prefix also makes the kernel route that whole subnet into the device. Host bits are allowed here. A malformed entry fails with bad tun address.
routesarray of stringsno[]Networks routed into the interface, in strict CIDR form such as "203.0.113.0/24": every host bit must be zero, so "203.0.113.5/24" fails with bad tun route "203.0.113.5/24": host part of address was not zero. Without a prefix, an entry is a host route. The routes are installed in the main table when the device is created and disappear with it. Installed only on Linux: on other systems a non-empty list fails with tun routes are installed only on Linux; add them with the OS route tool.
udpboolnotrueRelay UDP as well as TCP. When false, every UDP packet that reaches the device is dropped without a reply.
udp_idle_timeoutu64no60Seconds without traffic after which the userspace stack retires a UDP flow (one client address and port talking to one peer). An association polls all its flows together, so traffic on any of them keeps every flow alive. An association ends when its last flow is retired, and in any case after 300 seconds without a datagram in either direction. 0 is accepted but breaks UDP: a flow is retired right after its first datagram, before a reply can arrive.
max_flowsintegerno65536Upper bound on live flows across the device: each TCP connection counts as one, and so does each UDP association (all the UDP traffic of one client address and port). At the limit, a new TCP connection or association is dropped and logged at debug level. 0 is accepted and drops every flow.

[[outbound]] · Explained in Outbounds

KeyTypeRequiredDefaultDescription
tagstringyes—Name of the outbound. Route rules, [route].default and [[balancer]].outbounds refer to it. Must be unique among outbounds (duplicate outbound tag: …), and no [[balancer]] may reuse it. Without [route].default, the first [[outbound]] in the file is the default route.
protocolstring (enum)yes—What the outbound speaks: freedom (alias direct), blackhole (alias block), socks, http, trojan, vless, vmess, shadowsocks, hysteria2 (aliases hysteria, hy2) or wireguard. Matched exactly and case-sensitively; anything else fails with unknown protocol.
serverstringdepends—Host name or IP address of the upstream server, without a port. Required by socks, http, trojan, vless, vmess, shadowsocks and hysteria2 (missing server); never used for traffic by freedom, blackhole and wireguard, where it only matters as a balancer health-probe target. Write an IPv6 address bare (2001:db8::1), without brackets. It is also the fallback for the TLS server name (including hysteria2 server_name), the WebSocket Host and the gRPC authority.
portu16depends—Port of the upstream server. Required by the same protocols as server (missing port); the others use it only as a balancer health-probe target. For hysteria2 it is a UDP port; for every other protocol, a TCP port.
streamtableno—Transport to the upstream: network (tcp, tls, ws, grpc), security and the tls, ws and grpc sub-tables. Honoured by socks, http, trojan, vless, vmess and shadowsocks. freedom, blackhole, wireguard and hysteria2 refuse any network other than tcp and any security other than none, and ignore the tls, ws and grpc sub-tables. Absent means plain TCP.
address_familystring (enum)no"auto"Which IP family to use when a name is resolved: auto, ipv4_only, ipv6_only, prefer_ipv4 or prefer_ipv6, plus aliases. Trimmed, case-insensitive, and - counts as _. For a proxy outbound it applies to the server name; for freedom, to the destination; for wireguard, to destinations inside the tunnel. An unknown value fails with invalid address_family.
settingstabledepends{}Protocol-specific settings, described on each protocol page. Required for trojan, vless, vmess, shadowsocks, hysteria2 and wireguard, which have mandatory keys; optional for socks and http; never read by freedom and blackhole. Unknown keys are refused, and errors here read outbound <tag>: invalid settings: ….

[outbound.settings] · protocol = "socks" · Explained in SOCKS

KeyTypeRequiredDefaultDescription
userstringno—Username for SOCKS5 username/password authentication (RFC 1929). When set, the client offers only the password method; when absent, it offers only "no authentication". Longer than 255 bytes is truncated to 255.
passstringno""Password that goes with user. Without user it is ignored and the client does not authenticate. Longer than 255 bytes is truncated to 255.

[outbound.settings] · protocol = "http" · Explained in HTTP proxy

KeyTypeRequiredDefaultDescription
userstringno—Username for the upstream proxy. When set, every CONNECT carries Proxy-Authorization: Basic with user:pass. When absent, no credential is sent.
passstringno""Password for the upstream proxy. Used only together with user: without user it is ignored, and user without pass sends an empty password.

[outbound.settings] · protocol = "shadowsocks" · Explained in Shadowsocks

KeyTypeRequiredDefaultDescription
methodstring (enum)yes—The cipher, with the same accepted values as the inbound: a 2022- value must be exactly 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm or 2022-blake3-chacha20-poly1305; any other value is a legacy AEAD method or alias, matched case-insensitively. It must match the server. An unknown value fails with unknown shadowsocks method or unknown shadowsocks-2022 method.
passwordstringyes—Legacy methods: the password, used as written (a : has no special meaning). Shadowsocks 2022: either one base64 PSK for a single-PSK server, or a chain iPSK:uPSK for a multi-user server, where the last key is your own user key and every key before it is an identity key. Each key follows the inbound length rules, and a key that is too short fails with shadowsocks-2022: PSK too short.

[outbound.settings] · protocol = "trojan" · Explained in Trojan

KeyTypeRequiredDefaultDescription
passwordstringyes—The password the server knows you by. The outbound sends the lowercase hex of SHA224(password) at the start of every connection it opens. Without it the outbound fails to build with an invalid settings: missing field error that names password. It is the only key: any other, such as email, is an error.

[outbound.settings] · protocol = "vless" · Explained in VLESS

KeyTypeRequiredDefaultDescription
idstringyes—The UUID to authenticate with; it must match a users[].id on the server. Same accepted forms as the inbound: hyphenated, 32 hex digits, or the hyphenated form braced or prefixed with a lowercase urn:uuid:. Leaving it out fails with invalid settings: missing field, a malformed value with invalid uuid. It is the only key: Xray's flow, encryption and level are rejected, and there is no mux setting.

[outbound.settings] · protocol = "vmess" · Explained in VMess

KeyTypeRequiredDefaultDescription
idstringyes—The UUID of the account on the server, in any form the inbound accepts. Leaving it out fails with a missing field error; a malformed value fails with outbound <tag>: invalid uuid "…".
securitystring (enum)no"aes-128-gcm"The body cipher. aes-128-gcm and auto select AES-128-GCM, as does leaving the key out. chacha20-poly1305 selects ChaCha20-Poly1305. Matching ignores letter case. Anything else, including none and zero, fails with unknown vmess security "…". Not to be confused with [outbound.stream].security, which turns on TLS.

[outbound.settings] · protocol = "hysteria2" · Explained in Hysteria 2

KeyTypeRequiredDefaultDescription
passwordstringyes—The credential sent in the Hysteria-Auth header once per QUIC connection. Must not be empty. For a server with a users table, write it as user:pass.
server_namestringno—TLS server name (SNI) and the name the certificate is verified against. Defaults to the outbound's server. Set it when server is an IP address and the certificate names a domain.
allow_insecureboolnofalseAccept any server certificate without checking its chain or name. Cannot be combined with ca_file. Use it only for testing.
ca_filepathno—PEM file of extra CA certificates, added to the system trust store rather than replacing it. The file is read by --test, but its contents are only parsed at the first connection, so a file with no certificates fails then with hysteria2: the CA file contains no certificates.
obfsstring (enum)no—Packet obfuscation beneath QUIC. The only accepted value is salamander; anything else fails with unknown obfs. Must match the server.
obfs_passwordstringdepends—Pre-shared key for salamander, at least 4 bytes, identical to the server's. Required when obfs is set; setting it without obfs is an error.
max_concurrent_streamsintegerno102400TCP flows this outbound may have open at once on its single shared connection, for all users together. A flow beyond the limit fails at once with connection is at its concurrent-stream limit. Must be at least 1; there is no upper bound.

[outbound.settings] · protocol = "wireguard" · Explained in WireGuard

KeyTypeRequiredDefaultDescription
private_keystringyes—Your own Curve25519 private key, the PrivateKey line of a wg-quick file. 32 bytes, written as standard padded base64 (what wg genkey prints) or as 64 hex digits. Unpadded base64 is refused. Error: invalid wireguard private_key.
peer_public_keystringyes—The peer's public key, the PublicKey line of the [Peer] section. Same encodings as private_key. Error: invalid wireguard peer_public_key.
preshared_keystringno—Optional pre-shared key mixed into the handshake, the PresharedKey line. Same encodings as private_key. Set it only when the peer has one for you; a mismatch means the handshake never completes. Error: invalid wireguard preshared_key.
endpointstringyes—The peer's UDP address as host:port, split at the last :. The host is an IP address or a name; a name is resolved with the system resolver (not [dns]) when the tunnel starts, and the first answer is used. Write IPv6 without brackets (2001:db8::1:51820): brackets are not stripped, so [2001:db8::1]:51820 passes --test and then fails to resolve. Errors: wireguard endpoint must be host:port, invalid wireguard endpoint port.
addressarray of IPsyes—The tunnel-local addresses the peer assigned to you, as bare IPs without a prefix length: ["10.0.0.2", "2001:db8:a::2"]. A CIDR such as 10.0.0.2/32 is refused with invalid IP address syntax. They decide which destination families the tunnel can reach: without an IPv6 address, IPv6 destinations are unreachable. An empty list passes --test, but every connection then fails with wireguard: no tunnel-local addresses configured.
mtuintegerno1420Largest IP packet inside the tunnel, in bytes; the userspace TCP stack sizes its segments from it. Not range-checked. Lower it (for example to 1280) when small requests work but large transfers stall.
keepaliveu16no—Persistent keepalive interval in seconds, the PersistentKeepalive line. Absent or 0 turns it off. Set it (commonly 25) when this host is behind NAT or a stateful firewall and connections may sit idle.
reservedarray of integersno—Exactly three bytes ([0, 0, 0] to [255, 255, 255]) written into header bytes 1 to 3 of every outgoing WireGuard packet, as Xray's reserved does. Incoming packets have these bytes cleared before decoding whether or not reserved is set. Only set it when your service tells you to. Any other length fails with invalid length …, expected an array of length 3.

[inbound.stream] · [outbound.stream] · Explained in Transports and TLS

KeyTypeRequiredDefaultDescription
networkstring (enum)no"tcp"The carrier under the protocol: tcp (plain TCP), tls (TLS over TCP), ws (WebSocket) or grpc (gRPC over HTTP/2). Case-sensitive and not trimmed: "WS", " ws" and "" all fail with unknown stream network. Protocols without a transport accept only tcp (or empty) here.
securitystring (enum)no"none"none (or empty) for no extra layer, tls to put TLS under a ws or grpc network. Surrounding spaces are ignored; the match is case-sensitive. Any other value fails with unknown stream security "..." (expected "tls" or "none"). With network = "tcp", tls is refused; write network = "tls" instead. With network = "tls", the connection is TLS whatever this says.
tlstabledepends—Certificate, key and verification settings, read only when the stream actually uses TLS, except that a ws or grpc outbound always reads server_name as its Host or :authority fallback. An inbound using TLS needs cert_file and key_file here. See the [stream.tls] table.
wstableno—WebSocket path and Host, read only when network = "ws". See the [stream.ws] table.
grpctabledepends—gRPC service name and authority, read only when network = "grpc"; that network requires grpc.service_name. See the [stream.grpc] table.

[inbound.stream.tls] · [outbound.stream.tls] · Explained in Transports and TLS

KeyTypeRequiredDefaultDescription
server_namestringno—Outbound only. The name sent as SNI and checked against the server certificate. Falls back to the outbound server. When the name is an IP address, no SNI is sent and the certificate must list that IP. A ws or grpc outbound also uses it as the fallback for ws.host or grpc.authority, with or without TLS. Ignored on an inbound.
allow_insecureboolnofalseOutbound only. Skip certificate chain and host name verification entirely. Cannot be combined with ca_file (tls.allow_insecure and tls.ca_file cannot both be set). Ignored on an inbound.
ca_filepathno—Outbound only. A PEM file of CA certificates to trust in addition to the system roots; the host name is still verified. A file with no certificate fails with no certificate in CA PEM bundle. Cannot be combined with allow_insecure. Ignored on an inbound.
cert_filepathdepends—Inbound only, required whenever the inbound uses TLS (tls stream needs tls.cert_file). A PEM certificate chain: the leaf certificate first, then any intermediates. Ignored on an outbound; client certificates are not supported.
key_filepathdepends—Inbound only, required whenever the inbound uses TLS (tls stream needs tls.key_file). The PEM private key that matches the leaf certificate; a key that does not match fails with no private key assigned. Ignored on an outbound.

[inbound.stream.ws] · [outbound.stream.ws] · Explained in Transports and TLS

KeyTypeRequiredDefaultDescription
pathstringno"/"The HTTP path of the upgrade request. An empty path becomes / and a missing leading / is added, so ray means /ray. A ?ed=N query parameter is not part of the path: on an outbound it turns on early data of up to N bytes (at most 16384); on an inbound it is removed and ignored. The inbound compares the request path exactly and answers anything else with 404.
hoststringno—On an inbound: when set, the request Host header must match it, ignoring case and any :port, or the upgrade gets 404; when absent, any Host is accepted. On an outbound: the Host header to send, falling back to tls.server_name and then to server; it does not change the TLS SNI. Set it when server is an IPv6 address, which is not a valid URI host on its own.

[inbound.stream.grpc] · [outbound.stream.grpc] · Explained in Transports and TLS

KeyTypeRequiredDefaultDescription
service_namestringdepends—Required when network = "grpc" (grpc stream needs grpc.service_name). The tunnel paths are /SERVICE/Tun and /SERVICE/TunMulti. The name is inserted verbatim, so write it without slashes. Both sides must use the same name.
authoritystringno—Outbound only. The HTTP/2 :authority of each request, falling back to tls.server_name and then to server; it does not change the TLS SNI. Set it when server is an IPv6 address, which is not a valid URI authority on its own. An inbound does not check the authority and ignores this key.

[route] · Explained in Routing

KeyTypeRequiredDefaultDescription
defaultstringno—Tag of the outbound or balancer that takes every flow no rule matches. When absent, the first [[outbound]] in the file is the default; a balancer is never picked implicitly. A tag that names neither an outbound nor a balancer fails with route references unknown outbound tag: <tag>.
geoippathdepends—Path of a v2ray-format geoip.dat. Required when any rule uses geoip (a geoip matcher is used but no geoip file is configured), and not even opened otherwise. A relative path resolves against the working directory of the process, not the directory of the config file. The file is read at startup, by --test and on every reload; only the codes the rules name are kept.
geositepathdepends—Path of a v2ray-format geosite.dat. Required when any rule uses geosite (a geosite matcher is used but no geosite file is configured), and not even opened otherwise. Relative paths and reading work as for geoip.
rulearray of tablesno[]The rules, written as [[route.rule]] blocks (singular: [[route.rules]] is an unknown field). Tried in file order; the first rule that matches picks the outbound.

[[route.rule]] · Explained in Routing

KeyTypeRequiredDefaultDescription
outboundstringyes—Tag of the outbound or balancer that takes the flows this rule matches. An unknown tag fails with route references unknown outbound tag: <tag>.
domain_suffixarray of stringsno[]Matches the domain itself and every subdomain, on label boundaries: example.com matches example.com and a.example.com, not notexample.com. Case-insensitive. Write no leading dot: .example.com matches nothing.
domain_keywordarray of stringsno[]Matches a domain that contains the string anywhere: ads matches ads.example.com and downloads.example.com. Case-insensitive.
domain_fullarray of stringsno[]Matches exactly this domain and no subdomain. Case-insensitive.
domain_regexarray of stringsno[]A regular expression in Rust regex syntax, tested against the lowercased domain. Unanchored: add ^ and $ to match the whole name. The pattern itself is not lowercased, so write it in lower case. An invalid pattern fails with invalid domain regex "<pattern>": ….
cidrarray of stringsno[]Matches a destination IP address inside the range, such as 10.0.0.0/8 or 2001:db8::/32. A bare address is a single host. Host bits must be zero (invalid cidr "10.0.0.1/8": host part of address was not zero). Never matches a destination given as a domain.
source_cidrarray of stringsno[]Matches when the client address is inside the range. Same syntax and errors as cidr. Flows from a Unix-socket inbound have no client address and never match.
portarray of stringsno[]Destination ports as strings: a single port "443" or an inclusive range "8000-9000". Spaces around the numbers are allowed. A bare integer is a type error (expected a string); a range whose lower bound is above its upper bound fails with invalid port spec: "9000-8000" has a lower bound above its upper bound.
networkstring (enum)no—The transport of the flow: tcp or udp. One string, not an array, lowercase only. Anything else fails with invalid rule network "<value>" (expected "tcp" or "udp").
inbound_tagarray of stringsno[]Matches flows that arrived on one of these inbounds. Compared exactly, case included. Not checked against the inbounds in the file, so a misspelt tag is accepted and never matches.
geositearray of stringsno[]A list from [route].geosite: code, or code@attr for only the entries that carry that attribute. Codes and attributes are case-insensitive. Write no geosite: prefix. An unknown code fails with geosite code not found: <code>; an unknown attribute is accepted and matches nothing.
geoiparray of stringsno[]A list from [route].geoip: code matches destination IPs in the list, !code matches destination IPs outside it. Case-insensitive. Like cidr, neither form ever matches a destination given as a domain. An unknown code fails with geoip code not found: <code>.

[[balancer]] · Explained in Balancers

KeyTypeRequiredDefaultDescription
tagstringyes—Name of the balancer. [route].default and [[route.rule]].outbound refer to it exactly as they refer to an outbound. It must not repeat any [[outbound]] tag or the tag of another balancer; both fail with balancer tag <tag> collides with an outbound tag.
outboundsarray of stringsyes—The member outbounds, by exact tag. At least one (a balancer needs at least one outbound). Only [[outbound]] tags are accepted: a missing tag or the tag of another balancer fails with balancer <tag> references unknown outbound tag: <member>. Every member needs a server and port that a TCP connect can probe. hysteria2 members are always refused, and freedom, blackhole and wireguard members are refused because they normally have no server and port; both fail with balancer <tag>: outbound <member> has no upstream a TCP health probe can reach, so it cannot be balanced. Under failover the list order is the priority. A tag listed twice is accepted and counts twice.
strategystring (enum)no"failover"How a healthy member is chosen for each new flow: failover takes the first healthy member in outbounds order, round_robin takes each healthy member in turn. Matched exactly and case-sensitively; anything else, such as "roundrobin" or "Failover", fails with unknown balancer strategy "<value>" (expected "failover" or "round_robin").
probe_intervalu64no30Seconds to wait after one health probe of a member finishes before the next one starts. Each member is probed on its own schedule, and the first probe runs as soon as the configuration starts. 0 is accepted and makes the probes run back to back with no pause, opening connections to the upstream continuously.
probe_timeoutu64no5Seconds one probe may take, name resolution included, before the member counts as down. 0 is accepted but leaves a probe only until the next timer tick, about a millisecond, so a member is marked down unless its connect completes almost at once. Use at least 1.

[dns] · Explained in DNS

KeyTypeRequiredDefaultDescription
backendstring (enum)no"system"Where answers come from: system (the host resolver, getaddrinfo), udp (plain DNS over UDP), tls (DNS over TLS, RFC 7858) or https (DNS over HTTPS, RFC 8484). Lower case only; any other value fails with dns: unknown backend "…" (expected "system", "udp", "tls" or "https").
serverstringdepends—Required for udp, tls and https. The resolver's socket address as an IP literal with a port, such as "192.0.2.53:53" or "[2001:db8::53]:853". A host name or a missing port is refused with dns: invalid server address: invalid socket address syntax, because there is nothing yet to resolve it with. For https this is the address that is dialed, whatever host and port url names. Ignored by system.
server_namestringdepends—Required for tls. The name sent as SNI and checked against the resolver's certificate. It is never guessed from server; a missing value fails with dns: the tls backend needs a server name to verify against. Ignored by every other backend, including https, which takes the name from url.
urlstringdepends—Required for https. Must start with https://. The host is sent as SNI and as the Host header and is what the certificate is checked against; everything from the first / on is the request path, and /dns-query is used when there is none. A port in the URL is ignored, and the host is cut at the first :, so write it as a name: an IPv6 literal passes the config check but fails every query. A URL with userinfo (user@) or an empty host is refused. Ignored by every other backend.
ca_filepathno—PEM file of extra CA certificates that tls and https trust in addition to the system store, for a resolver with a private certificate. The file is read whenever the key is set, whatever the backend, so a missing file fails the config even with system or udp; only tls and https parse it, and a file with no certificate then fails with no certificate in CA PEM bundle. A relative path resolves against the working directory.