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.
Top-level tables
Section titled “Top-level tables”config.toml · Explained in The configuration file
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
log | table | no | {} | Logging, written as [log]. Holds only level. See the [log] section of the configuration file page. |
dns | table | no | {} | Name resolution for outbounds and balancer probes, written as [dns]. Without it, etemenanki-app uses the host resolver behind an always-on cache. |
inbound | array of tables | no | [] | 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. |
outbound | array of tables | yes | — | 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. |
balancer | array of tables | no | [] | 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. |
route | table | no | {} | 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]]. |
Logging
Section titled “Logging”[log] · Explained in The configuration file
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
level | string | no | "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. |
Inbounds
Section titled “Inbounds”Inbound common fields
Section titled “Inbound common fields”[[inbound]] · Explained in Inbounds
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
tag | string | yes | — | 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. |
protocol | string (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. |
listen | string | no | "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. |
port | u16 | depends | — | 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. |
stream | table | no | — | 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_family | string | no | — | Accepted so that inbound and outbound tables share a shape, but it has no effect on an inbound. The value is not validated. |
sniffing | bool | no | true | Read 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. |
settings | table | no | {} | Protocol-specific settings, described on each protocol page. Unknown keys are refused with inbound TAG: invalid settings: .... These errors carry no line number. |
SOCKS inbound
Section titled “SOCKS inbound”[inbound.settings] · protocol = "socks" · Explained in SOCKS
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
auth | string (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. |
accounts | array of tables | depends | [] | 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". |
udp | bool | no | true | Accept the SOCKS5 UDP ASSOCIATE command. When false, the server answers UDP ASSOCIATE with reply code 0x07 (command not supported) and closes the connection. |
udp_bind | string | depends | — | 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. |
HTTP inbound
Section titled “HTTP inbound”[inbound.settings] · protocol = "http" · Explained in HTTP proxy
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
accounts | array of tables | no | [] | 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[].user | string | yes | — | 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[].pass | string | yes | — | 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_transparent | bool | no | false | Accept 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. |
Shadowsocks inbound
Section titled “Shadowsocks inbound”[inbound.settings] · protocol = "shadowsocks" · Explained in Shadowsocks
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
method | string (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. |
password | string | yes | — | 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. |
users | array of tables | no | [] | 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[].password | string | yes | — | 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[].email | string | no | "" | 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. |
Trojan inbound
Section titled “Trojan inbound”[inbound.settings] · protocol = "trojan" · Explained in Trojan
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
users | array of tables | no | [] | 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[].password | string | yes | — | 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[].email | string | no | "" | 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. |
VLESS inbound
Section titled “VLESS inbound”[inbound.settings] · protocol = "vless" · Explained in VLESS
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
users | array of tables | no | [] | 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[].id | string | yes | — | 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. |
VMess inbound
Section titled “VMess inbound”[inbound.settings] · protocol = "vmess" · Explained in VMess
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
users | array of tables | no | [] | 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[].id | string | yes | — | 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. |
Hysteria 2 inbound
Section titled “Hysteria 2 inbound”[inbound.settings] · protocol = "hysteria2" · Explained in Hysteria 2
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
cert_file | path | yes | — | 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_file | path | yes | — | 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. |
password | string | depends | — | 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. |
users | array of tables | depends | — | 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[].user | string | yes | — | 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[].pass | string | yes | — | 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[].email | string | no | "" | 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. |
udp | bool | no | false | Relay 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_timeout | u64 | no | 60 | Seconds 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_connections | integer | no | 4096 | Concurrent QUIC connections this listener serves. A client beyond the limit has its handshake refused at once. Must be at least 1. |
max_circuits | integer | no | 65536 | Live 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. |
obfs | string (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_password | string | depends | — | 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. |
masquerade | table | no | — | The HTTP/3 response given to any request that is not a valid authentication, including a wrong credential. See the masquerade table below. |
Hysteria 2 masquerade
Section titled “Hysteria 2 masquerade”[inbound.settings.masquerade] · Explained in Hysteria 2
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
status | u16 | no | 404 | HTTP 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. |
body | string | no | "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_type | string | no | "text/plain; charset=utf-8" | Value of the Content-Type response header. |
TUN inbound
Section titled “TUN inbound”[inbound.settings] · protocol = "tun" · Explained in TUN
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | no | — | 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. |
mtu | u16 | no | 1500 | MTU 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. |
address | array of strings | no | [] | 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. |
routes | array of strings | no | [] | 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. |
udp | bool | no | true | Relay UDP as well as TCP. When false, every UDP packet that reaches the device is dropped without a reply. |
udp_idle_timeout | u64 | no | 60 | Seconds 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_flows | integer | no | 65536 | Upper 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. |
Outbounds
Section titled “Outbounds”Outbound common fields
Section titled “Outbound common fields”[[outbound]] · Explained in Outbounds
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
tag | string | yes | — | 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. |
protocol | string (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. |
server | string | depends | — | 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. |
port | u16 | depends | — | 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. |
stream | table | no | — | 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_family | string (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. |
settings | table | depends | {} | 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: …. |
SOCKS outbound
Section titled “SOCKS outbound”[outbound.settings] · protocol = "socks" · Explained in SOCKS
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
user | string | no | — | 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. |
pass | string | no | "" | Password that goes with user. Without user it is ignored and the client does not authenticate. Longer than 255 bytes is truncated to 255. |
HTTP outbound
Section titled “HTTP outbound”[outbound.settings] · protocol = "http" · Explained in HTTP proxy
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
user | string | no | — | Username for the upstream proxy. When set, every CONNECT carries Proxy-Authorization: Basic with user:pass. When absent, no credential is sent. |
pass | string | no | "" | Password for the upstream proxy. Used only together with user: without user it is ignored, and user without pass sends an empty password. |
Shadowsocks outbound
Section titled “Shadowsocks outbound”[outbound.settings] · protocol = "shadowsocks" · Explained in Shadowsocks
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
method | string (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. |
password | string | yes | — | 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. |
Trojan outbound
Section titled “Trojan outbound”[outbound.settings] · protocol = "trojan" · Explained in Trojan
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
password | string | yes | — | 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. |
VLESS outbound
Section titled “VLESS outbound”[outbound.settings] · protocol = "vless" · Explained in VLESS
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | yes | — | 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. |
VMess outbound
Section titled “VMess outbound”[outbound.settings] · protocol = "vmess" · Explained in VMess
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | yes | — | 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 "…". |
security | string (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. |
Hysteria 2 outbound
Section titled “Hysteria 2 outbound”[outbound.settings] · protocol = "hysteria2" · Explained in Hysteria 2
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
password | string | yes | — | 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_name | string | no | — | 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_insecure | bool | no | false | Accept any server certificate without checking its chain or name. Cannot be combined with ca_file. Use it only for testing. |
ca_file | path | no | — | 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. |
obfs | string (enum) | no | — | Packet obfuscation beneath QUIC. The only accepted value is salamander; anything else fails with unknown obfs. Must match the server. |
obfs_password | string | depends | — | 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_streams | integer | no | 102400 | TCP 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. |
WireGuard outbound
Section titled “WireGuard outbound”[outbound.settings] · protocol = "wireguard" · Explained in WireGuard
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
private_key | string | yes | — | 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_key | string | yes | — | The peer's public key, the PublicKey line of the [Peer] section. Same encodings as private_key. Error: invalid wireguard peer_public_key. |
preshared_key | string | no | — | 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. |
endpoint | string | yes | — | 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. |
address | array of IPs | yes | — | 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. |
mtu | integer | no | 1420 | Largest 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. |
keepalive | u16 | no | — | 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. |
reserved | array of integers | no | — | 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. |
Transports
Section titled “Transports”Stream
Section titled “Stream”[inbound.stream] · [outbound.stream] · Explained in Transports and TLS
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
network | string (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. |
security | string (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. |
tls | table | depends | — | 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. |
ws | table | no | — | WebSocket path and Host, read only when network = "ws". See the [stream.ws] table. |
grpc | table | depends | — | 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
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
server_name | string | no | — | 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_insecure | bool | no | false | Outbound 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_file | path | no | — | 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_file | path | depends | — | 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_file | path | depends | — | 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. |
WebSocket
Section titled “WebSocket”[inbound.stream.ws] · [outbound.stream.ws] · Explained in Transports and TLS
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | no | "/" | 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. |
host | string | no | — | 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
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
service_name | string | depends | — | 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. |
authority | string | no | — | 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. |
Routing and balancing
Section titled “Routing and balancing”[route] · Explained in Routing
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
default | string | no | — | 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>. |
geoip | path | depends | — | 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. |
geosite | path | depends | — | 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. |
rule | array of tables | no | [] | 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 rules
Section titled “Route rules”[[route.rule]] · Explained in Routing
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
outbound | string | yes | — | 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_suffix | array of strings | no | [] | 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_keyword | array of strings | no | [] | Matches a domain that contains the string anywhere: ads matches ads.example.com and downloads.example.com. Case-insensitive. |
domain_full | array of strings | no | [] | Matches exactly this domain and no subdomain. Case-insensitive. |
domain_regex | array of strings | no | [] | 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>": …. |
cidr | array of strings | no | [] | 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_cidr | array of strings | no | [] | 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. |
port | array of strings | no | [] | 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. |
network | string (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_tag | array of strings | no | [] | 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. |
geosite | array of strings | no | [] | 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. |
geoip | array of strings | no | [] | 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>. |
Balancers
Section titled “Balancers”[[balancer]] · Explained in Balancers
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
tag | string | yes | — | 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. |
outbounds | array of strings | yes | — | 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. |
strategy | string (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_interval | u64 | no | 30 | Seconds 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_timeout | u64 | no | 5 | Seconds 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. |
Resolver
Section titled “Resolver”[dns] · Explained in DNS
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
backend | string (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"). |
server | string | depends | — | 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_name | string | depends | — | 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. |
url | string | depends | — | 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_file | path | no | — | 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. |