Skip to content

katana configuration

Every table of the katana configuration file on one page. The same tables appear on the katana 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. Unlike etemenanki-app, katana applies a changed level on hot reload.
dnstableno{}Name resolution for the whole outbound pool, written as [dns]. Without it, katana uses the host resolver behind an always-on cache. The panel HTTP requests do not use it.
nodearray of tablesyes—The panel nodes to serve, one [[node]] block each, with the sub-tables [node.api], [node.controller], [node.controller.cert], [node.hysteria], [node.route] and [[node.route.rule]]. Written in the singular: [[nodes]] is an unknown field, and a single [node] table fails with invalid type: map, expected a sequence. The file parses without any, but both --test and a normal start fail with config defines no [[node]] entries.
outboundarray of tablesno[]Named upstream proxies, one [[outbound]] block each, shared by every node. Written in the singular. The tags direct, freedom, block and blackhole always exist and cannot be redefined; a reserved or repeated tag fails with duplicate/reserved outbound tag <tag>.

[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 "info,katana=debug". At startup a RUST_LOG variable that holds a valid filter takes precedence. --test does not validate it: at startup an unparsable directive is skipped with an ignoring ... line on stderr, and a word that is not a level, such as "warning", is read as a target name and silences every log line, errors included. A hot reload that changes the value applies it and replaces the RUST_LOG filter; a value that does not parse is logged as invalid log level and the current filter stays.

[dns] · Explained in The configuration file

KeyTypeRequiredDefaultDescription
backendstring (enum)no"system"Where answers come from: "system" (the host resolver, which honours /etc/hosts and nsswitch.conf), "udp" (plain DNS), "tls" (DNS over TLS, RFC 7858) or "https" (DNS over HTTPS, RFC 8484). Case-sensitive, unlike most katana enums: "UDP" fails with dns: unknown backend "UDP" (expected "system", "udp", "tls" or "https").
serverstringdepends—The resolver address as an IP literal with a port, for example "192.0.2.53:53" or "[2001:db8::53]:853". Required for udp, tls and https; without it the build fails with dns: the <backend> backend needs a server address. A host name or a missing port fails with dns: invalid server address: invalid socket address syntax. For https this is the address katana dials; the host in url is not resolved. Ignored by system.
server_namestringdepends—The name the resolver certificate is verified against. Required for tls; without it the build fails with dns: the tls backend needs a server name to verify against. Ignored by the other backends.
urlstringdepends—The DNS over HTTPS endpoint, for example "https://dns.example.com/dns-query". Required for https; without it the build fails with dns: the https backend needs the resolver's url. It must start with https://, otherwise dns: "<url>" is not an https:// url. Its host is the name the certificate is verified against and the Host header; a port in it is ignored, and a URL without a path uses /dns-query. A URL with a user part fails with has no usable host. Ignored by the other backends.
ca_filepathno—A PEM bundle of extra CA certificates for verifying a tls or https resolver. katana adds them to the system roots rather than replacing them. katana reads it whenever the key is set, even for system and udp, which do not use it. A relative path is resolved against the working directory. A missing file fails with the bare No such file or directory (os error 2), which does not name the path. With tls or https, a file that holds no certificate fails with no certificate in CA PEM bundle.

[[node]] · Explained in Nodes

KeyTypeRequiredDefaultDescription
panel_typestring (enum)yes—Which panel API this node talks to: NewV2board (UniProxy, for Xboard and V2board), its alias V2board, or SSpanel (mod_mu). Compared case-insensitively. Leaving it out fails --test with unknown panel_type ""; any other value fails the same way, with the value shown lowercased. At startup, such a node is logged and skipped, and katana exits only when no node can be built. On a hot reload, an unknown value makes katana refuse the whole reload and keep running the current configuration. Part of the node identity.
apitableyes—How to reach the panel and what to ask it for, written as [node.api]: the panel URL, node ID, key and node type, plus a few local overrides.
controllertableno{}This machine's side of the node, written as [node.controller]: listen address, poll interval, the sniffing, rule and upload switches, and the [node.controller.cert] certificate table. Every key has a default.
routetableno{}This node's routing table, written as [node.route] with [[node.route.rule]] blocks. Without it, every flow goes to the direct outbound.
hysteriatableno{}Settings for a Hysteria 2 node that no panel describes, written as [node.hysteria]: credential format, UDP relay, masquerade, and optionally a local port and obfuscation. Ignored unless node_type is a Hysteria 2 value.

[node.api] · Explained in Nodes

KeyTypeRequiredDefaultDescription
hoststringyes—Base URL of the panel, with the scheme: https://panel.example.com. If the panel is served under a path, include it. Trailing slashes are removed before katana appends the API path. --test does not check the value: a host without https:// or http:// passes, and then every panel request fails. Part of the node identity, compared exactly as written.
node_idu32yes—The node's ID in the panel. A negative value fails the parse with an invalid value error ending in expected u32. Part of the node identity.
keystringyes—The panel's node communication key: the server token in Xboard and V2board, muKey in SSPanel. UniProxy panels receive it as the token query parameter, SSPanel as both key and muKey, on every request. Part of the node identity.
node_typestring (enum)yes—What kind of node the panel describes, compared case-insensitively: V2ray, vmess or vless (all three mean a V2ray node, VMess unless enable_vless is on), Trojan, Shadowsocks, or Hysteria2, hysteria or hy2. UniProxy panels receive it lowercased as the node_type query parameter (or as vless for a V2ray node with enable_vless on), so write it the way the panel spells it. Leaving it out fails with unknown node_type "". On NewV2board and V2board, the type katana asks for is part of the node identity, so a hot reload that changes the requested type (anything but case) replaces the node. Any other change, and any change on SSPanel, builds a new panel client on a hot reload and applies at once.
enable_vlessboolnofalseServe VLESS instead of VMess on a V2ray node. A UniProxy panel is then asked for node_type=vless, and katana reads the transport settings from the panel's network_settings object instead of networkSettings. SSPanel can also turn VLESS on from custom_config; either source is enough. On NewV2board and V2board, turning it on or off for a node whose node_type is V2ray or vmess changes the type katana asks for, which is part of the node identity, so a hot reload replaces the node. With node_type = "vless" the type asked for is vless either way. Otherwise, including on SSPanel, a hot reload that changes it builds a new panel client and rebuilds the listener at once.
vless_flowstringno""SSPanel legacy server string only: the VLESS flow of a V2ray node. Any non-empty value makes such a node fail with node requests kernel-unsupported feature: VLESS XTLS flow, so leave it empty. UniProxy panels and SSPanel custom_config supply the flow themselves. A hot reload that changes it builds a new panel client and applies the change at once.
timeoutu64no0Limit for each panel HTTP request, in seconds, from connecting until the whole response has been read. 0 means 5 seconds. A hot reload that changes it builds a new panel client in place, without replacing the node.
speed_limitfloatno0Speed-limit override in Mbps (1 Mbps is 125 000 bytes per second; fractions and integers are both accepted). Above 0, it replaces every limit the panel sends for this node, node-level and per-user, so every user gets exactly this rate. 0 or a negative value uses the panel's limits. A hot reload that changes it builds a new panel client and keeps every connection; the new rate applies to every flow opened after the reload.
device_limitintegerno0Accepted and ignored. katana does not limit devices per user.
rule_list_pathpathno""A local file of audit rules, one regular expression per line; blank lines and lines starting with # are skipped. Each rule is matched against the destination domain or IP, without the port, and a match refuses the flow. Read together with the panel rules, so disable_get_rule = true also turns this file off. Re-read at every rule refresh, so edits to its contents apply at the next cycle (with SSPanel, only when the panel's own rule request succeeds with a full answer). An unreadable file or an invalid line is logged and skipped. Hits on these rules refuse the flow but are not reported to the panel. A hot reload that changes the path builds a new panel client, which reads the new file at once; connections are kept.
disable_custom_configboolnofalseSSPanel only. Read the node from the legacy server string even when the panel is version 2021.11 or later and offers custom_config. A panel that reports an older version, or none, always gets the server string parse. A hot reload that changes it builds a new panel client and applies the change at once.

[node.controller] · Explained in Nodes

KeyTypeRequiredDefaultDescription
listen_ipstringno"0.0.0.0"Address the listener binds: TCP for VMess, VLESS, Trojan and Shadowsocks nodes, UDP for Hysteria 2. The port always comes from the panel (or from [node.hysteria].port). Write an IPv4 or IPv6 address without brackets, such as "0.0.0.0", "::" or "203.0.113.10". --test does not check it; a bad address makes the bind fail when the node starts. Also part of the node tag, such as V2ray_0.0.0.0_443. A hot reload that changes it rebuilds the listener.
send_ipstringno"0.0.0.0"Accepted and ignored. katana does not bind outgoing connections to a source address; the operating system chooses it.
update_periodicu64no60Seconds between poll cycles. One timer drives everything: fetching the node settings and users, refreshing the audit rules, and reporting traffic and audit hits. 0 counts as 1. The panel's own push and pull intervals are not read. The first cycle runs one full period after the node is up. It also caps the wait between attempts while a node retries its first start, which otherwise grows to 60 seconds. A hot reload that changes it restarts the timer.
disable_upload_trafficboolnofalseStop reporting user traffic to the panel. Speed limits and audit rules still apply. Read on every cycle, so a hot reload applies it at the next cycle.
disable_get_ruleboolnofalseStop fetching audit rules, both from the panel and from rule_list_path. Set at startup, the node runs with no audit rules. Turned on by hot reload, it stops the refreshes but keeps the rules already loaded until katana restarts. Audit hits already recorded are still reported.
disable_sniffingboolnofalseStop reading the TLS SNI or HTTP Host from the first bytes of each TCP flow. Sniffing only feeds the routing rules, so a flow addressed by IP can still match a domain rule; the destination katana connects to is never changed. A hot reload that changes it rebuilds the listener at once, dropping the node's connections.
certtabledepends{}The listener certificate, written as [node.controller.cert]. Required with mode = "file" for Trojan and Hysteria 2 nodes, and for V2ray nodes the panel marks as TLS. A hot reload that changes any of its keys rebuilds the listener.

[node.controller.cert] · Explained in Nodes

KeyTypeRequiredDefaultDescription
modestring (enum)depends"none""none" (no certificate) or "file" (read cert_file and key_file). Case-sensitive. "file" is required for every node that uses TLS; without it a stream node fails with TLS node requires cert.mode = "file" and a Hysteria 2 node with hysteria2 node requires cert.mode = "file". The ACME modes "dns", "http" and "tls" are refused on every stream node, TLS or not: node requests kernel-unsupported feature: ACME cert mode "dns"; on a Hysteria 2 node they fail like any value other than "file". Any other value behaves like "none" on a stream node.
cert_filepathdepends""PEM certificate chain: the server certificate first, then any intermediates (a fullchain.pem). Required with mode = "file"; if it or key_file is empty, a TLS or Hysteria 2 node fails with TLS node requires cert.cert_file and cert.key_file. Read each time the listener is built, not when the file changes. A missing file fails with the bare OS error, such as No such file or directory (os error 2), which does not name the path.
key_filepathdepends""PEM private key matching cert_file. Required with mode = "file". Read at the same moments as cert_file; a key that does not match the certificate fails the listener build.
reject_unknown_sniboolnofalseNot implemented, so true is refused rather than silently ignored: the node fails with node requests kernel-unsupported feature: cert.reject_unknown_sni. Leave it false.

[node.hysteria] · Explained in Hysteria 2 nodes

KeyTypeRequiredDefaultDescription
portu16no0The UDP port to listen on. A non-zero value makes this a locally described node: katana builds the node from this table and never calls the panel's node-config endpoint. 0 asks the panel for the port and the obfuscation, as for every other node type.
credentialstring (enum)no""How the client's auth string identifies a user. "" or uuid: the whole string is the user's UUID. user_pass: the string is label:uuid, split on the first colon, where the label is <uuid>@v2board.user on Xboard/V2board and the numeric user ID on SSPanel; the label is compared case-insensitively. The value itself is case-sensitive; anything else fails with unknown hysteria credential kind.
udpboolnofalseRelay UDP as well as TCP. Off by default, unlike the upstream server: the listener then tells clients that UDP is disabled and relays TCP only.
udp_idle_timeoutu64no60Seconds a UDP session may carry no datagrams before katana closes it. Accepted range 2 to 600. The default 60 applies only when udp = true: the key is only valid with UDP on, and setting it while udp is off fails with udp_idle_timeout is set but udp is not enabled.
obfsstring (enum)no—Packet obfuscation for a locally described node. The only accepted value is salamander, in lowercase. When port = 0 the panel's obfuscation applies and this key is ignored at run time, although --test still checks it.
obfs_passwordstringdepends—The Salamander key shared with every client, at least 4 bytes. Required when obfs is set. Setting it without obfs is refused, so a typo cannot quietly turn obfuscation off. Same local-node rule as obfs.
masqueradetableno{}The HTTP/3 response given to every request that does not carry a valid credential, written as [node.hysteria.masquerade]. Applies to both local and panel-described nodes.

[node.hysteria.masquerade] · Explained in Hysteria 2 nodes

KeyTypeRequiredDefaultDescription
statusu16no404The HTTP status code. Any code from 100 to 999 except 233, which is the Hysteria 2 authentication success status and would tell the prober it had authenticated. Out-of-range values fail with is not an HTTP status code.
bodystringno"404 page not found\n"The response body, sent as is with a matching Content-Length.
content_typestringno"text/plain; charset=utf-8"The value of the Content-Type header. katana does not check it. A value that is not a valid HTTP header value, for example one containing a newline, makes the masquerade a bare 200 response with no headers, so keep it to printable characters.

[node.route] · Explained in Routing

KeyTypeRequiredDefaultDescription
defaultstringno"direct"Tag of the outbound a flow takes when no rule matches. Any tag in the outbound pool is accepted: the built-in direct, block, freedom and blackhole, or the tag of a top-level [[outbound]]. Tags are case-sensitive. An unknown tag fails with route references unknown outbound tag: <tag>.
geoippathdepends—Path to a v2ray-format geoip.dat. Required as soon as any rule of this node uses geoip, otherwise the build fails with a geoip matcher is used but no geoip file is configured. katana reads the file only when a rule references it, so an unused path is never opened. Only the referenced codes are kept in memory.
geositepathdepends—Path to a v2ray-format geosite.dat (the v2fly dlc.dat works). Required as soon as any rule of this node uses geosite, otherwise the build fails with a geosite matcher is used but no geosite file is configured. Read only when referenced, like geoip.
rulearray of tablesno[]The rules, one [[node.route.rule]] block each, checked from top to bottom. The first rule that matches picks the outbound. Written in the singular: rules is an unknown field.

[[node.route.rule]] · Explained in Routing

KeyTypeRequiredDefaultDescription
outboundstringyes—Tag of the outbound a matching flow takes. It must exist in the outbound pool (direct, block, freedom, blackhole or a top-level [[outbound]] tag), otherwise the build fails with route references unknown outbound tag: <tag>. Leaving it out fails the parse with a missing field error.
domain_suffixarray of stringsno[]Domains that match themselves and every subdomain: example.com matches example.com and www.example.com, not notexample.com. katana lowercases each entry. Matched against the domain the client asked for and against the sniffed TLS SNI or HTTP Host. Write entries without a leading dot: .example.com matches nothing.
cidrarray of stringsno[]Destination IP ranges, IPv4 or IPv6, such as 192.0.2.0/24 or 2001:db8::/32. A bare address is a single-host range. Parsing is strict: set host bits (192.0.2.1/24) or an oversized prefix fail with invalid cidr "<value>": <reason>. Matches only destinations the client addressed by IP; katana does not resolve domains before routing.
portarray of stringsno[]Destination ports as strings: a single port "443" or an inclusive range "6881-6889". Spaces around the numbers are ignored. A TOML integer such as 80 fails the parse with invalid type: integer, followed by expected a string. A value that is not a port fails with invalid port spec: "<value>", and a range whose lower bound is above its upper bound fails with invalid port spec: "<value>" has a lower bound above its upper bound.
geositearray of stringsno[]Entries of the [node.route].geosite file: code, or code@attr to keep only the domains tagged with that attribute, such as category-ads-all or google@ads. Codes and attributes are case-insensitive. Matched against the requested domain and the sniffed domain, like domain_suffix. A code missing from the file fails with geosite code not found: <code>; an attribute that tags no domain is accepted and matches nothing.
geoiparray of stringsno[]Entries of the [node.route].geoip file: code, such as private or cn, or !code to match every IP outside that list. Codes are case-insensitive. Like cidr, it matches only destinations addressed by IP, and !code does not match a domain destination either. A code missing from the file fails with geoip code not found: <code>.

[[outbound]] · Explained in Outbounds

KeyTypeRequiredDefaultDescription
tagstringyes—Name that [node.route].default and [[node.route.rule]].outbound refer to. Must be unique across all [[outbound]] tables and must not be one of the built-in tags direct, freedom, block or blackhole; either mistake fails with duplicate/reserved outbound tag <tag>. Compared case-sensitively everywhere.
protocolstring (enum)yes—Which client to build, case-insensitive: socks (alias socks5), http, vmess, vless, shadowsocks (alias ss), wireguard (alias wg), or direct (alias freedom). Anything else fails with unknown outbound protocol "<name>", with the name in lower case. There is no trojan, hysteria2 or blackhole protocol here; use the built-in block tag to drop traffic.
serverstringdepends—The upstream host: an IP address or a domain name. For wireguard it is the peer's UDP endpoint host. Required for every protocol except direct, which ignores it. Write an IPv6 address without brackets. A domain is resolved with the [dns] resolver when a connection is made, except for wireguard, whose endpoint name goes through the system resolver when the tunnel starts. Empty fails with outbound needs a non-empty server and non-zero port.
portu16depends—The upstream port; the peer's UDP port for wireguard. Required and non-zero for every protocol except direct. Zero or absent fails with outbound needs a non-empty server and non-zero port (got "<server>":0). This check runs before protocol is read, so it also catches a misspelled protocol that has no server or port.
usernamestringno—socks and http only. SOCKS5 username/password authentication, or HTTP Basic Proxy-Authorization. Used only when password is also set; with one of the two missing, the client does not authenticate at all.
passwordstringdepends—For socks and http, the password that goes with username. For shadowsocks it is required and holds the secret: the password for a classic method, or the base64 PSK for a 2022- method, optionally as an iPSK:uPSK chain. Missing for shadowsocks fails with shadowsocks outbound <tag> needs a password. A 2022 key shorter than the method's key length fails with shadowsocks-2022: PSK too short (…), and invalid base64 with decode PSK: ….
uuidstringdepends—Required for vmess and vless: the account UUID on the upstream server. Missing fails with outbound <tag> needs a uuid; a malformed value fails with outbound <tag>: uuid is not a valid UUID, which does not repeat the value, because it is a credential.
securitystring (enum)no"auto"vmess only. The body cipher, case-insensitive: auto, aes-128-gcm or aes128gcm select AES-128-GCM; chacha20-poly1305 or chacha20poly1305 select ChaCha20-Poly1305. auto always means AES-128-GCM. none, zero and anything else fail with unsupported vmess security "<name>".
methodstring (enum)depends—Required for shadowsocks; the name picks the generation. SIP022: 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm, 2022-blake3-chacha20-poly1305, written exactly in lower case. Classic AEAD, case-insensitive: aes-128-gcm, aes-256-gcm, chacha20-poly1305 (or chacha20-ietf-poly1305), xchacha20-poly1305 (or xchacha20-ietf-poly1305), and the aead_aes_128_gcm, aead_aes_256_gcm, aead_chacha20_poly1305 aliases. Anything else, including an empty or missing value, fails with unsupported shadowsocks cipher "<name>".
global_paddingboolnofalsevmess only. Sets the VMess global padding option: each chunk carries 0 to 63 bytes of padding, which hides the exact payload sizes at a small cost in bandwidth.
address_familystring (enum)no"auto"Which resolved addresses the outbound may use, for any protocol: auto (keep the resolver's order), ipv4_only, ipv6_only, prefer_ipv4, prefer_ipv6. Case-insensitive, surrounding spaces are ignored, - is read as _, an empty string means auto, and these aliases are accepted: ipv4, v4, 4, ipv4only; ipv6, v6, 6, ipv6only; prefer_v4, ipv4_prefer, v4_prefer; prefer_v6, ipv6_prefer, v6_prefer. It applies to the destination for direct and wireguard, and to the server name for the other protocols. Anything else fails with outbound <tag> invalid address_family "<value>".
private_keystringdepends—Required for wireguard: your own Curve25519 private key, the PrivateKey line of a wg-quick file. 32 bytes as padded standard base64 (what wg genkey prints) or 64 hex digits. Missing fails with wireguard outbound <tag> needs a private_key; a bad encoding with wireguard outbound <tag> private_key: invalid WireGuard key: expected base64 or hex encoding of 32 bytes.
public_keystringdepends—Required for wireguard: the peer's public key, the PublicKey line of the [Peer] section. Same encodings and error forms as private_key. etemenanki-app calls this key peer_public_key.
pre_shared_keystringno—wireguard only. Optional pre-shared key, the PresharedKey line. Same encodings as private_key; a bad one fails with wireguard outbound <tag> pre_shared_key: …. Set it only when the peer has one for you. etemenanki-app spells it preshared_key.
local_addressarray of stringsdepends—Required for wireguard, at least one entry: the tunnel-local addresses from the Address line, such as ["10.8.0.2/32", "2001:db8:a::2/128"]. Anything from / on is dropped, so the prefix length is optional. Without an IPv6 entry the tunnel cannot reach IPv6 destinations, and the other way round. Errors: wireguard outbound <tag> needs at least one local_address, wireguard outbound <tag> invalid local_address "<value>": ….
mtuintegerno1420wireguard only. 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.
keepaliveu16no—wireguard only. 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.
reservedarray of integersno—wireguard only. Exactly three bytes, each 0 to 255, written into header bytes 1 to 3 of every outgoing WireGuard packet, as Xray's reserved does. Set it only when your service tells you to. Another length fails with wireguard outbound <tag> reserved must be exactly 3 bytes; a value above 255 fails the parse.