Skip to content

Xboard and V2board (UniProxy)

With panel_type = "newv2board", a katana node takes its description and its users from a panel that implements the UniProxy node API under /api/v1/server/UniProxy/. Xboard and V2board are the panels this API comes from, and the ones this page describes. katana polls the panel, builds the listener the panel describes, and sends back per-user traffic.

Read this page when you connect katana to one of these panels, when you move a node over from another node agent, or when a node does not come up and you need to know what katana asked the panel. The general layout of the config file is on Config file; this page covers what is specific to UniProxy.

Property UniProxy in katana
panel_type "newv2board", or its alias "v2board", matched without regard to case
Authentication [node.api].key, sent as the token query parameter on every request
Node types VMess, VLESS, Trojan, Shadowsocks, Hysteria 2
Node description GET …/UniProxy/config, with ETag caching
Users GET …/UniProxy/user, with ETag caching
Traffic POST …/UniProxy/push, one row per user with traffic
Audit Panel routes whose action is block, plus an optional local rule file. Hits are not reported.
Poll interval [node.controller].update_periodic, default 60 seconds. The panel’s own intervals are ignored.
Not implemented Online users and IPs (alive, alivelist), node status (status), the /api/v2/server/ routes

The panel owns the node’s shape: the protocol, the port, the transport and whether TLS is on. katana owns everything local to the machine: which address to bind, the certificate, routing and outbounds.

  1. Create the node. Pick the protocol (VMess, VLESS, Trojan, Shadowsocks or Hysteria), then set the port, the transport and TLS the way clients should connect. How panel settings map to what katana serves lists the choices katana can serve. Leave the panel’s certificate settings alone; katana does not read them.

  2. Give the node users. Attach the node to the user groups (or plans) whose users should reach it. Xboard answers with an empty user list for a node that belongs to no group, and katana binds nothing while the list is empty.

  3. Note the node ID. It is the number the panel shows for the node. Xboard looks the node up by this ID and by the node type katana sends, so a node_type that does not match the node’s protocol gets Server does not exist even when the ID is right.

  4. Note the server communication key. This is the panel-wide secret node agents authenticate with, in the panel’s node or server settings. katana calls it key.

  5. Get a certificate for the name clients connect to, if the node uses TLS or is a Hysteria node. katana reads it from local files, never from the panel.

This config serves one VMess node from an Xboard panel. Whatever the panel says about the port, the transport and TLS, katana follows; the certificate is used only if the panel turns TLS on.

/etc/katana/config.toml
[log]
level = "info"
[[node]]
panel_type = "NewV2board"
[node.api]
host = "https://panel.example.com"
node_id = 1
key = "replace-with-the-panel-key"
node_type = "vmess"
timeout = 10
[node.controller]
listen_ip = "0.0.0.0"
update_periodic = 60
[node.controller.cert]
mode = "file"
cert_file = "/etc/katana/cert/fullchain.pem"
key_file = "/etc/katana/cert/privkey.pem"

Check it before you start katana:

Terminal window
katana --test -c /etc/katana/config.toml
Configuration OK

--test builds the panel client and the router, but it does not contact the panel. It cannot tell you whether the node ID, the key or the node type match what the panel has, and it cannot tell you whether the node the panel describes is one katana can serve. Hysteria 2 nodes are the exception for the local part: --test checks their [node.hysteria] block and reads their certificate.

When katana starts and the panel answers, the node logs:

INFO katana::manager::node: node 1: listening on 0.0.0.0:443

The only line that changes between protocols is node_type, plus enable_vless for VLESS and a [node.hysteria] block for Hysteria 2. Each tab shows the [[node]] entry on its own.

[[node]]
panel_type = "NewV2board"
[node.api]
host = "https://panel.example.com"
node_id = 1
key = "replace-with-the-panel-key"
node_type = "vmess"
# Needed only if the panel turns TLS on for this node.
[node.controller.cert]
mode = "file"
cert_file = "/etc/katana/cert/fullchain.pem"
key_file = "/etc/katana/cert/privkey.pem"

