VlessServer
VlessServer is the server-side plain VLESS v0 tunnel for WaterWall. It parses the VLESS request header from the
previous stream-facing node, authenticates the 16-byte raw UUID, extracts the requested TCP or UDP destination, and
forwards accepted traffic to its configured next node.
This node implements only base VLESS protocol handling. It does not create TLS, REALITY, XTLS Vision, mux, reverse, XUDP, or transport-specific wrapping.
Cached Account Admission
With AuthenticationClient, admission uses its last valid RAM table and clock basis during ordinary network outages and re-authentication. Cached enabled status, expiry, traffic quota and local connection/IP reservations still apply. No initial cache, stopping, terminal accounting-continuity failure or explicit client credential rejection blocks new admission. Only validated authorization clears that rejection; existing authorized traffic keeps cached enforcement and accounting. Offline administrative changes apply after refresh. Local allowances can overshoot aggregate limits across multiple disconnected clients; they are not global reservations.
Splice and retention
VlessServer supports ordinary and splice payloads in both directions. Splice runs only on supported Linux builds with
misc.splice enabled and every node in the expanded chain capable, including UserController, connector/DomainResolver
helpers and merged fallback branches. TLS and ordinary-only fallback nodes still block the whole chain.
Request parsing reads only the required metadata into a cache of at most 278 bytes. The first Payload callback must
contain the version and all 16 UUID bytes in its logical length, including bytes in a private pipe; fewer than 17
bytes, including an empty callback, takes fallback/rejection. Successful authentication is cached. Unsupported addons
and other malformed authenticated requests close the protected flow. Accepted metadata is removed before body budgeting.
The first nonempty TCP reply or first framed UDP reply, including an empty datagram, shares one ordinary buffer with
the 00 00 response header. Ordinary input with
enough headroom is reused in place; splice-backed input or insufficient headroom uses a best-fit ordinary buffer.
Later opaque TCP replies retain their representation where possible. UDP uses one fixed request destination and exact two-byte
length framing, with bodies of 0..65,535 bytes. Pipe allocation or transfer pressure uses complete ordinary fallback;
not every frame is guaranteed zero-copy.
| Retained obligation | Inclusive limit |
|---|---|
| Upstream Init/reentry and fallback backlog, shared | 2,097,152 logical bytes and 1,024 buffers |
| Downstream response-header/reentry backlog | 2,097,152 logical bytes and 1,024 buffers |
| Request/UDP parser, including cached metadata and active input | 2,162,689 logical bytes (2 MiB + 65,537) |
Empty retained output buffers consume entries; incomplete UDP fragments have no 1,024-entry output cap. Equality is accepted; overflow or queue refusal closes the affected association and releases locally owned data. These are retention limits, not RSS/kernel-pipe-memory limits or size limits on an already-ready synchronous TCP handoff.
Typical Placement
Normal VLESS server:
TcpListener -> TlsServer -> VlessServer -> TcpUdpConnector
TCP-only outbound:
TcpListener -> TlsServer -> VlessServer -> TcpConnector
UDP-capable outbound:
TcpListener -> TlsServer -> VlessServer -> TcpUdpConnector
VlessServer does not terminate TLS by itself. For public VLESS deployments, place TlsServer before it. Without TLS,
the UUID and destination metadata are visible on the wire.
Minimal Example
[
{
"name": "tls-in",
"type": "TlsServer",
"settings": {
"cert-file": "/etc/waterwall/fullchain.pem",
"key-file": "/etc/waterwall/privkey.pem"
},
"next": "vless-server"
},
{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"uuid": "5783a3e7-e373-51cd-8642-c83782b807c5",
"connect": true,
"udp": true,
"verbose": false
},
"next": "outbound"
},
{
"name": "outbound",
"type": "TcpUdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port"
}
}
]
Multiple Local Users
{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"users": [
"5783a3e7-e373-51cd-8642-c83782b807c5",
{
"username": "alice",
"id": "11111111-2222-3333-4444-555555555555"
}
],
"connect": true,
"udp": true
},
"next": "outbound"
}
In local allowlist mode, a matching object username is recorded on the line. The canonical lowercase UUID string is
also recorded as the line password so Router rules can match authenticated credentials even without a users database.
AuthenticationClient Mode
{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"auth-client-node-name": "auth-client",
"connect": true,
"udp": true
},
"next": "outbound"
}
In this mode, the wire UUID is converted to canonical lowercase dashed form and authenticated as the user's password
through AuthenticationClient.
Example user database entry:
{
"id": 2001,
"name": "vless-user",
"password": "5783a3e7-e373-51cd-8642-c83782b807c5",
"enabled": true
}
When auth-client-node-name is configured, local UUID settings are rejected. VlessServer internally inserts a
UserController before the configured outbound node so user accounting and user-aware routing can work normally.
Fallback Example
{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"uuid": "5783a3e7-e373-51cd-8642-c83782b807c5",
"fallback-node-name": "fallback-service",
"fallback-intentional-delay-ms": 7,
"fallback-intentional-delay-jitter-ms": 1,
"connect": true,
"udp": true
},
"next": "outbound"
}
Fallback is for active-probing resistance. Invalid unauthenticated traffic can be handed to another configured node instead of being closed immediately.
Required Fields
Top-level fields:
| Field | Type | Description |
|---|---|---|
name | string | User-chosen node name. Must be unique inside the config file. |
type | string | Must be exactly "VlessServer". |
settings | object | Non-empty VLESS server settings. |
next | string | Required. Accepted VLESS traffic is forwarded to this node. |
Choose exactly one authentication mode.
Local Authentication Settings
Use local authentication by omitting auth-client-node-name and configuring at least one UUID.
| Field | Type | Description |
|---|---|---|
uuid | string | One UUID. |
id | string | Alias for uuid. Use either uuid or id, not both. |
uuids | array of strings | Multiple UUIDs. |
users | array | UUID strings or objects with one of uuid, id, or user-id. Objects can include username. |
clients | array | Alias for users. Use either users or clients, not both. |
UUIDs may be dashed RFC4122 strings or compact 32-character hex strings. Duplicate local UUIDs are fatal configuration errors, even if the duplicate entries use different usernames.
Database Authentication Settings
| Field | Type | Description |
|---|---|---|
auth-client-node-name | string | Name of an existing AuthenticationClient node in the same config. |
In database mode, do not configure uuid, id, uuids, users, or clients.
Optional Settings
| Field | Default | Description |
|---|---|---|
connect | true | Enables VLESS TCP command 0x01. |
udp | true | Enables VLESS UDP command 0x02. |
verbose | false | Enables extra rejection and diagnostic logging. |
fallback-node-name | not set | Fallback branch for unauthenticated invalid probes. Aliases: fallback-node, fallback. |
fallback-intentional-delay-ms | 7 | Delay for upstream payloads sent to fallback. Nonzero delay uses configured jitter. At 0, an active fallback with no older FIFO batch or scheduled drain forwards inline; independently retained bytes remain FIFO and their scheduled drain runs when due without waiting for Resume. |
fallback-intentional-delay-jitter-ms | 1 | Random jitter around delayed fallback payload scheduling. Ignored when delay is 0. |
At least one of connect or udp must be enabled.
Request And Response Format
Accepted request format:
version: 1 byte, must be 00
user id: 16 raw UUID bytes
addons len: 1 byte, must be 00
command: 01 TCP or 02 UDP
destination: port first, then address
body: TCP raw stream or UDP length-prefixed packets
Destination format:
port: 2 bytes big-endian
address type: 01 IPv4, 02 domain, 03 IPv6
address body: IPv4 4 bytes, domain length + bytes, or IPv6 16 bytes
Response header:
00 00
Every same-line TCP and fallback Est is forwarded, including repeats, independently of the response header and output
Pause. Owned UDP backend Est maps to its client association once. The 00 00 header is sent exactly once, together with
the complete first nonempty TCP reply or first framed UDP reply in one ordinary buffer, including a valid reply received
before Est. An empty UDP reply is valid: its first combined response is 00 00 00 00, containing the response header
and the datagram's zero length.
Est, Resume and empty TCP replies never send a standalone header; there is no header-only timer. A connection that closes
without a backend reply sends no VLESS response bytes. Later replies keep the normal splice-capable forwarding path.
This avoids a deliberate header-only write, but does not guarantee TCP packet or TLS record boundaries.
Authenticated TCP permanently bypasses this node after the first response header/body handoff, once initialization and retained input/replies have settled. Local line state is destroyed before bypass. UDP and fallback remain active for framing, owned-line cleanup and branch routing.
TCP and fallback relay every Pause/Resume, including duplicates. TCP response-ordering storage and due fallback replay drain without waiting for Resume. UDP associations retain producer control: their independent reply drain waits while client output is paused, while an admitted reply may complete synchronously. Response/Est reentry cannot reorder older replies. The retained response FIFO is bounded to 2 MiB logical bytes plus 1,024 buffers, with transactional admission and per-association close on overflow. Client receiver Pause recorded before branch Init is replayed onto the initialized selected branch and every new or recreated owned UDP backend. Before branch selection, Resume clears that pending Pause. Nested application input during branch Init/replay stays behind the original input in a temporary FIFO with the same limits. Authentication, fallback selection and the exact owned UDP backend remain unchanged.
TCP Runtime Behavior
For command 0x01, the server:
- authenticates the UUID
- parses the requested destination
- writes that destination into
line->routing_context.dest_ctx - initializes the configured
nextnode - forwards any TCP body bytes that arrived in the same payload as the request header
- combines the VLESS response header with the first nonempty backend reply in one ordinary buffer
The configured next node is usually TcpConnector, TcpUdpConnector, Router, or another node that understands the
destination context.
UDP Runtime Behavior
For command 0x02, the destination from the initial VLESS request is fixed for that VLESS line. Individual UDP frames do
not carry per-packet addresses.
UDP frames use:
length: 2 bytes big-endian
payload: length bytes
VlessServer creates one internal backend UDP line for the target destination. Incoming VLESS UDP frames are decoded and
forwarded to that backend line. Replies from the backend line are wrapped with the same 2-byte length prefix and sent
downstream to the VLESS client.
A received length of 0 consumes its two-byte prefix and delivers exactly one empty UDP datagram. The association
stays open and parsing continues with the next frame in order. Empty backend replies emit a 00 00 length prefix;
oversized local replies are dropped without closing the backend.
This differs from Xray's base VLESS implementation, whose writers drop empty datagrams and whose reader skips zero-length frames.
Empty-datagram delivery requires a peer that supports it; nonempty framing is unchanged.
Empty datagrams are valid at the UDP layer (RFC 8085 §5).
UDP parser input is capped at 2,162,689 logical bytes, including cached metadata. Partial two-byte length prefixes and datagram bodies wait for later input.
Fallback Behavior
Fallback is used only before successful UUID authentication. Once the UUID is accepted, malformed addons, unsupported commands, malformed destinations, or bad UDP frames close the VLESS line instead of replaying authenticated bytes to fallback.
Fallback can be selected for:
- invalid version byte
- UUID split across the first upstream payload
- failed UUID lookup
- cached AuthenticationClient admission being unavailable (unloaded, stopping, rejected or terminal continuity failure)
When fallback is configured:
- the fallback branch receives
Initimmediately - saved initial bytes and later upstream payloads are forwarded to fallback
- normal live upstream fallback payloads observe
fallback-intentional-delay-msplus jitter when delay is nonzero - at delay
0, an active fallback with no older FIFO batch or scheduled drain forwards inline; independently retained bytes remain FIFO and their scheduled drain runs when due without waiting for Resume - the delayed fallback FIFO is bounded to the shared upstream 2 MiB / 1,024-buffer budget
- on upstream
Finish, an initialized branch receives one accepted FIFO batch synchronously before fallbackFinish, including during Pause; a branch still inside Init discards the still-local batch and closes - downstream fallback responses are not intentionally delayed
The remaining intentional delay is not kept alive after upstream close.
Fallback quality is part of the public fingerprint. A down fallback, generic error, immediate close, mismatched content, or mismatched timing can identify the deployment. Tune delay and jitter against the public behavior you are trying to resemble; they are mitigations, not proof of indistinguishability.
First-Payload Authentication Rule
The complete VLESS credential, meaning the version byte plus the 16-byte raw UUID, must be present in the first upstream payload callback.
If the first payload does not contain the full credential, the line is treated as an invalid unauthenticated probe. With fallback configured, those bytes go to fallback. Without fallback, the line is closed. Later VLESS fields may still arrive across multiple payload callbacks after the UUID is present.
Finish Behavior
VlessServer destroys its own line state before propagating real Finish callbacks.
For TCP and fallback branches, it forwards finish toward the selected branch when appropriate. For UDP, it owns the internal backend UDP line it created and closes it safely when either side closes.
Padding
VlessServer prepends the 2-byte response header once and a 2-byte length prefix on every UDP reply. The first UDP
reply needs both, so it advertises:
required_padding_left = 4
Unsupported Features
The current implementation rejects:
- nonzero request addons length
- XTLS Vision flow addons
- mux command
0x03 - reverse command
0x04 - unknown commands
- XUDP and advanced UDP/mux behavior
- VLESS over non-stream transports
Invalid unauthenticated probes may go to fallback. Bad authenticated protocol data closes without writing another VLESS response header.
Common Mistakes
- Do not expose
VlessServerdirectly on a public TCP listener withoutTlsServerunless you intentionally want plain VLESS on the wire. - Do not configure local UUIDs together with
auth-client-node-name. - Do not set the next node to a manual
UserController; database mode inserts its own internalUserController. - Do not expect UDP frames to choose a new destination per packet; the initial VLESS request fixes the UDP target.
- Do not rely on unsupported VLESS features such as Vision, flow, mux, reverse, or XUDP.
Node Metadata
Source-backed metadata:
| Property | Value |
|---|---|
| node flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 4 bytes |