Error messages
This page lists the errors you meet while writing a configuration for etemenanki-app or katana: what each message says, what caused it and what to change. It is grouped by the part of the file that fails, so you can search the page for the text your terminal shows.
Every message on this page is the text that etemenanki 2.0.2 (whose etemenanki-app still reports version 2.0.0) and katana 3.0.1 print, checked against their source and captured by running --test on small broken files. Where a message can only appear at start or during a reload, the page says so. In the messages, <tag> stands for an inbound, outbound or balancer tag, <value> for the value you wrote, <path> for a file path, <error> for the text of an underlying error, <id> for a katana node’s api.node_id, <name> for the node name katana’s reload lines use (<panel>@<host>#<id>[/<type>], see katana reload failures) and <N> for a number of seconds. Everything else is printed exactly as shown.
Reading an error
Section titled “Reading an error”Both programs build the whole configuration before they open a socket, and they stop at the first error. Fix it, run --test again, and repeat until the file passes. The order of the checks is on Command line: for etemenanki-app it is parse, at least one outbound, DNS, outbounds, balancers, routing, inbounds; for katana it is parse, DNS and outbounds, at least one node, then each node.
Where the message appears
Section titled “Where the message appears”The message itself is the same wherever it appears. Only the prefix in front of it changes.
| Situation | etemenanki-app prefix | katana prefix |
|---|---|---|
--test |
configuration invalid: on an ERROR log line, standard output |
configuration error: on standard error |
| Start | failed to start: |
failed to load config: (read or parse), failed to build outbounds: ([dns] or [[outbound]]), node <id>: (panel type or node type), node <id>: build router: (routing) |
| Node bring-up (katana only) | node <id>: initial start failed: <error>; retrying in <N>s, and the same form for the other retry reasons (see Retries while a node comes up); later node <id>: rebuild failed: |
|
| Reload | reload: parse failed, keeping current config: , reload: build failed, keeping current config: |
config reload failed, keeping current: , reload: bad outbounds, keeping current config: , reload: node <name>: <error>; keeping current config, node <id>: config edit refused, keeping the running one: , and after an [[outbound]] change node <id>: route rebuild failed, keeping current: |
katana also puts config parse error: in front of every TOML error and config not utf-8: in front of an encoding error, wherever they appear, so at start you read failed to load config: config parse error: …. Under --test it adds node <id>: in front of a Hysteria 2 node’s errors.
A TOML error spans several lines. It gives the line and column, quotes the line, and ends with the reason:
$ etemenanki-app --test -c config.toml2026-09-25T03:57:12.004118Z ERROR etemenanki_app: configuration invalid: TOML parse error at line 8, column 1 |8 | prot = 1080 | ^^^^unknown field `prot`, expected one of `tag`, `protocol`, `listen`, `port`, `stream`, `address_family`, `sniffing`, `settings`Messages that do not say where
Section titled “Messages that do not say where”Most messages name the inbound, outbound or node they are about. These do not, so you have to find the cause from the value they quote, or by commenting out sections and running --test again:
- operating-system errors such as
No such file or directory (os error 2)andPermission denied (os error 13). They come from any file the configuration names: the config file itself,cert_file,key_file,ca_file,[dns].ca_file,geoipandgeosite. The path is not printed; - OpenSSL errors, which start with
error:followed by a code, and the relatedno certificate in PEM bundleandno certificate in CA PEM bundle; - Shadowsocks 2022 key errors:
decode PSK: …andshadowsocks-2022: …; - errors from a Hysteria 2 user table or certificate, which start with
hysteria2:; unknown vmess security "<value>",unknown balancer strategy "<value>" …,a balancer needs at least one outboundandwireguard cannot be used as an inbound (no server implementation);- every routing error. They quote the value or tag, but not the rule.
etemenanki-app
Section titled “etemenanki-app”TOML syntax and unknown keys
Section titled “TOML syntax and unknown keys”The whole file is parsed before anything is built. Every table rejects keys it does not know, so a typo stops the proxy instead of being ignored.
| Message | Meaning | Fix |
|---|---|---|
TOML parse error at line <n>, column <m> followed by unknown field `<key>`, expected one of … |
A key is misspelled, or sits in a table that has no such key. The list shows the keys that table accepts. | Correct the key. If the key is right, check which table it is in (see below). |
missing field `<key>` |
A required key is absent: tag or protocol in [[inbound]] and [[outbound]], outbound in [[route.rule]], tag or outbounds in [[balancer]]. |
Add the key. |
invalid type: string "<value>", expected u16 |
A number was written in quotes, for example port = "1080". |
Remove the quotes. |
invalid value: integer `<n>`, expected u16 |
The number is out of range, for example port = 70000. |
Use a value from 0 to 65535. |
invalid type: string "<value>", expected a boolean |
A flag such as sniffing was written as a string. |
Write true or false without quotes. |
duplicate key |
A table such as [dns] or [route] appears twice, or a key appears twice in one table. |
Merge the two into one. |
unclosed array table, expected `]]`, unclosed table, expected `]`, string values must be quoted, expected literal string, … |
The TOML itself is malformed at the reported position. | Fix the syntax at the line and column shown. |
invalid utf-8 sequence of <n> bytes from index <i> |
The file is not UTF-8, for example it was saved as UTF-16 or with a legacy code page. | Save the file as UTF-8. |
No such file or directory (os error 2), Is a directory (os error 21) |
The path given to -c does not exist or is a directory. |
Pass the right path. A relative path is resolved against the working directory. |
config defines no outbounds |
The file has no [[outbound]]. An empty file fails this way. |
Add at least one outbound. The first one is the default route. |
Protocol settings tables
Section titled “Protocol settings tables”The [inbound.settings] and [outbound.settings] tables are checked after the file has been parsed, when the protocol is known. Their errors therefore carry the tag instead of a line number.
| Message | Meaning | Fix |
|---|---|---|
inbound <tag>: invalid settings: unknown field `<key>`, expected one of … |
The settings table has a key this protocol does not accept. The list shows the accepted keys. | Correct or remove the key. The protocol pages list every key. |
inbound <tag>: invalid settings: missing field `<key>` |
A required setting is missing, for example password for a Trojan user. A second line in `users` names the enclosing key when there is one. |
Add the key. |
inbound <tag>: invalid settings: invalid type: … |
A setting has the wrong type. A second line, such as in `udp` , names it. |
Use the type the protocol page gives. |
outbound <tag>: invalid settings: … |
The same three errors for an outbound. | As above. |
DNS resolver
Section titled “DNS resolver”These come from [dns]. katana prints the same messages for its own [dns] table.
| Message | Meaning | Fix |
|---|---|---|
dns: unknown backend "<value>" (expected "system", "udp", "tls" or "https") |
backend is not one of the four names. "doh" and "dot" are not accepted. |
Use "https" for DNS over HTTPS and "tls" for DNS over TLS. |
dns: the udp backend needs a server address (also tls, https) |
Every backend except system needs server. |
Add server = "<ip>:<port>". |
dns: invalid server address: invalid socket address syntax |
server is not an IP address with a port. A host name or a bare IP is refused. |
Write it as "192.0.2.53:53" or "[2001:db8::53]:853". |
dns: the tls backend needs a server name to verify against |
backend = "tls" without server_name. |
Add the name on the resolver’s certificate. |
dns: the https backend needs the resolver's url |
backend = "https" without url. |
Add url, for example "https://dns.example.com/dns-query". |
dns: "<url>" is not an https:// url |
The url does not start with https://. |
Use the https:// form. |
dns: "<url>" has no usable host |
The url has no host, or has a user name (user@) before it. |
Remove the user part and keep the host. |
No such file or directory (os error 2) |
ca_file names a missing file. The file is read whatever the backend, even system. |
Fix the path, or remove ca_file. |
no certificate in CA PEM bundle |
ca_file has no PEM certificate in it (backends tls and https). |
Point it at a PEM file with at least one BEGIN CERTIFICATE block. |
Stream settings and TLS
Section titled “Stream settings and TLS”These come from [inbound.stream] and [outbound.stream] and their tls, ws and grpc subtables. The prefix is inbound <tag>: or outbound <tag>: .
| Message after the prefix | Meaning | Fix |
|---|---|---|
security = "tls" is not valid with network = "tcp"; use network = "tls" for TLS over plain TCP (security = "tls" layers TLS under network = "ws" or "grpc") |
The Xray spelling of TLS over TCP. Here TLS over TCP is its own network. | Replace both keys with network = "tls". |
unknown stream security "<value>" (expected "tls" or "none") |
security is neither "tls" nor "none". The value is case-sensitive, and "reality" and "xtls" are not supported. |
Write "tls" or "none", in lower case. |
unknown stream network "<value>" |
network is not tcp, tls, ws or grpc. "h2", "http", "quic" and "kcp" are not supported. |
Choose one of the four networks. |
grpc stream needs grpc.service_name |
network = "grpc" without [….stream.grpc] service_name. |
Add the service name. It must match on both ends. |
protocol <name> does not support stream network "<value>" |
The protocol has no transport of its own: SOCKS and Shadowsocks inbounds, Hysteria 2 and TUN, and the freedom, blackhole, wireguard and hysteria2 outbounds. |
Remove the [….stream] table. |
protocol <name> does not support stream security "<value>" |
The same, for security. |
Remove the [….stream] table. |
tls stream needs tls.cert_file |
An inbound uses TLS (network = "tls", or security = "tls" under ws or grpc) without a certificate. |
Add cert_file under [inbound.stream.tls]. |
tls stream needs tls.key_file |
The same for the private key. | Add key_file. |
tls stream needs tls.server_name or server (also ws+tls and grpc+tls) |
An outbound using TLS has neither server_name nor server to verify the certificate against. |
Set server, or server_name under [outbound.stream.tls]. |
ws stream needs ws.host or server |
A WebSocket outbound has no host for the Host header. |
Set server, tls.server_name or ws.host. |
grpc stream needs grpc.authority or server |
A gRPC outbound has no authority. | Set server, tls.server_name or grpc.authority. |
tls.allow_insecure and tls.ca_file cannot both be set |
The two keys contradict each other. | Keep ca_file to trust a private CA, or allow_insecure to skip verification, not both. |
The certificate files produce errors without a prefix:
| Message | Meaning | Fix |
|---|---|---|
No such file or directory (os error 2) |
cert_file, key_file or ca_file does not exist at that path. |
Fix the path. Relative paths are resolved against the working directory. |
Permission denied (os error 13) |
The process may not read the file, typically the private key. | Give the service user read access. |
no certificate in PEM bundle |
cert_file holds no PEM certificate. |
Point it at the certificate chain, leaf first. |
no certificate in CA PEM bundle |
An outbound’s ca_file holds no PEM certificate. |
Point it at the CA certificate. |
error:1E08010C:DECODER routines:…:No supported data to decode. Input type: PEM |
key_file holds no PEM private key. The exact OpenSSL text varies with the OpenSSL version. |
Point it at the unencrypted PEM private key. |
error:0A0000BE:SSL routines:SSL_CTX_check_private_key:no private key assigned:… |
The private key does not belong to the certificate. | Use the key that was generated with this certificate. |
Listen addresses and binding
Section titled “Listen addresses and binding”The prefix is inbound <tag>: .
| Message after the prefix | Meaning | Fix |
|---|---|---|
port is required |
An inbound listening on an IP address has no port. |
Add port. |
a unix socket listen has no port; remove port |
listen starts with /, which makes it a Unix socket path, and port is also set. |
Remove port. |
abstract unix sockets are not supported; use a filesystem path |
listen starts with @. |
Use a filesystem path such as /run/etemenanki/http.sock. |
hysteria2 listens on UDP and cannot use a unix socket |
A Hysteria 2 inbound has a path in listen. |
Give an IP address and a port. |
socks over a unix socket has no local IP for UDP associate; set udp_bind or udp = false |
A SOCKS inbound on a Unix socket still has UDP on, which is the default. | Set udp = false, or set udp_bind to an IP address of this host in the family of the addresses clients send datagrams from, such as 127.0.0.1 for local IPv4 clients. Each client’s UDP ASSOCIATE must then name the exact address and port its datagrams come from. See Listening on a Unix socket. |
protocol <name> over a unix socket does not support stream network "<value>" |
A Unix-socket inbound has a [inbound.stream] table. A Unix socket carries no transport. |
Remove the [inbound.stream] table. |
tun owns a network interface and has no listener; remove listen/port |
A TUN inbound has listen or port. |
Remove both. |
tun mtu must be at least 1280 |
The TUN mtu is below the IPv6 minimum. |
Use 1280 or more. |
bad tun address "<value>": <error> |
An entry in address is not an IP address, with or without a prefix length. |
Write it as "198.18.0.1/15" or "198.18.0.1". |
bad tun route "<value>": host part of address was not zero |
A route has host bits set, for example "198.18.0.1/15". |
Write the network address: "198.18.0.0/15". |
--test does not bind anything, so these appear only at start (failed to start: …) or on a reload:
| Message | Meaning | Fix |
|---|---|---|
inbound <tag> bind <bind> failed: Address already in use (os error 98) |
Another program, or another inbound in the same file, holds the port. Two inbounds on one port pass --test. |
Free the port or change it. |
inbound <tag> bind <bind> failed: Permission denied (os error 13) |
A port below 1024 without the right privilege. | Grant CAP_NET_BIND_SERVICE, or use a higher port. |
inbound <tag> bind <bind> failed: Cannot assign requested address (os error 99) |
listen is not an address of this host. |
Use an address the host has, or 0.0.0.0. |
inbound <tag> bind unix:<path> failed: <path> exists and is not a socket |
The Unix socket path is taken by a regular file or directory. A stale socket is replaced, anything else is left alone. | Remove the file or choose another path. |
inbound <tag> bind tun auto failed: Operation not permitted (os error 1) |
Creating a TUN device needs CAP_NET_ADMIN. tun auto becomes tun <name> when name is set. |
Run with CAP_NET_ADMIN. See TUN. |
<bind> is <host>:<port> for TCP, udp <host>:<port> for Hysteria 2, unix:<path> for a Unix socket and tun <name> for TUN.
A SOCKS inbound that passes --test can still refuse a UDP ASSOCIATE when a client asks for one. The inbound then answers with SOCKS5 reply 0x02 (connection not allowed by ruleset), closes the control connection and logs the reason at debug level. In these lines <source> is Some(<ip>), the client’s address, over TCP and None over a Unix socket.
| Log line | Meaning | Fix |
|---|---|---|
socks connection from <source> ended: socks: UDP associate from an address family the relay is not bound in |
The relay could never hear the client, so the inbound refuses the association instead of letting it go quiet. An IPv4 udp_bind, or an IPv4-mapped IPv6 one such as ::ffff:192.0.2.10, hears only IPv4 clients, :: hears both, and any other IPv6 udp_bind hears only IPv6 clients. For example, udp_bind = "127.0.0.1" and a client that connects over ::1. Over a Unix socket the family is that of the address the client’s request names. |
Set udp_bind in the family your clients connect over, or run one inbound per family. |
socks connection from None ended: socks: UDP associate over a unix socket must name its source address and port |
A client on a Unix socket sent a UDP ASSOCIATE that names a zero address, a zero port or a domain name. Over TCP the inbound holds the association to the control connection’s address, but a Unix socket has no such address, so the request is all it has to go on. RFC 1928 lets a client send zeros when it does not know its address, and many clients do, including etemenanki-app’s own SOCKS outbound. |
Have the client name the exact address and port it will send datagrams from, or set udp = false. See UDP ASSOCIATE. |
Inbound and outbound protocols
Section titled “Inbound and outbound protocols”| Message | Meaning | Fix |
|---|---|---|
inbound <tag>: unknown protocol "<value>" |
Not an inbound protocol. The inbound protocols are socks, http, trojan, vless, vmess, shadowsocks, hysteria2 (also hysteria, hy2) and tun. |
Correct the name. Names are lower case. |
outbound <tag>: unknown protocol "<value>" |
Not an outbound protocol. The outbound protocols are freedom (also direct), blackhole (also block), socks, http, trojan, vless, vmess, shadowsocks, hysteria2 (also hysteria, hy2) and wireguard. |
Correct the name. |
wireguard cannot be used as an inbound (no server implementation) |
WireGuard is outbound-only. | Use WireGuard as an [[outbound]] only. |
outbound <tag>: missing server |
A proxy outbound has no server. |
Add the upstream host name or IP address. |
outbound <tag>: missing port |
A proxy outbound has no port. |
Add the upstream port. |
inbound <tag>: unknown socks auth "<value>" |
auth is not "none" or "password". |
Use one of the two. |
inbound <tag>: invalid uuid "<value>": <error> |
A VLESS or VMess user id is not a UUID. |
Use the 36-character form, for example 11111111-2222-3333-4444-555555555555. |
outbound <tag>: invalid uuid "<value>": <error> |
The same for an outbound id. |
As above. |
unknown vmess security "<value>" |
A VMess outbound security is not auto, aes-128-gcm or chacha20-poly1305 (any case). The value is shown in lower case. |
Use one of the three, or leave it out for aes-128-gcm. |
inbound <tag>: unknown shadowsocks method "<value>" (also outbound) |
Not a supported method: aes-128-gcm, aes-256-gcm, chacha20-poly1305 (also chacha20-ietf-poly1305), xchacha20-poly1305 (also xchacha20-ietf-poly1305), in any case, or a 2022- method. Stream ciphers such as rc4-md5 are not supported. |
Use a supported AEAD method. |
inbound <tag>: unknown shadowsocks-2022 method "<value>" (also outbound) |
A method that starts with 2022- but is not 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm or 2022-blake3-chacha20-poly1305. |
Correct the name. These three are case-sensitive. |
decode PSK: <error> |
A Shadowsocks 2022 password is not standard base64. | Use a base64 key, for example from openssl rand -base64 32. |
shadowsocks-2022: PSK too short (<n> < <len>) |
The decoded key has fewer bytes than the method needs: 16 for 2022-blake3-aes-128-gcm, 32 for the other two. |
Generate a key of the right length: openssl rand -base64 16 or openssl rand -base64 32. |
shadowsocks-2022: multi-user requires an aes-gcm method |
A Shadowsocks 2022 inbound with users uses 2022-blake3-chacha20-poly1305. |
Use an aes-gcm method for multi-user. |
outbound <tag>: invalid address_family "<value>" |
Not auto, ipv4_only, ipv6_only, prefer_ipv4 or prefer_ipv6. The spellings ipv4, v4, 4 and their IPv6 counterparts are also accepted, in any case and with - in place of _. |
Use one of those values. |
Tags and balancers
Section titled “Tags and balancers”| Message | Meaning | Fix |
|---|---|---|
duplicate outbound tag: <tag> |
Two [[outbound]] sections share a tag. |
Rename one. |
duplicate inbound tag: <tag> |
Two [[inbound]] sections share a tag. |
Rename one. |
balancer tag <tag> collides with an outbound tag |
A balancer uses a tag that an outbound, or an earlier balancer, already has. | Give the balancer its own tag. |
balancer <tag> references unknown outbound tag: <member> |
A name in outbounds is not an outbound tag. A balancer cannot contain another balancer. |
Correct the member tag. |
balancer <tag>: outbound <member> has no upstream a TCP health probe can reach, so it cannot be balanced |
The health probe is a TCP connect to the member’s server and port. The member has no server and port, as freedom, blackhole and wireguard outbounds normally do not, or it is hysteria2, which listens on UDP only. |
Balance only proxy outbounds with a TCP upstream. |
unknown balancer strategy "<value>" (expected "failover" or "round_robin") |
strategy has another value. |
Use "failover" (the default) or "round_robin". |
a balancer needs at least one outbound |
outbounds = []. |
List at least one member. |
Balancers explains the probe and the strategies.
Routing rules and geodata
Section titled “Routing rules and geodata”Routing errors do not name the rule. Search the file for the value they quote. An inbound_tag that names no inbound is not an error: that rule never matches.
| Message | Meaning | Fix |
|---|---|---|
route references unknown outbound tag: <tag> |
A rule’s outbound, or [route].default, names no outbound or balancer. |
Correct the tag. |
invalid cidr "<value>": host part of address was not zero |
A cidr or source_cidr entry has host bits set, such as "192.0.2.1/24". |
Write the network address: "192.0.2.0/24", or "192.0.2.1/32" for one address. |
invalid cidr "<value>": invalid length for network: … |
The prefix length is too long, such as /33 for IPv4. |
Use /0 to /32 for IPv4 and /0 to /128 for IPv6. |
invalid cidr "<value>": couldn't parse address in network: invalid IP address syntax |
The entry is not an address at all, for example a host name. | Use an IP network. Match names with the domain matchers. |
invalid rule network "<value>" (expected "tcp" or "udp") |
network is a list or another word, such as "tcp,udp". |
Use "tcp" or "udp", or leave network out to match both. |
invalid port spec: "<value>" |
A port entry is not a number or a low-high range. Comma lists such as "80,443" and names such as "https" are refused. |
Write one entry per port or range: port = ["80", "443", "8000-9000"]. |
TOML parse error … invalid type: integer `<n>`, expected a string |
A port entry is written as a number, for example port = [443]. Ports in rules are strings. |
Quote each entry: port = ["443"]. |
invalid port spec: "<value>" has a lower bound above its upper bound |
A range such as "443-80". |
Put the lower bound first. |
invalid domain regex "<value>": regex parse error: … |
A domain_regex entry does not compile. The message continues over several lines and points at the problem. |
Fix the pattern, or use domain_suffix or domain_full. |
a geosite matcher is used but no geosite file is configured |
A rule uses geosite and [route].geosite is not set. |
Set [route].geosite to the path of geosite.dat. |
a geoip matcher is used but no geoip file is configured |
The same for geoip. |
Set [route].geoip. |
No such file or directory (os error 2) |
The geodata file does not exist at that path. | Fix the path. The file is only read when a rule uses it. |
geosite code not found: <code> |
The list name is not in geosite.dat. The prefix geosite: from Xray is not stripped, so "geosite:cn" fails this way. |
Write the bare name, for example "cn". The comparison ignores case. |
geoip code not found: <code> |
The same for geoip.dat, including a geoip: prefix. |
Write the bare code, for example "cn" or "!cn". |
geosite decode: failed to decode Protobuf message: <error> |
The geosite file is not a geosite .dat: truncated, an HTML error page from a failed download, or the geoip file. |
Download the file again and check that the two paths are not swapped. |
geoip decode: failed to decode Protobuf message: <error> |
The same for the geoip file. | As above. |
geoip cidr: <error> |
The geoip file decodes, but a network in the list a rule uses is malformed: an address that is not 4 or 16 bytes, or a prefix longer than the address. | Replace the geoip file. |
WireGuard outbound
Section titled “WireGuard outbound”The prefix is outbound <tag>: .
| Message after the prefix | Meaning | Fix |
|---|---|---|
invalid wireguard private_key |
The key is not the base64 or hex form of 32 bytes. | Paste the key from wg genkey, or from the PrivateKey line of a wg-quick file. |
invalid wireguard peer_public_key |
The same for the peer’s public key. | Use the PublicKey from the peer’s [Peer] section. |
invalid wireguard preshared_key |
The same for the preshared key. | Use the PresharedKey value, or remove the key. |
wireguard endpoint must be host:port |
endpoint has no :. |
Write "198.51.100.1:51820". The port is whatever follows the last :, so write an IPv6 endpoint without brackets: "2001:db8::1:51820". |
invalid wireguard endpoint port |
The part after the last : is not a port number. |
Correct the port. |
invalid settings: missing field `address` |
address is required. |
Add the tunnel address, for example address = ["10.0.0.2"]. |
invalid settings: invalid IP address syntax with in `address` |
An address entry has a prefix length, such as "10.0.0.2/32". This key takes plain addresses. |
Remove the /32 or /128. |
invalid settings: invalid length <n>, expected an array of length 3 with in `reserved` |
reserved does not have three numbers. |
Write three bytes, for example reserved = [0, 0, 0], or remove the key. |
wireguard address_family ipv6_only needs an IPv6 address (or ipv4_only … IPv4) |
address_family allows one family, and address has no address of that family. |
Add an address of that family, or change address_family. |
WireGuard maps each key to its wg-quick field.
Hysteria 2
Section titled “Hysteria 2”A Hysteria 2 inbound checks its certificate first, then its users, then everything else. Messages with the prefix inbound <tag>: or outbound <tag>: are shown with it; the others are printed without one.
| Message | Meaning | Fix |
|---|---|---|
inbound <tag>: hysteria2 needs both cert_file and key_file |
One or both are missing from [inbound.settings]. Hysteria 2 always uses TLS. |
Set both. |
hysteria2: the certificate file contains no certificates |
cert_file holds no PEM certificate. |
Point it at the certificate chain. |
hysteria2: the key file contains no private key |
key_file holds no PEM private key. |
Point it at the private key. |
hysteria2: could not read the certificate: <error> |
The PEM in cert_file is damaged. |
Replace the file. |
hysteria2: could not read the private key: <error> |
The PEM in key_file is damaged. |
Replace the file. |
hysteria2: certificate and key do not match: <error> |
The key does not belong to the certificate. | Use the matching key. |
inbound <tag>: password and users cannot both be set; a credential would have two answers |
Both authentication modes are configured. | Keep password for one shared password, or users for one entry per user. |
inbound <tag>: hysteria2 needs password or users |
Neither is set. | Set one. |
hysteria2: the password must not be empty |
password = "". |
Set a password. |
hysteria2: a user needs both a name and a password |
A users entry has an empty user or pass. |
Fill in both. |
hysteria2: a username cannot contain ':' — it separates the two on the wire |
A user contains :. |
Choose a name without a colon. |
hysteria2: two users share a name once lower-cased |
Two user values are the same when case is ignored. |
Give them distinct names. |
inbound <tag>: hysteria2: 233 is the authentication success status and cannot be used for the masquerade |
masquerade.status = 233. |
Use another status, such as the default 404. |
inbound <tag>: hysteria2: <n> is not an HTTP status code |
masquerade.status is outside 100 to 999. |
Use a real status code. |
inbound <tag>: obfs_password is set but obfs is not; did you mean obfs = "salamander"? |
obfs_password without obfs. |
Add obfs = "salamander", or remove obfs_password. |
inbound <tag>: obfs_password must be at least 4 bytes for salamander |
obfs = "salamander" with a short or missing obfs_password. |
Use a longer password. |
inbound <tag>: unknown obfs "<value>" (expected "salamander") |
obfs has another value. It is case-sensitive. |
Write "salamander". |
inbound <tag>: udp_idle_timeout must be between 2 and 600 seconds |
The value is out of range. | Use 2 to 600, or leave it out for 60. |
inbound <tag>: udp_idle_timeout is set but udp is not enabled |
udp_idle_timeout without udp = true. |
Set udp = true, or remove the timeout. |
inbound <tag>: max_connections must be at least 1 (also max_circuits) |
The limit is 0. |
Use a positive number, or leave it out for the default. |
| Message | Meaning | Fix |
|---|---|---|
outbound <tag>: invalid settings: missing field `password` |
password is required. |
Add it. |
outbound <tag>: hysteria2 password must not be empty |
password = "". |
Set the server’s password. |
outbound <tag>: allow_insecure and ca_file cannot both be set |
The two contradict each other. | Keep one. |
outbound <tag>: obfs_password is set but obfs is not; did you mean obfs = "salamander"? |
obfs_password without obfs. |
Add obfs = "salamander". |
outbound <tag>: obfs_password must be at least 4 bytes for salamander |
The obfuscation password is too short or missing. | Use the server’s obfuscation password. |
outbound <tag>: unknown obfs "<value>" (expected "salamander") |
obfs has another value. |
Write "salamander". |
outbound <tag>: max_concurrent_streams must be at least 1 |
The value is 0. |
Use a positive number, or leave it out. |
outbound <tag>: missing server |
No server. |
Add the server’s host name or address. |
--test reads the outbound’s ca_file but does not parse it. A file without a certificate passes the check and fails when the outbound connects.
Hysteria 2 documents every key.
etemenanki-app reload failures
Section titled “etemenanki-app reload failures”When the config file changes, etemenanki-app parses and builds the new file before it touches the running configuration. These lines come from the etemenanki_app::instance log target, except config hot-reload disabled, which comes from etemenanki_app.
| Log line | What happened | What to do |
|---|---|---|
reload: cannot read <path>: <error> |
The file could not be read. The running configuration stays. | Fix the file’s path or permissions. |
reload: parse failed, keeping current config: <error> |
The new file does not parse. <error> is one of the TOML errors above. The running configuration stays. |
Fix the file and save it again. |
reload: build failed, keeping current config: <error> |
The new file parses but does not build. <error> is any build error on this page. The running configuration stays. |
Fix the cause and save the file again. |
inbound <tag> bind <bind> failed: <error> |
The new configuration replaced the old one, but this inbound could not bind. The other inbounds run; this one stays down. | Free the port and save the file again. |
config hot-reload disabled: <error> |
At start, the config directory could not be watched. The proxy runs, but ignores edits until a restart. | Check that the directory exists and is readable. |
etemenanki-app remembers the bytes of the last file it read, whether it applied the file or rejected it, and does not try the same bytes again. If the cause was outside the file, such as a missing certificate or a busy port, fixing it is not enough: change the config file as well, even by one comment character. etemenanki-app hot reload explains the reload sequence.
katana
Section titled “katana”katana uses the same kernel as etemenanki-app, so its DNS, outbound key, routing and Hysteria 2 messages often match the ones above word for word. This part lists katana’s own messages and the places where they differ.
File, parse and nodes
Section titled “File, parse and nodes”| Message | Meaning | Fix |
|---|---|---|
config parse error: TOML parse error at line <n>, column <m> … |
The file is not valid TOML, has an unknown key or a wrong type. The reasons are the same as in TOML syntax and unknown keys. | Fix the key at the reported line. |
config parse error: … unknown field `ApiHost`, expected one of `host`, … |
An XrayR key name. katana uses its own snake-case names. | Rename the keys. Migrating from XrayR has the mapping. |
config not utf-8: invalid utf-8 sequence of <n> bytes from index <i> |
The file is not UTF-8. | Save it as UTF-8. |
No such file or directory (os error 2) |
The config file, [dns].ca_file, or a geodata file a rule needs does not exist. |
Fix the path. |
config defines no [[node]] entries |
The file has no [[node]]. An empty file fails this way. |
Add a node. |
dns: … |
[dns] is invalid. |
See DNS resolver: the messages are identical. |
At start, a parse error is logged as failed to load config: <error>, and a [dns] or [[outbound]] error as failed to build outbounds: <error>. Both stop katana with exit status 1.
panel_type and node_type
Section titled “panel_type and node_type”| Message | Meaning | Fix |
|---|---|---|
unknown panel_type "<value>" |
panel_type is missing or not NewV2board, V2board or SSPanel (any case). The value is shown in lower case, so a missing key shows "". |
Use one of the three. An Xboard panel uses NewV2board. See Xboard and V2board. |
unknown node_type "<value>" |
api.node_type is missing or not V2ray, Vmess, Vless, Trojan, Shadowsocks, Hysteria2, Hysteria or Hy2 (any case). |
Use the type the panel has for this node. |
At start these appear as node <id>: unknown panel_type "<value>" and node <id>: unknown node_type "<value>". katana skips that node and starts the others. When no node is left, it logs no nodes could be started and exits with status 1. On a reload, the same errors refuse the whole reload: katana logs reload: node <name>: unknown panel_type "<value>"; keeping current config (or unknown node_type) and applies nothing (see katana reload failures).
Two combinations pass --test and fail only when the node first contacts its panel:
| Log line at start | Cause | Fix |
|---|---|---|
node <id>: node_info failed: sspanel: Shadowsocks node type is not supported; retrying in <N>s |
panel_type = "SSPanel" with node_type = "Shadowsocks". |
Serve Shadowsocks from an Xboard or V2board panel. |
node <id>: node_info failed: sspanel: a hysteria2 node needs custom_config; the legacy server string cannot describe one; retrying in <N>s |
An SSPanel Hysteria 2 node read in the legacy format: disable_custom_config = true, or a panel older than version 2021.11. |
Use custom_config for the node. See SSPanel. |
Errors from the panel request itself, such as a wrong key or node ID, are covered in Troubleshooting.
Retries while a node comes up
Section titled “Retries while a node comes up”A node comes up in one attempt: katana reads the node from the panel, then its users, then starts the listener. When an attempt fails, katana logs why and tries again, so a panel that is restarting, a DNS server that is not answering yet or a port that is still held does not leave the node down:
| Log line | What failed |
|---|---|
node <id>: node_info failed: <error>; retrying in <N>s |
The node request to the panel. |
node <id>: panel returned no node info; retrying in <N>s |
The panel answered the node request with 304 Not Modified instead of the node. |
node <id>: panel returned port 0; retrying in <N>s |
An SSPanel panel gave the node port 0. On Xboard and V2board the same fault reads node <id>: node_info failed: newV2board: server port must be > 0; retrying in <N>s. |
node <id>: user_list failed: <error>; retrying in <N>s |
The user list request to the panel. |
node <id>: panel returned no user list; retrying in <N>s |
The panel answered the user request with 304 Not Modified instead of the list. |
node <id>: initial start failed: <error>; retrying in <N>s |
The listener did not start, for example because of a certificate or a node setting in the next two sections. |
The first wait is 1 second. It doubles after each failure, up to 60 seconds, and never exceeds the node’s update_periodic. An edit to the node’s [[node]] table that a reload applies makes katana retry at once, without waiting for the rest of the delay. Each attempt asks the panel afresh. Once the node is up, it no longer retries this way; later changes are applied by the regular poll.
Node certificates
Section titled “Node certificates”--test reads the certificate only for Hysteria 2 nodes, because the panel decides whether any other node uses TLS. For every other node type, these errors appear when the node starts, as node <id>: initial start failed: <error>; retrying in <N>s, or after a change, as node <id>: rebuild failed: <error>. katana builds the listener only once the panel lists at least one user, so a node with no users does not report them yet.
| Message | Meaning | Fix |
|---|---|---|
node requests kernel-unsupported feature: cert.reject_unknown_sni |
reject_unknown_sni = true. katana does not implement it, and refuses it on every node, with or without TLS. |
Remove the key. |
node requests kernel-unsupported feature: ACME cert mode "<mode>" |
mode is "dns", "http" or "tls". katana has no ACME client. The check applies to every node, with or without TLS. |
Use mode = "file" with certificates you renew yourself. |
TLS node requires cert.mode = "file" |
The panel turned TLS on, and mode is not "file". The default is "none". An unknown mode such as "files" passes until a TLS node meets it. |
Set mode = "file". |
TLS node requires cert.cert_file and cert.key_file |
mode = "file" with an empty path. |
Set both paths. |
No such file or directory (os error 2), Permission denied (os error 13) |
A certificate or key file is missing or unreadable. The path is not printed. | Check both paths and the file owner. |
no certificate in PEM bundle, or an OpenSSL message starting with error: |
The files are not a PEM certificate and a matching PEM private key. See Stream settings and TLS. | Replace the files. |
A Hysteria 2 node has its own messages for the same table:
| Message | Meaning | Fix |
|---|---|---|
hysteria2 node requires cert.mode = "file" |
A Hysteria 2 node without mode = "file", including the default "none" and the ACME modes. |
Set mode = "file" with cert_file and key_file. |
node requests kernel-unsupported feature: cert.reject_unknown_sni |
reject_unknown_sni = true. --test reports it for a Hysteria 2 node too. |
Remove the key. |
TLS node requires cert.cert_file and cert.key_file |
A path is empty. | Set both. |
hysteria2: the certificate file contains no certificates, hysteria2: the key file contains no private key, hysteria2: certificate and key do not match: <error> |
As for the etemenanki-app Hysteria 2 inbound. | Replace the files. |
Node settings katana cannot serve
Section titled “Node settings katana cannot serve”Some settings come from the panel and cannot be checked by --test. When the node starts, katana refuses the ones it does not implement instead of serving something else, and logs node <id>: initial start failed: <error>; retrying in <N>s (or node <id>: rebuild failed: <error> after a change). katana keeps retrying until the setting changes, so correct it on the panel.
| Message | Meaning | Fix |
|---|---|---|
node requests kernel-unsupported feature: REALITY |
The panel has REALITY turned on for this node. | Use TLS or no security on the panel. |
node requests kernel-unsupported feature: VLESS XTLS flow |
The panel sets a VLESS flow, such as xtls-rprx-vision, or api.vless_flow is set for an SSPanel node read in the legacy format. |
Clear the flow on the panel, or remove api.vless_flow. |
node requests kernel-unsupported feature: httpupgrade transport, … splithttp transport, … transport "<name>" |
The panel’s network is HTTPUpgrade, SplitHTTP or XHTTP, or another name katana does not know. katana serves TCP, WebSocket and gRPC. | Change the node’s network on the panel. |
node requests kernel-unsupported feature: shadowsocks cipher "<method>" |
The panel’s Shadowsocks method is not one of the supported AEAD or 2022- methods. |
Choose a supported method on the panel. |
node <id>: node_info failed: newV2board: shadowsocks obfs "<value>" is not supported; retrying in <N>s |
An Xboard or V2board Shadowsocks node has an obfuscation plugin set. | Turn the plugin off for this node. |
katana outbounds
Section titled “katana outbounds”katana’s [[outbound]] has a flat layout, with keys such as uuid and method directly in the table. Its messages differ from etemenanki-app’s.
| Message | Meaning | Fix |
|---|---|---|
duplicate/reserved outbound tag <tag> |
Two outbounds share a tag, or a tag is one of the built-in direct, block, freedom or blackhole. |
Rename the outbound. The built-in tags are always available to rules. |
unknown outbound protocol "<value>" |
Not direct (also freedom), socks (also socks5), http, vmess, vless, shadowsocks (also ss) or wireguard (also wg), in any case. Trojan and Hysteria 2 have no outbound in katana. |
Use a supported protocol. |
outbound needs a non-empty server and non-zero port (got "<server>":<port>) |
A proxy outbound without server or port. A protocol = "blackhole" entry fails the same way: use the built-in block tag instead. |
Add server and port. |
outbound <tag> needs a uuid |
A VMess or VLESS outbound without uuid. |
Add it. |
outbound <tag>: uuid is not a valid UUID |
The uuid does not parse. The value is not printed. |
Correct the UUID. |
unsupported vmess security "<value>" |
security is not auto, aes-128-gcm (also aes128gcm) or chacha20-poly1305 (also chacha20poly1305), in any case. none and zero are not supported. |
Use one of those, or leave it out. |
shadowsocks outbound <tag> needs a password |
A Shadowsocks outbound without password. |
Add it. |
unsupported shadowsocks cipher "<value>" |
method is missing or not a supported method. katana accepts the same methods as etemenanki-app, and reports an unknown 2022- method this way too. |
Set method to a supported name. |
decode PSK: <error>, shadowsocks-2022: PSK too short (<n> < <len>) |
A Shadowsocks 2022 key is not base64, or too short. | As in Inbound and outbound protocols. |
outbound <tag> invalid address_family "<value>" |
As for etemenanki-app, without the colon after the tag. | Use a listed value. |
wireguard outbound <tag> needs a private_key (also public_key) |
A required WireGuard key is missing. The peer’s key is called public_key here. |
Add it. |
wireguard outbound <tag> <field>: invalid WireGuard key: expected base64 or hex encoding of 32 bytes |
private_key, public_key or pre_shared_key does not decode to 32 bytes. |
Paste the key from wg genkey or wg pubkey. |
wireguard outbound <tag> needs at least one local_address |
No local_address. |
Add the tunnel address. Here a prefix length such as /32 is allowed. |
wireguard outbound <tag> invalid local_address "<value>": <error> |
An entry is not an IP address. | Correct it. |
wireguard outbound <tag> address_family ipv6_only needs an IPv6 local_address (or ipv4_only … IPv4) |
No local_address of the family address_family requires. |
Add one, or change address_family. |
wireguard outbound <tag> reserved must be exactly 3 bytes |
reserved does not have three entries. |
Write three numbers from 0 to 255. |
katana outbounds documents each key.
Hysteria 2 nodes
Section titled “Hysteria 2 nodes”For a node whose node_type is Hysteria2, Hysteria or Hy2, --test runs [node.hysteria] and the certificate through the same builder a start uses, without binding the port. Under --test the messages carry the prefix node <id>: ; at start they follow node <id>: initial start failed: and end with ; retrying in <N>s.
| Message | Meaning | Fix |
|---|---|---|
unknown hysteria credential kind "<value>" (expected "uuid" or "user_pass") |
credential has another value. |
Use "uuid" (the default) or "user_pass". |
obfs_password is set but obfs is not; did you mean obfs = "salamander"? |
obfs_password without obfs. |
Add obfs = "salamander". |
obfs_password must be at least 4 bytes for salamander |
The obfuscation password is shorter than 4 bytes. | Use a longer one. |
unknown obfs "<value>" (expected "salamander") |
obfs has another value. |
Write "salamander". |
udp_idle_timeout must be between 2 and 600 seconds |
Out of range. | Use 2 to 600. |
udp_idle_timeout is set but udp is not enabled |
Set without udp = true. |
Enable udp, or remove the timeout. |
hysteria2: 233 is the authentication success status and cannot be used for the masquerade |
[node.hysteria.masquerade] status = 233. |
Choose another status. |
hysteria2: <n> is not an HTTP status code |
The status is outside 100 to 999. |
Use a real status code. |
When the panel describes the node, obfs and obfs_password come from the panel, and the same obfuscation messages then point at the panel’s settings. katana Hysteria 2 explains which settings come from where.
Node routing
Section titled “Node routing”A node’s [node.route] compiles with the same code as etemenanki-app’s [route], so it prints the messages in Routing rules and geodata. The differences:
- a rule accepts only
outbound,domain_suffix,cidr,port,geositeandgeoip. Any other matcher, such asdomain_keyword, is a parse error:unknown field `domain_keyword`, expected one of `outbound`, `domain_suffix`, `cidr`, `port`, `geosite`, `geoip`; route references unknown outbound tag: <tag>means the tag is neither an[[outbound]]tag nor one of the built-indirect,block,freedomandblackhole. Withoutdefault, unmatched traffic goes todirect;- at start the message follows
node <id>: build router:, and katana skips that node. On a reload, see katana reload failures.
rule_list_path is not an error source. If the file cannot be read, the node starts without it and logs the warning cannot read rule_list_path <path>: <error>; a line that is not a valid regular expression is skipped with invalid local rule "<line>": <error>. See Audit rules.
katana reload failures
Section titled “katana reload failures”These lines come from the katana::runtime and katana::manager::node log targets.
| Log line | What happened | What to do |
|---|---|---|
config reload failed, keeping current: <error> |
The new file could not be read or does not parse. Nothing was applied. | Fix the file and save it again. |
reload: bad outbounds, keeping current config: <error> |
The new [[outbound]] list, or the [dns] table built with it, did not build. Nothing was applied, including the node changes. katana rebuilds [dns] only when [[outbound]] changed too, so a [dns] edit on its own is neither checked nor applied until a restart. |
Fix the outbound or [dns] and save again. Run katana --test to check [dns]. |
invalid log level "<value>": <error> |
The new [log].level does not parse. The old filter stays in force, although reload: log level → <value> is logged as well. |
Correct the level. |
reload: node <panel>@<host>#<id>[/<type>]: <error>; keeping current config |
A node the reload adds, or the panel client of a node whose [[node]] table changed, did not build, for example unknown panel_type "<value>", unknown node_type "<value>" or build router: <error>. Nothing was applied: every running node keeps its old configuration. A node that did not build at start is built again on each reload, so while it stays broken every reload is refused. <panel> is the panel_type in lower case, and /<type> is the node type katana asks an Xboard or V2board panel for. |
Fix the node and save again. Run katana --test before saving. |
node <id>: config edit refused, keeping the running one: <error> |
A running node’s new [node.route] did not compile, for example route references unknown outbound tag: <tag>. The node applied nothing from its edit and kept its connections. The rest of the reload was applied. |
Fix the rule and save again. Run katana --test before saving. |
node <id>: route rebuild failed, keeping current: <error> |
An [[outbound]] change broke a node’s rules, for example by removing an outbound tag a rule names. The node keeps its previous rules, and katana still rebuilds its listener, which drops its connections. |
Restore the outbound, or change the rule to another tag. |
config watcher disabled (no live reload): <error> |
At start, the config directory could not be watched. katana runs, but ignores edits until a restart. | Check the directory. |
katana hot reload describes what each change does to a running node.