The Hysteria 2 listener settings that no panel describes, such as UDP relay, the credential format and the masquerade page, are covered on Hysteria 2 nodes.

These are the [[node]] keys that matter for a UniProxy panel. panel_type sits directly in [[node]]; the others live in [node.api] unless the key names another table. The complete list of node keys is on Config file.

Key Type Default With a UniProxy panel
panel_type string "" "newv2board" or "v2board", in any case. Anything else fails with unknown panel_type "…".
host string "" Panel base URL, including the scheme. katana removes any trailing / and appends /api/v1/server/UniProxy/…, so a base path such as https://example.com/panel is kept. Not checked by --test.
node_id u32 0 The node’s numeric ID, sent as the node_id query parameter.
key string "" The server communication key, sent as the token query parameter.
node_type string (enum) "" Which protocol to serve and which node type to ask the panel for. Accepted values, in any case: v2ray, vmess, vless, trojan, shadowsocks, hysteria2, hysteria, hy2. Anything else fails with unknown node_type "…". See node_type.
enable_vless bool false For a V2ray-family node: serve VLESS instead of VMess, ask the panel for node_type=vless, and read the transport settings from network_settings. Ignored for other node types.
timeout u64 0 Total time for one panel request, in seconds. 0 means 5 seconds.
speed_limit float 0.0 Mbps. A value above 0 replaces every user’s panel limit with this one. See Speed limits.
rule_list_path string "" A local file of audit regexes, added to the rules from the panel. See Audit rules.
vless_flow string "" No effect with this panel. The flow comes from the panel’s flow field.
device_limit integer 0 No effect. katana does not enforce device limits.
disable_custom_config bool false No effect. SSPanel only.
[node.controller] update_periodic u64 60 Seconds between polls. One timer drives the node fetch, the user fetch, the rule refresh and the traffic report. Values below 1 count as 1.
[node.controller] disable_get_rule bool false true stops katana from loading audit rules, both the panel’s and the local file’s. Rules already loaded before a reload turns this on stay in force until katana restarts or a reload replaces the node.
[node.controller] disable_upload_traffic bool false true stops traffic reports. The counts of users who leave the node are discarded. The counters of current users keep running, so turning reports back on with a reload also reports what they moved in the meantime.
[node.hysteria] port u16 0 Hysteria 2 only. 0 asks the panel for the node. A non-zero port describes the node locally, and katana never calls the config endpoint.

node_type does two jobs. It picks the protocol katana serves, and, lowercased, it becomes the node_type query parameter the panel uses to find the node. The one exception is a V2ray-family node with enable_vless = true: katana then sends vless, whatever you wrote.

node_type (any case) enable_vless katana serves Sent to the panel Xboard
v2ray false VMess v2ray Accepted, as an alias of vmess
vmess false VMess vmess Accepted
v2ray, vmess or vless true VLESS vless Accepted
vless false VMess vless Accepted, but the protocols do not match
trojan ignored Trojan trojan Accepted
shadowsocks ignored Shadowsocks shadowsocks Accepted
hysteria2 ignored Hysteria 2 hysteria2 Accepted, as an alias of hysteria
hysteria ignored Hysteria 2 hysteria Accepted
hy2 ignored Hysteria 2 hy2 Refused: Invalid node type specified

katana calls three endpoints, all under host. Every request, including the POST, carries the same three query parameters:

Parameter Value
node_id [node.api].node_id
node_type The lowercased node_type, or vless (see node_type)
token [node.api].key
Call When Response katana reads
GET /api/v1/server/UniProxy/config At startup and every poll. Skipped for a Hysteria 2 node with a local [node.hysteria].port. A JSON object describing the node, without an envelope. See Fields katana reads.
GET /api/v1/server/UniProxy/user At startup and every poll {"users": [{"id": …, "uuid": "…", "speed_limit": …}, …]}
POST /api/v1/server/UniProxy/push Every poll, if any user has traffic Only the HTTP status. The body is ignored.
sequenceDiagram
    participant K as katana node
    participant P as Panel
    K->>P: GET config
    P-->>K: node description and routes
    K->>P: GET user
    P-->>K: user list
    Note over K: bind the listener, load audit rules
    loop every update_periodic seconds
        K->>P: GET config (If-None-Match)
        P-->>K: 200 with a new description, or 304
        K->>P: GET user (If-None-Match)
        P-->>K: 200 with a new list, or 304
        Note over K: reconcile, refresh audit rules
        K->>P: POST push
    end

