Skip to content

Speed limits and connection limits

katana limits speed per user. Every user on a node gets one token bucket, and every byte that user moves, in either direction and over TCP or UDP, is paid for from it. The rate comes from your panel, and a single key in the katana config can override it for the whole node.

This page explains where a user’s rate comes from, how the bucket enforces it, when a change takes effect, and which fixed connection limits protect a node. Read it when you set up plans with speed limits, when a user reports a speed that does not match their plan, or when you size a busy node.

Most of the time the panel sets the limits and you leave katana alone: give the user or their plan a speed limit in the panel, and katana applies it on the next poll. To cap every user of a node at the same speed instead, set speed_limit in [node.api]:

/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 = "V2ray"
speed_limit = 50 # Mbps; every user on this node runs at 50 Mbps
[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"

With this file, each user on node 1 gets a bucket of 50 Mbps, which is 6,250,000 bytes per second, whatever the panel says about them. Check the file with katana --test -c /etc/katana/config.toml before you start katana with it or save it over the file a running katana watches.

Two keys in [node.api] relate to limits. The rest of [node.api] is described in Config file.

Key Type Default Description
speed_limit float 0 Speed limit in Mbps for every user of this node. A value greater than 0 replaces every user’s panel limit and the panel’s node limit, even when the panel limit is lower. 0 or a negative value keeps the panel’s limits. Decimals are allowed, for example 12.5.
device_limit integer 0 Accepted and ignored. katana has no device limit.

speed_limit must be a number. A quoted value is a parse error: katana refuses to start with it, and a hot reload that contains it is rejected while katana keeps its current config. This is what katana --test prints for it:

configuration error: config parse error: TOML parse error at line 12, column 15
|
12 | speed_limit = "50"
| ^^^^
invalid type: string "50", expected f64

A misspelt key is rejected the same way, because [node.api] refuses unknown fields:

configuration error: config parse error: TOML parse error at line 12, column 1
|
12 | speedlimit = 50
| ^^^^^^^^^^
unknown field `speedlimit`, expected one of `host`, `node_id`, `key`, `node_type`, `enable_vless`, `vless_flow`, `timeout`, `speed_limit`, `device_limit`, `rule_list_path`, `disable_custom_config`

You do not need to restart katana after you change speed_limit on a running node. Save the file, and the hot reload applies the new value at once without dropping any connection. When a change takes effect explains which flows get the new rate. Limits you change in the panel do not need a restart either; katana picks them up on the next poll.

katana works out one rate per user from up to three sources:

Source Where it is set Unit on the wire Panels
User limit The user (or their plan) in the panel Mbps Xboard / V2board, SSPanel
Node limit The node in the panel Mbps SSPanel only
Local override speed_limit in [node.api] Mbps Both

katana reads the user limit from the speed_limit field of each user in the UniProxy user list. It is a whole number of Mbps. katana reads no node-level speed limit from the UniProxy node config, so on these panels the node limit is always 0.

The rules for combining them:

  1. Convert to bytes per second. katana treats a megabit as 1,000,000 bits, so 1 Mbps is 125,000 bytes per second. The result is rounded down to a whole byte. A value of 0 or less becomes 0, which means unlimited.
  2. Apply the local override. If speed_limit is greater than 0, it replaces both the user limit and the node limit.
  3. Take the smallest non-zero limit. The user’s rate is the smaller of the node limit and the user limit, ignoring any that is 0. If both are 0, the user is unlimited.
User limit Node limit (SSPanel) speed_limit Effective rate
20 Mbps 0 0 20 Mbps = 2,500,000 B/s
20 Mbps 50 Mbps 0 20 Mbps
0 50 Mbps 0 50 Mbps = 6,250,000 B/s
0 0 0 unlimited
-1 0 0 unlimited
20 Mbps 50 Mbps 100 100 Mbps = 12,500,000 B/s
0 0 12.5 12.5 Mbps = 1,562,500 B/s

The sixth row is the one that surprises people: the override is not a ceiling. It replaces the panel’s numbers, so a user whose plan says 20 Mbps runs at 100 Mbps on that node.

Each user on a node has exactly one token bucket, holding bytes:

  • It refills at the user’s rate.
  • It holds at most one second of that rate. A 50 Mbps user can bank 6,250,000 bytes and no more, however long they were idle, so after a quiet period they get at most one extra second’s worth of data before the rate applies.
  • It starts full when katana first sees the user, or when the user’s rate changes.
  • Every flow of that user shares it: all their connections, both directions, TCP streams and UDP packets alike. The limit is on the total, so a 50 Mbps user who uploads and downloads at the same time gets 50 Mbps between the two, not 50 Mbps each way.

The bucket belongs to the node, so a user on two [[node]] entries of the same katana process has two separate buckets. Their panel limit applies on each node independently.

A transfer may start whenever the bucket is not in debt. After it moves, katana charges its full size, even when that is more than the bucket holds. The balance then goes negative, and every later transfer of that user, on any connection, waits until the refill has paid the debt back.

flowchart TB
  A["a transfer is ready to move"] --> B{"is the user's bucket in debt?"}
  B -- "yes" --> W["wait until the refill repays the debt"]
  W --> B
  B -- "no" --> M["move the bytes"]
  M --> C["charge their full size; the balance may go negative"]
  C --> R["refill at the user's rate, up to one second of rate"]
  R --> A

For example, take a rate of 10,000 bytes per second and a full bucket. A single 30,000-byte write passes at once: 10,000 bytes come out of the bucket and 20,000 become debt. The next transfer, upload or download, waits two seconds. Over time the user moves 10,000 bytes per second, exactly the limit.

This is why the limit does not depend on chunk size. A scheme that waited for “enough tokens” before each chunk would have to let a chunk larger than the whole bucket through after one refill period, so large reads would run faster than the limit. With debt, the long-run average respects the rate however the bytes were chunked. The price is that a single transfer can overshoot for a moment; the transfers after it pay for that.

katana meters on the outbound side of the relay. The bytes it counts and charges are the plain payload: after the inbound protocol has removed its framing and encryption, and before the outbound protocol adds its own. So the numbers do not change with the inbound protocol or transport, and TLS, WebSocket or VMess overhead is not billed to the user.

  • Bytes the user sends are counted as upload; bytes they receive, as download. Both go through the same bucket.
  • A TCP flow that routing sends to a block outbound, or that an audit rule forbids, is refused before it opens, so it costs nothing.
  • A UDP association is routed packet by packet. A packet that is blocked or forbidden is dropped and not billed; every packet that leaves, and every reply, is billed and paced like stream bytes.

The same counters feed traffic reporting. How they are reported to the panel is described in Traffic reporting.

katana polls the panel every update_periodic seconds (60 by default). When a user’s effective rate differs from the last poll, whether because the user limit or the SSPanel node limit changed, katana gives that user a new counter with a new, full bucket. It does not drop any of their connections.

  • New flows use the new counter and the new rate.
  • Existing flows keep the counter they opened with and finish at the old rate. A UDP association counts as one flow, so it stays on the old rate until it ends.
  • Bytes on the old counter are still reported until its last flow ends, so a rate change never loses traffic.

For a short time after a change, the user’s old flows and new flows draw from different buckets, so the user can briefly exceed either rate. A user whose rate did not change keeps their counter and bucket across polls.

A change to speed_limit in the config file takes the same path, without waiting for the next poll. When a hot reload changes speed_limit, katana builds a new panel client for the node and reads the node and its users from the panel at once, in full. Every user whose effective rate changes gets a new counter and bucket as described above: the user tables are refreshed in place, flows already open keep the old rate, and new flows get the new one. This includes a Hysteria 2 node that you describe locally, where speed_limit is also the node limit. If the panel cannot be reached at that moment, the new rate reaches every user on the first poll that gets an answer. An edit that also changes a setting the listener is built from, such as listen_ip, the certificate or the route, rebuilds the listener and drops every connection on the node; Hot reload lists what each key does.

A user who leaves the panel’s user list is handled differently: katana refuses their new flows at once and ends the flows they already have.

On top of per-user limits, each node has a fixed set of connection guardrails. They are constants in the code, not settings. They are sized far above normal traffic and exist to keep a scan, a flood of idle clients or a stuck peer from exhausting memory or file descriptors.

The limits below apply to the TCP-based node types: VMess, VLESS, Trojan and Shadowsocks. “Per listener” means per node, because each node has one listener.

Guardrail Value Scope When it is reached
Live connections 65,536 per listener A new socket is accepted and closed at once. The place is held for the whole connection, including every stream a gRPC connection carries.
Concurrent transport handshakes 2,048 per listener katana stops accepting until a place frees; further clients wait in the kernel’s listen backlog. Covers TLS, the WebSocket upgrade and the HTTP/2 preface.
Transport handshake place 10 s per socket The socket gives up its handshake place when the transport yields its first stream or after 10 seconds, whichever comes first.
Pre-authentication streams 512 per listener A new stream that arrives while 512 streams are still in their protocol handshake is dropped at once.
Protocol handshake deadline 10 s per stream A client that has not completed its protocol handshake after 10 seconds is disconnected.
Relay idle timeout 300 s per connection A connection whose relay carried nothing in either direction for 300 seconds is closed.
Progress watchdog 360 s per connection A connection that moved no bytes at all for 360 seconds is dropped. It catches connections the idle timeout cannot close, such as a client that stopped reading.
Outbound links per UDP association 64 per UDP association An association opens one link for each outbound it sends through, not one per destination. Opening a 65th closes the one sent to least recently.

A Hysteria 2 node serves QUIC and has its own limits instead:

Guardrail Value Scope When it is reached
QUIC connections 4,096 per listener A new connection is refused as it arrives, so the client learns at once instead of retrying into silence.
Live circuits 65,536 per listener Counts proxy streams and UDP sessions together, for their whole life. A new proxy stream is rejected and a new UDP session’s packets are dropped; the client’s other streams continue.
Open streams 1,024 per QUIC connection QUIC withholds stream credit, so the client waits before it opens another stream.
UDP sessions 256 per QUIC connection Packets that would start a 257th session are dropped.
QUIC idle timeout 30 s per QUIC connection A connection that carries no packets for 30 seconds is closed.

None of these limits is per user. There is no setting to lower them for one user or raise them for a node.

Refusals by the guardrails are logged at debug level, one line each, for example:

dropping connection; live connection limit reached
node V2ray_0.0.0.0_443: dropping a stream; pre-auth limit reached
inbound handshake failed: timed out after 10s
connection ended: nothing moved for 360s
hysteria2: refusing a connection; the listener is full
hysteria2: refusing a stream; the listener is at its circuit limit

At the default info level you see a summary instead. On TCP-based nodes, katana counts failed inbound handshakes per node: bad transport framing, failed authentication, malformed requests, handshake timeouts, and connections refused by the live-connection and pre-authentication limits. When the count passes 10 in one second, katana logs a warning:

node V2ray_0.0.0.0_443: 37 inbound handshake failures in the last 1s (possible handshake scan/DoS or misconfigured clients)

The warning is an alert only; katana does not block anything because of it. A steady stream of these warnings usually means a scanner, or clients with a wrong UUID, password or transport setting.

Symptom Likely cause What to do
Every user on a node has the same speed, whatever their plan speed_limit is set in [node.api], which replaces every panel limit Set speed_limit = 0 and save the file. The hot reload applies it; no restart is needed.
A changed speed_limit has no effect The reload was rejected and katana kept its current config. A value that does not parse, such as a quoted one, logs config reload failed, keeping current: …; a node in the same file that does not build, such as one with an unknown node_type, logs reload: node …: …; keeping current config Run katana --test -c /etc/katana/config.toml, fix the error it prints, and save the file again.
A user is slower than their plan on an SSPanel node The node’s node_speedlimit is lower than the user’s, and the smaller one wins Raise or clear the node limit in the panel.
A user’s speed is half the plan in each direction during a large upload and download The limit covers both directions together This is intended; set the plan’s limit for combined traffic.
A new panel limit applies to some of a user’s connections but not others Flows opened before the change keep the old rate Wait for old connections to close, or have the client reconnect.
A user can open unlimited connections or devices katana has no per-user connection or device limit Enforce device limits elsewhere.