Outbounds
An outbound is where katana sends a user’s traffic after the node has accepted it. By default that is direct: katana connects to the destination from its own host. With [[outbound]] tables you add named upstreams, such as a SOCKS5 or HTTP proxy, a VMess, VLESS or Shadowsocks server, or a WireGuard peer. A node’s route rules then pick one of them per connection or per UDP packet.
This page covers the outbound pool, every [[outbound]] key, what each protocol can carry, a complete WireGuard egress example, and how to check an upstream before you put it in front of users. Which traffic goes where is decided by the route table; see Routing.
One pool for every node
Section titled “One pool for every node”katana builds a single outbound pool for the whole process. Every [[node]] shares it, and each node’s [node.route] refers to outbounds by tag. An outbound used by several nodes is one object: a WireGuard outbound, for example, is one tunnel that carries the traffic of every node and user routed to it.
flowchart LR A["node 1 route"] --> P B["node 2 route"] --> P P["outbound pool"] --> D["direct / freedom"] P --> K["block / blackhole"] P --> S["socks-up"] P --> W["wg-egress"] D --> I["destination"] S --> U["upstream proxy"] W --> R["WireGuard peer"]
The pool always contains four built-in tags, whether or not you declare any [[outbound]]:
| Tag | Alias | What it does |
|---|---|---|
direct |
freedom |
Connects to the destination from this host, with address_family = "auto". It is also the route default: a node without [node.route].default sends unmatched traffic here. |
block |
blackhole |
Refuses a TCP connection and drops a UDP packet. A dropped packet is not billed to the user. |
You cannot redefine these tags. Declaring [[outbound]] with tag = "direct" fails the build, and so does declaring the same tag twice:
configuration error: duplicate/reserved outbound tag directTags are compared case-sensitively. Direct is not a reserved tag and is accepted as a new outbound, and a route that says outbound = "Up" does not find an outbound tagged up: it fails with route references unknown outbound tag: Up.
A minimal example
Section titled “A minimal example”This config forwards one domain’s traffic to an upstream SOCKS5 proxy and sends everything else directly:
[[node]]panel_type = "NewV2board"
[node.api]host = "https://panel.example.com"node_id = 1key = "replace-with-the-panel-key"node_type = "V2ray"
[[node.route.rule]]outbound = "socks-up"domain_suffix = ["example.com"]
[[outbound]]tag = "socks-up"protocol = "socks"server = "192.0.2.20"port = 1080username = "katana"password = "replace-with-a-long-random-password"[[outbound]] is a top-level table, like [[node]]. You can put it anywhere in the file as long as it starts on its own header line. Run katana --test -c config.toml after every edit. It builds the whole pool and every node’s route table, so a bad key, a missing field or an unknown tag fails there, before a running katana ever sees it.
Settings
Section titled “Settings”Every [[outbound]] table is strict: an unknown key, such as user instead of username, fails the parse with unknown field and the list of accepted keys. A key that the chosen protocol does not use, for example uuid on a socks outbound, is accepted and ignored.
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
tag | string | yes | — | 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. |
protocol | string (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. |
server | string | depends | — | 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. |
port | u16 | depends | — | 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. |
username | string | no | — | 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. |
password | string | depends | — | 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: …. |
uuid | string | depends | — | 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. |
security | string (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>". |
method | string (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_padding | bool | no | false | vmess 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_family | string (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_key | string | depends | — | 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_key | string | depends | — | 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_key | string | no | — | 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_address | array of strings | depends | — | 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>": …. |
mtu | integer | no | 1420 | wireguard 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. |
keepalive | u16 | no | — | 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. |
reserved | array of integers | no | — | 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. |
Which keys each protocol reads:
protocol |
Required | Optional |
|---|---|---|
direct, freedom |
none | address_family |
socks, socks5 |
server, port |
username and password, address_family |
http |
server, port |
username and password, address_family |
vmess |
server, port, uuid |
security, global_padding, address_family |
vless |
server, port, uuid |
address_family |
shadowsocks, ss |
server, port, method, password |
address_family |
wireguard, wg |
server, port, private_key, public_key, local_address |
pre_shared_key, mtu, keepalive, reserved, address_family |
What each protocol carries
Section titled “What each protocol carries”Nodes relay UDP as well as TCP when their protocol carries it. katana routes each UDP packet on its own, so one client’s UDP traffic can reach several outbounds. Not every outbound can carry UDP:
| Outbound | TCP | UDP |
|---|---|---|
direct |
Yes | Yes. One socket per address family, each packet to its own destination. |
block |
Refused | Dropped |
socks |
Yes | Yes, through SOCKS5 UDP ASSOCIATE. Each packet carries its own destination. |
http |
Yes, through CONNECT |
No |
vmess, vless |
Yes | Yes, with one fixed target per client association (see below). |
shadowsocks (both generations) |
Yes | No |
wireguard |
Yes | Yes. Each packet carries its own destination. |
When a UDP packet is routed to an outbound that carries no UDP, katana drops it. The client sees no error, only silence, so do not route DNS or QUIC to http or shadowsocks outbounds.
direct
Section titled “direct”The built-in direct tag uses address_family = "auto". To send direct traffic with a different address-family policy, declare your own direct outbound under a new tag and route to it:
[[outbound]]tag = "direct-v4"protocol = "direct"address_family = "ipv4_only"A direct outbound needs no server or port and ignores them if present. For TCP, katana resolves the destination and tries every allowed address in turn until one connects. For UDP, it binds a socket for each allowed family, resolves each domain target once per association and sends to the first allowed address, and drops packets to a name with no usable address.
socks and http
Section titled “socks and http”socks speaks SOCKS5 and http speaks HTTP CONNECT. Authentication is used only when both username and password are set: SOCKS5 username/password authentication, or an HTTP Basic Proxy-Authorization header. With only one of the two set, katana connects without credentials and the upstream decides whether to accept that.
vmess and vless
Section titled “vmess and vless”Both need the account uuid from the upstream server. VMess uses the AEAD header format with security choosing the body cipher; auto is AES-128-GCM, and there is no unencrypted mode. VLESS sends an empty flow, so an Xray upstream whose account requires xtls-rprx-vision rejects its TCP connections.
shadowsocks
Section titled “shadowsocks”The method name decides which generation katana speaks, and password changes meaning with it:
[[outbound]]tag = "ss-up"protocol = "shadowsocks"server = "198.51.100.30"port = 8388method = "2022-blake3-aes-256-gcm"password = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="password is a base64 key of at least the method’s key length: 16 bytes for 2022-blake3-aes-128-gcm, 32 bytes for the other two. Generate one with openssl rand -base64 32 (or -base64 16). A shorter key fails with shadowsocks-2022: PSK too short (16 < 32), and invalid base64 fails with decode PSK: ….
For a multi-user server, write the identity key and your user key separated by a colon, iPSK:uPSK. The last key is always yours; the ones before it form the identity chain. Each part is decoded and length-checked on its own. The multi-user extension (SIP023) defines identity keys for the two AES methods only, so use a chain only with those.
[[outbound]]tag = "ss-up"protocol = "shadowsocks"server = "198.51.100.30"port = 8388method = "chacha20-ietf-poly1305"password = "replace-with-a-long-random-password"password is an ordinary password; katana derives the key from it the same way other Shadowsocks implementations do.
The 2022 method names must be written exactly, in lower case: 2022-BLAKE3-AES-128-GCM fails with unsupported shadowsocks cipher. Classic names are case-insensitive. Stream ciphers such as rc4-md5 are not supported.
wireguard
Section titled “wireguard”A wireguard outbound runs a userspace WireGuard tunnel inside katana: no network interface, no host routes, no root. server and port are the peer’s UDP endpoint. It carries TCP and UDP. The rest of this section explains how it differs from the other outbounds; the WireGuard example below shows a complete node.
- Lazy start. Building the config opens nothing, so
--testchecks the key encodings but never contacts the peer. The first flow routed to the outbound resolves the endpoint, binds a UDP socket and starts the tunnel, and that first connection waits for the handshake. - Endpoint resolution. A domain in
serveris resolved once per tunnel start with the host’s system resolver, not[dns], and the first answer is used. - Destination resolution. When a flow through the tunnel names its destination by domain, katana resolves the name with its own
[dns]resolver on this host and connects to the IP through the tunnel. The lookup itself does not travel through the tunnel. - Address families. The tunnel can reach only the families it has a
local_addressfor. Withaddress_family = "ipv4_only"you need an IPv4local_address, and withipv6_onlyan IPv6 one; otherwise the build fails withwireguard outbound <tag> address_family ipv6_only needs an IPv6 local_address. A connection to a destination with no usable address fails withwireguard: no usable <policy> destination address for …, followed by the families the tunnel supports. - One stalled destination. Each flow through the tunnel has its own bounded buffers. When a destination stops reading, its flow’s buffers fill and then only that flow’s sender waits, so katana reads no more of that flow’s upload from the client until the destination reads again. The other flows, users and nodes that share the tunnel keep moving. A slow tunnel or peer likewise pushes back on the senders, which slow down to the pace the tunnel accepts. A UDP datagram that is larger than its association’s send buffer, or whose destination the tunnel cannot address, is dropped, and the rest of the association keeps flowing.
- Recovery. If the tunnel’s driver stops, for example because its UDP socket reports an error, the next flow builds a new tunnel and resolves the endpoint again. A tunnel that had run for at least 10 seconds is rebuilt at once. If it died sooner, or a start failed (for example, the endpoint name did not resolve), katana waits 2 seconds before the next attempt, doubling up to 30 seconds, and flows that arrive in the meantime fail with
wireguard: tunnel is down, waiting before the next attempt.
Address family
Section titled “Address family”address_family works for every protocol, but it filters different addresses depending on who resolves what:
| Outbound | address_family applies to |
|---|---|
direct |
The destination’s resolved addresses. |
socks, http, vmess, vless, shadowsocks |
The resolved addresses of server. The upstream resolves the destination itself. |
wireguard |
The destination’s resolved addresses, further limited to the families of local_address. The endpoint in server is not affected. |
| Value | Effect |
|---|---|
auto |
Keep the resolver’s order. TCP tries every address in turn; UDP uses the first usable one. |
ipv4_only, ipv6_only |
Use only that family. |
prefer_ipv4, prefer_ipv6 |
Try that family first and keep the other as a fallback. |
WireGuard egress example
Section titled “WireGuard egress example”This node sends two domains and one network through a WireGuard peer, blocks one domain, and relays everything else directly. The destinations routed to wg-egress see the peer’s address instead of the node’s.
# katana: one newV2board node whose route sends selected traffic out through# a WireGuard tunnel, blocks one domain, and relays everything else directly.# Keys are placeholders: generate your own with `wg genkey | tee private.key | wg pubkey`.
[log]level = "info"
[[node]]panel_type = "NewV2board"
[node.api]host = "https://panel.example.com"node_id = 1key = "replace-with-the-panel-key"node_type = "V2ray"
[node.route]default = "direct"
# First match wins. Traffic for these domains and this network leaves# through the tunnel, so the destination sees the WireGuard peer's address.[[node.route.rule]]outbound = "wg-egress"domain_suffix = ["example.com", "example.org"]cidr = ["198.51.100.0/24"]
[[node.route.rule]]outbound = "block"domain_suffix = ["ads.example.net"]
# The tunnel. `server` and `port` are the peer's UDP endpoint# (wg-quick's `Endpoint`); `public_key` is the peer's key.[[outbound]]tag = "wg-egress"protocol = "wireguard"server = "203.0.113.10"port = 51820private_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="public_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="local_address = ["10.8.0.2/32", "2001:db8:a::2/128"]address_family = "prefer_ipv4"mtu = 1420keepalive = 25The keys in the file are placeholders. Generate a real key pair with wg genkey | tee private.key | wg pubkey, and register the public half with the peer, or use the keys your WireGuard service gives you.
From a wg-quick file
Section titled “From a wg-quick file”WireGuard services usually hand out a wg-quick file. Both tabs describe the same tunnel:
[Interface]PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=Address = 10.8.0.2/32, 2001:db8:a::2/128DNS = 192.0.2.53MTU = 1280
[Peer]PublicKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=PresharedKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=AllowedIPs = 0.0.0.0/0, ::/0Endpoint = 203.0.113.10:51820PersistentKeepalive = 25[[outbound]]tag = "wg-egress"protocol = "wireguard"server = "203.0.113.10"port = 51820private_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="public_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="pre_shared_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="local_address = ["10.8.0.2/32", "2001:db8:a::2/128"]mtu = 1280keepalive = 25# AllowedIPs: use [[node.route.rule]] instead.# DNS: use [dns] instead.etemenanki-app builds the same tunnel from differently named keys, nested under [outbound.settings]. The last column matters when you test an upstream with etemenanki-app first, as described in the next section:
| wg-quick | katana [[outbound]] |
etemenanki-app [outbound.settings] |
|---|---|---|
[Interface] PrivateKey |
private_key |
private_key |
[Interface] Address |
local_address, prefix allowed: ["10.8.0.2/32"] |
address, bare IPs only: ["10.8.0.2"] |
[Interface] MTU |
mtu |
mtu |
[Peer] PublicKey |
public_key |
peer_public_key |
[Peer] PresharedKey |
pre_shared_key |
preshared_key |
[Peer] Endpoint |
server and port, separately |
endpoint = "203.0.113.10:51820" |
[Peer] PersistentKeepalive |
keepalive |
keepalive |
[Peer] AllowedIPs |
none: route rules choose what enters the tunnel | none |
[Interface] DNS |
none: [dns] resolves names on the host |
none |
| none | reserved |
reserved |
Copying Address into etemenanki-app with its prefix fails with invalid IP address syntax, and a peer_public_key key in a katana outbound fails with unknown field. Both programs refuse the wrong spelling rather than ignoring it.
Test an upstream before you use it
Section titled “Test an upstream before you use it”katana --test proves only that the outbound builds. It cannot tell you whether the upstream accepts your credentials or forwards your traffic, and a wrong WireGuard key looks exactly like an unreachable peer: connections time out. Before you route users to a new upstream, run it once through a throwaway etemenanki-app on the same host. It shares nothing with the running katana: separate process, separate config file, loopback port.
-
Translate the outbound into etemenanki-app’s format and give it a loopback SOCKS inbound. For the WireGuard outbound above:
wg-check.toml # Throwaway check of one WireGuard upstream through a loopback SOCKS proxy.[[inbound]]tag = "check"protocol = "socks"listen = "127.0.0.1"port = 10808[[outbound]]tag = "wg-check"protocol = "wireguard"address_family = "prefer_ipv4"[outbound.settings]private_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="peer_public_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="endpoint = "203.0.113.10:51820"address = ["10.8.0.2", "2001:db8:a::2"]mtu = 1420keepalive = 25etemenanki-app sends unmatched traffic to its first outbound, so with a single outbound everything goes through it. For a SOCKS, HTTP, VMess, VLESS or Shadowsocks upstream, use the matching etemenanki-app outbound from Outbounds instead, with no TLS, WebSocket or gRPC transport, because katana reaches the upstream over plain TCP.
-
Check the file with
etemenanki-app --test -c wg-check.toml, then start it in the foreground withetemenanki-app -c wg-check.toml. -
From a second terminal, ask an IP echo service which address it sees:
Terminal window curl -m 30 --socks5-hostname 127.0.0.1:10808 https://ifconfig.meThe answer should be the upstream’s exit address, not this host’s. If the first request through a new WireGuard tunnel times out, try once more before you conclude anything: the first connection waits for the handshake.
-
Stop etemenanki-app with Ctrl-C, copy the same values into katana’s
[[outbound]]using katana’s key names, and runkatana --test.
Changing outbounds on a running node
Section titled “Changing outbounds on a running node”katana watches its config file. When you save a change to any [[outbound]] table:
- katana builds the whole new pool first. If any outbound fails to build, it logs
reload: bad outbounds, keeping current config: …and applies nothing from that edit, including node and log changes. The running pool keeps working. - Before it touches a running node, katana builds every node the edit adds, panel client and route table, against the new pool, and the panel client of every node whose settings changed. If one of them fails, it logs
reload: node <node>: <error>; keeping current configand again applies nothing from that edit.<node>is the panel type, host, node ID and, on NewV2board and V2board, the node type, for examplenewv2board@https://panel.example.com#1/v2ray. So a new node whose route names a tag the new pool lacks rejects the whole edit, withbuild router: route references unknown outbound tag: …as the error. - If everything builds, every node recompiles its route table against the new pool and rebuilds its listener. This drops every open connection on every node, and every WireGuard tunnel starts over with a new handshake. A node that is still retrying its first start has no listener yet: it recompiles its routes against the new pool and uses them at its next attempt.
- If, after the edit, a node’s route still names a tag the new pool no longer has, that node logs
node <id>: route rebuild failed, keeping current: …and keeps routing with its previous table, which still points at the old outbounds.katana --testreports the same mistake asroute references unknown outbound tag.
The [dns] resolver is built together with the pool, so a change to [dns] alone takes effect only at the next outbound change or restart. See Hot reload for everything a reload does.
At startup, a pool that fails to build stops katana with failed to build outbounds: … and exit status 1.
Common errors
Section titled “Common errors”| Error | Cause and fix |
|---|---|
duplicate/reserved outbound tag <tag> |
The tag is direct, freedom, block or blackhole, or two [[outbound]] tables share it. Rename one. |
route references unknown outbound tag: <tag> |
A route rule or default names a tag that is not in the pool. Check the spelling and case. |
unknown outbound protocol "<name>" |
protocol is not one of the values in the settings table. The name is shown in lower case. Trojan and Hysteria 2 upstreams are not supported. |
outbound needs a non-empty server and non-zero port (got …) |
server or port is missing on a protocol that needs an upstream. katana checks this before it looks at protocol, so a misspelled protocol without server and port also fails here. |
outbound <tag> needs a uuid |
A vmess or vless outbound has no uuid. |
outbound <tag>: uuid is not a valid UUID |
The uuid is malformed. The message leaves the value out on purpose. |
unsupported vmess security "<name>" |
Use auto, aes-128-gcm or chacha20-poly1305. |
shadowsocks outbound <tag> needs a password |
Add password. |
unsupported shadowsocks cipher "<name>" |
method is missing, misspelled, or a 2022 name in the wrong case. |
shadowsocks-2022: PSK too short (16 < 32) |
The key decodes to fewer bytes than the method needs. Generate one with openssl rand -base64 32. |
outbound <tag> invalid address_family "<value>" |
Use one of the values in Address family. |
wireguard outbound <tag> needs a private_key (or public_key) |
Add the missing key. |
wireguard outbound <tag> private_key: invalid WireGuard key: … |
The key is not 32 bytes of padded base64 or hex. Copy it again, including the trailing =. |
wireguard outbound <tag> needs at least one local_address |
Copy the Address line from the wg-quick file. |
wireguard outbound <tag> reserved must be exactly 3 bytes |
Write three numbers, for example reserved = [1, 2, 3], or remove the key. |
unknown field …, expected one of … |
A misspelled key, or an etemenanki-app key name such as peer_public_key or endpoint. |