katana keeps one ETag per endpoint, one for config and one for user. When a full answer carries an ETag header, katana stores it and sends it back as If-None-Match on the next request to the same endpoint. Xboard answers 304 Not Modified when nothing has changed, and katana then keeps the node description or the user list it last applied, without parsing anything. A response without an ETag header leaves the stored tag as it was.

katana stores the tag as soon as the panel answers 200, before it parses the body. If a running node gets an answer it rejects, for example a user list it cannot parse or a node without a port, the next poll still carries the new tag, Xboard answers 304, and katana keeps the last state it applied without logging the error again. katana reads the rejected answer again in three cases:

  • its content changes in the panel;
  • a config edit to [node.api] gives the node a new panel client, which holds no tags and reads both endpoints in full;
  • katana restarts, or a reload replaces the node.

While a node is coming up, every attempt forgets the stored tags and asks the panel afresh, so an answer rejected at startup is read again at each retry.

  • Startup. katana fetches the node description first, then the user list, then binds the listener. The first poll comes one full update_periodic after the node is up.

  • Startup fails. Any failure on the way up is retried: the panel is unreachable or refuses the request, an answer does not parse, the panel describes a feature katana refuses, or the port is still in use. The node logs the reason at ERROR level, followed by the wait before the next attempt:

    ERROR katana::manager::node: node 1: node_info failed: GET /api/v1/server/UniProxy/config; retrying in 1s

    The wait starts at 1 second and doubles after each failure, up to 60 seconds or update_periodic, whichever is shorter. Each attempt starts again from the node description, without ETags. Saving an edit to the node’s settings in the config file starts the next attempt at once. The node binds nothing until an attempt succeeds.

  • The user list is required. A node does not come up until it has read a user list: a user fetch that fails or does not parse is a failed attempt like the others, logged as user_list failed and retried. An empty list is accepted: the node comes up and binds nothing until users arrive.

  • A later fetch fails. katana logs a warning and keeps the last applied description or user list. There is no retry within a poll; the next tick is the retry.

  • The user list is empty. katana closes the listener and binds nothing until users come back.

Failed HTTP requests are logged without their URL, because the URL carries the key. The log line names the endpoint only, for example GET /api/v1/server/UniProxy/config.

katana reads only these fields from the config response. It does not reject unknown fields, so a panel may send anything else.

Field Node types What katana does with it
server_port All The port to listen on. 0 or a missing value fails with newV2board: server port must be > 0.
network VMess, VLESS The transport: tcp or raw, ws or websocket, grpc or gun. Empty means TCP. See Transports.
networkSettings VMess path (WebSocket), headers.Host (WebSocket), serviceName (gRPC), header (TCP)
network_settings VLESS The same fields, read only when enable_vless = true
tls VMess, VLESS 0 plain, 1 TLS, 2 REALITY (refused)
flow VLESS Must be empty. Any flow is refused.
host, server_name Trojan, Hysteria 2 Recorded, and a change rebuilds the listener. They do not change what katana serves; the local certificate decides the name.
cipher, server_key Shadowsocks The cipher, and the server PSK for Shadowsocks 2022
obfs Shadowsocks Must be empty, plain or none. Xboard does not send this field for Shadowsocks.
obfs, obfs-password Hysteria 2 Salamander obfuscation. obfs must be salamander or empty, and obfs-password must be empty when obfs is.
routes[].match, routes[].action All Audit rules. See Audit rules.

From the user response, katana reads each user’s id (the uid traffic is reported under), uuid (the credential) and speed_limit (Mbps).

katana ignores these fields, even when the panel sends them:

