Skip to main content

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 obligationInclusive limit
Upstream Init/reentry and fallback backlog, shared2,097,152 logical bytes and 1,024 buffers
Downstream response-header/reentry backlog2,097,152 logical bytes and 1,024 buffers
Request/UDP parser, including cached metadata and active input2,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:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "VlessServer".
settingsobjectNon-empty VLESS server settings.
nextstringRequired. 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.

FieldTypeDescription
uuidstringOne UUID.
idstringAlias for uuid. Use either uuid or id, not both.
uuidsarray of stringsMultiple UUIDs.
usersarrayUUID strings or objects with one of uuid, id, or user-id. Objects can include username.
clientsarrayAlias 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​

FieldTypeDescription
auth-client-node-namestringName of an existing AuthenticationClient node in the same config.

In database mode, do not configure uuid, id, uuids, users, or clients.

Optional Settings​

FieldDefaultDescription
connecttrueEnables VLESS TCP command 0x01.
udptrueEnables VLESS UDP command 0x02.
verbosefalseEnables extra rejection and diagnostic logging.
fallback-node-namenot setFallback branch for unauthenticated invalid probes. Aliases: fallback-node, fallback.
fallback-intentional-delay-ms7Delay 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-ms1Random 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:

  1. authenticates the UUID
  2. parses the requested destination
  3. writes that destination into line->routing_context.dest_ctx
  4. initializes the configured next node
  5. forwards any TCP body bytes that arrived in the same payload as the request header
  6. 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 Init immediately
  • saved initial bytes and later upstream payloads are forwarded to fallback
  • normal live upstream fallback payloads observe fallback-intentional-delay-ms plus 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 fallback Finish, 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 VlessServer directly on a public TCP listener without TlsServer unless 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 internal UserController.
  • 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:

PropertyValue
node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left4 bytes