Field Consequence
base_config (push_interval, pull_interval) katana polls and reports every update_periodic seconds.
tls_settings, cert_config The certificate always comes from [node.controller.cert]. The panel’s server name, REALITY keys and certificate mode have no effect.
multiplex katana does not implement the smux, yamux or h2mux multiplexing this setting turns on in clients. Leave it off. Xray’s Mux.Cool works without any setting.
decryption (VLESS encryption) VLESS clients must use encryption = "none".
up_mbps, down_mbps (Hysteria) katana does not implement Brutal congestion control. Use speed limits instead.
device_limit (users) Device limits are not enforced.
plugin, plugin_opts (Shadowsocks) katana serves plain Shadowsocks without a plugin.
network (Trojan) Trojan is always served over TCP with TLS.
version (Hysteria) katana always serves Hysteria 2.
listen_ip, protocol, custom_outbounds, custom_routes katana binds [node.controller].listen_ip and routes with [node.route] and [[outbound]].

How panel settings map to what katana serves

Section titled “How panel settings map to what katana serves”

This table follows Xboard’s node form. When a row says refused, the listener build fails, the log line names the feature, and the node does not listen until the panel setting changes. When a row says ignored, katana serves anyway and clients configured for the ignored setting fail to connect.

Panel setting katana serves
Transport TCP TCP
Transport TCP with an HTTP header disguise Plain TCP. The header setting is ignored.
Transport WebSocket WebSocket at path (default /). If headers.Host is set, only requests for that host are accepted.
Transport gRPC gRPC with the panel’s serviceName. With TLS, ALPN h2 is offered.
Transport HTTPUpgrade Refused: node requests kernel-unsupported feature: httpupgrade transport
Transport XHTTP or SplitHTTP Refused: node requests kernel-unsupported feature: splithttp transport
Any other transport Refused: node requests kernel-unsupported feature: transport "…"
TLS on TLS with the local certificate. Needs [node.controller.cert] mode = "file".
REALITY Refused: node requests kernel-unsupported feature: REALITY
VLESS flow such as xtls-rprx-vision Refused: node requests kernel-unsupported feature: VLESS XTLS flow
VLESS encryption Ignored
Multiplex (smux, yamux, h2mux) Ignored. Clients that use it fail; leave it off. Xray’s Mux.Cool works regardless.

Users authenticate with their UUID. VMess runs in AEAD mode only, with alterId 0.

Panel setting katana serves
TLS TCP with TLS, using the local certificate
Transport WebSocket or gRPC Still TCP with TLS. The transport is ignored.
REALITY Ordinary TLS with the local certificate. REALITY is ignored.
Multiplex (smux, yamux, h2mux) Ignored. Clients that use it fail; leave it off.

The Trojan password is the user’s UUID.

Panel cipher katana serves
aes-128-gcm, aes-256-gcm, chacha20-ietf-poly1305, xchacha20-ietf-poly1305 Shadowsocks AEAD. Each user’s password is their UUID.
2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm Shadowsocks 2022 with one port for all users. The panel’s server_key is the server PSK, and each user’s PSK is the first 16 or 32 characters of their UUID as text, the same derivation Xboard uses for client subscriptions.
2022-blake3-chacha20-poly1305 Fails: Xboard sends no server_key for this cipher, and katana reports shadowsocks-2022: PSK too short (0 < 32). Use an AES variant.
Any other cipher Refused: node requests kernel-unsupported feature: shadowsocks cipher "…"
A plugin Ignored

katana serves Shadowsocks over TCP only; it does not open a UDP port for Shadowsocks.

Panel setting katana serves
Version 2 Hysteria 2 on UDP server_port, with the local certificate
Version 1 Not supported. katana serves Hysteria 2 on the port, which Hysteria 1 clients cannot use, and a Version 1 obfuscation password makes the node fail with unknown obfs.
Obfuscation on, type Salamander Salamander with the panel’s password. A password shorter than 4 bytes fails with obfs_password must be at least 4 bytes for salamander.
Obfuscation off, with a password still filled in Fails with obfs_password is set but obfs is not; did you mean obfs = "salamander"?. Xboard sends the stored password even when obfuscation is off, so clear the password field when you turn obfuscation off.
Bandwidth up and down Ignored
TLS server name, allow insecure Ignored; the certificate decides the name

Users authenticate with their UUID. With [node.hysteria] credential = "user_pass", the username is <uuid>@v2board.user and the password is the UUID. Hysteria 2 nodes covers the rest of the Hysteria settings.

Every entry in the user response becomes one account:

Response field katana uses it as
id The uid that traffic is reported under
uuid The credential: the VMess or VLESS ID, the Trojan password, the Shadowsocks password, or the Hysteria 2 auth string
speed_limit The user’s speed limit in Mbps. 0 means no limit.

A user who drops out of the list, for example because the panel banned them or their plan expired or ran out of traffic, loses their open connections at the next poll that sees the new list. On a VMess or VLESS node, katana leaves out any user whose uuid is not a valid UUID and logs skipping user <id>: uuid is not a valid UUID; the other users are served.

katana converts Mbps to bytes per second by multiplying by 125 000, and enforces the result per user across all of that user’s connections on the node. The panel supplies one limit per user and no node-wide limit.

[node.api] speed_limit Effective limit for each user
0 (default) or less The user’s speed_limit from the panel. 0 there means unlimited.
Above 0 This value, for every user, whatever the panel says

A change in a user’s panel limit applies at the next poll without dropping their connections: the connections they already have keep the old limit, and the ones they open after that poll get the new one. Speed limits explains how the limit is enforced.

Xboard’s route rules double as katana’s audit rules. When the node has routes assigned in the panel, the config response carries them as routes, and katana turns every route whose action is block into one rule:

  • the route’s match entries are joined with | into a single regular expression;
  • the expression is searched for, unanchored, in the destination host of each connection: the domain name the client asked for, or the IP address, without the port;
  • the first rule that matches refuses the connection; a UDP packet to a matching destination is dropped.

Routes with any other action (direct, dns, proxy) are ignored. Use [node.route] for routing.

For example, a route with action block and match ["(^|\\.)example\\.org$", "^torrent\\."] refuses example.org, www.example.org and any host that starts with torrent., such as torrent.example.com.

katana needs no extra request for the rules: it rebuilds them from the last config response at every poll, together with the rules in rule_list_path. If the panel stops sending routes, the panel rules disappear at the next full config response. UniProxy has no endpoint for audit hits, so katana refuses the connection and reports nothing to the panel. Audit rules covers the local rule file and the matching in detail.

At every poll, katana posts the traffic each user has moved since the last successful report:

{"1001": [52428800, 1073741824], "1002": [0, 4096]}

Each key is a user’s id as a string, and each value is [upload, download] in bytes. Upload is what the user sent, download is what they received. katana counts the bytes relayed to and from the destination, so protocol and TLS overhead is not included.

  • Users with no traffic are left out. When nobody has traffic, katana sends no request at all.
  • katana checks only the HTTP status. On success, it subtracts exactly the reported bytes, so traffic counted while the request was in flight goes into the next report.
  • On failure, katana logs report traffic and keeps the counts; the next poll reports them together with the new traffic. If the panel did apply a report but katana never saw the answer, for example because the request timed out, those bytes are reported again.
  • When katana stops, and when a reload removes or replaces a node, the node reports its traffic one last time.
  • With disable_upload_traffic = true, katana posts nothing. See Settings for what happens to the counts.

Xboard takes a node’s online-user figure and its last-push time from the pushes, and keeps the figure for an hour. A node that carries no traffic sends no push, so the panel shows it as online without recent pushes. Traffic reporting describes the counters in detail.

Endpoint What the panel is missing
POST /api/v1/server/UniProxy/alive, GET …/alivelist Online IPs per user. The panel cannot enforce device limits with katana.
POST /api/v1/server/UniProxy/status CPU, memory and disk load for the node
/api/v2/server/… katana uses only the V1 routes
ShadowsocksTidalab, TrojanTidalab katana uses only UniProxy

Xboard marks a node as checked in each time katana fetches the user list, so the node shows as online as long as katana polls.

katana watches its config file and applies most edits without a restart; Hot reload has the full list. For this panel:

  • Changing panel_type (other than case), host, node_id or key replaces the node. So does any edit that changes the node type katana asks the panel for: node_type (other than case), or enable_vless on a v2ray or vmess node. Xboard finds a node by its ID and its type, so each of these edits names another panel node. The old node stops, dropping its connections, and sends its final traffic report; katana then starts a fresh node, with its own traffic counters, from the panel.
  • Changing any other [node.api] key, such as timeout, speed_limit or rule_list_path, applies at once. katana builds a new panel client with the new values, reads the node and its users in full, and applies the answer like a poll’s: connections are kept unless the node’s protocol or transport changes.
  • On a node whose requested type stays the same, for example with node_type = "vless", changing enable_vless rebuilds the listener, which drops that node’s connections.
  • An edit that leaves a node unable to build, such as an unknown node_type, is refused as a whole. katana logs reload: node …: unknown node_type "…"; keeping current config, and every node keeps running as it was.
  • Changes in the panel need nothing on the katana side. katana picks them up at the next poll: a new port, transport or TLS setting rebuilds the listener and drops that node’s connections, and a changed user list is applied in place.

The first two messages are what --test prints. At startup the same errors appear as node 1: unknown panel_type "xboard", and katana skips that node; if no node is left, it exits with no nodes could be started.

The node_info failed, user_list failed and initial start failed messages come from a node that is coming up. katana logs them at ERROR level with ; retrying in <N>s at the end, and the node tries again on its own, so fixing the cause is enough: the node comes up at the next attempt.

Message Cause Fix
configuration error: unknown panel_type "xboard" panel_type names the panel product Use panel_type = "newv2board" for Xboard and V2board.
configuration error: unknown node_type "…" A node_type katana does not know Use one of the values in node_type.
node 1: node_info failed: GET /api/v1/server/UniProxy/config The request failed: the panel is unreachable, or it refused the key, the node ID or the node type Ask the panel yourself, as shown below.
node 1: node_info failed: parse UniProxy config response The answer is not the JSON katana expects, for example an HTML page because host points at the wrong site Check host. It is the panel’s base URL, without /api/….
node 1: node_info failed: newV2board: server port must be > 0 The node has no port in the panel Set the port in the panel.
node 1: node_info failed: newV2board: shadowsocks obfs "…" is not supported The panel asks for Shadowsocks obfuscation Turn obfuscation off in the panel.
node 1: initial start failed: node requests kernel-unsupported feature: … The panel describes a feature katana refuses Change the node in the panel; see How panel settings map to what katana serves.
node 1: initial start failed: TLS node requires cert.mode = "file" The panel turned TLS on, and the node has no certificate Add [node.controller.cert] with mode = "file", cert_file and key_file.
node 1: initial start failed: obfs_password is set but obfs is not; … A Hysteria node has obfuscation off in the panel but a stored obfuscation password Clear the password in the panel.
node 1: user_list failed: parse UniProxy user response; retrying in … at startup, node 1: user_list: parse UniProxy user response later A user entry does not have the expected types, typically speed_limit set to null See Users.
node 1: rebuild failed: … A panel change asked for something katana cannot serve The node stays dark and retries at every poll. Fix the setting in the panel.
node 1: report traffic: POST UniProxy push The traffic report failed Nothing to do if it is temporary: the counts are kept and sent with the next report.

When the log names only the endpoint, ask the panel directly with the values from your config. The key goes into your shell history, so clear it afterwards:

Terminal window
curl -sS "https://panel.example.com/api/v1/server/UniProxy/config?node_id=1&node_type=vmess&token=replace-with-the-panel-key"

Xboard answers a wrong key with Invalid token, an unknown node type with Invalid node type specified, and a node ID that has no node of that type with Server does not exist. A working node answers with a JSON object that contains server_port.