Guides, Nov 6, 2026

# VLESS protocol explained: how it works, Vision, REALITY, configs

VLESS is a lightweight, stateless proxy protocol from the Xray project. The client sends a short header (a version byte, a 16-byte UUID, the command and the destination) and then the data; the server checks the UUID against its list and forwards the traffic. VLESS does not encrypt by default and does not depend on the clock, so in practice it always runs inside TLS or REALITY. An optional post-quantum VLESS Encryption arrived in the stable Xray-core v25.9.5 release.

ST Muntaha, 10 min read

VLESS is the protocol behind every vless:// link that a 3x-ui, Marzban or Hiddify panel hands out, and it is simpler than its reputation. This post explains what VLESS does on the wire, what it leaves to other layers, and how the pieces around it (the Vision flow, transports, REALITY) fit together. It ends with a minimal server and client config. Versions I checked against: Xray-core v26.10.31 and sing-box 1.14.4, the current releases on 1 November 2026.

## What is the VLESS protocol?

VLESS is a proxy protocol: it tells a server "connect me to this address and port", proves who is asking, and then carries the data. Xray-core is the reference implementation, and sing-box implements it too. The official [VLESS configuration docs](https://xtls.github.io/en/config/inbounds/vless.html) describe it in one line: "a stateless, lightweight transport protocol".

Three properties explain almost everything about it:

-   **Stateless.** Each connection authenticates on its own. There are no sessions to keep, and the server needs no clock: unlike VMess, VLESS works even when the client's time is wrong.
-   **UUID authentication.** Each user has an ID. It is normally a UUID, but Xray also accepts any string of less than 30 bytes and maps it to a UUIDv5. `xray uuid` makes a random one, and `xray uuid -i "some text"` shows the UUID for a string.
-   **No encryption of its own, by default.** VLESS leaves confidentiality to the layer below it, which is TLS or REALITY. Inside TLS, a second layer of encryption would only cost CPU.

## Is VLESS a VPN?

No. VLESS carries TCP connections and UDP traffic for applications, one connection at a time. It does not create a network interface, assign the client an address in a private network, or route IP packets the way WireGuard or OpenVPN do. The server only sees "connect to this host and port".

Client apps make it feel like a VPN. They run a TUN interface on the device, take each connection that enters it, and send that connection through VLESS. For a user the result looks the same. For an operator the difference matters: there is no tunnel subnet to route, and the server's outbound and DNS settings decide where traffic goes.

## What does a VLESS request look like on the wire?

The [VLESS protocol spec](https://xtls.github.io/en/development/protocols/vless.html) is short. A request is:

| Size | Field | Notes |
| --- | --- | --- |
| 1 byte | Protocol version | Xray-core sends 0 |
| 16 bytes | User ID | The UUID as raw bytes |
| 1 byte | Addons length M | 0 when there are no addons |
| M bytes | Addons | Protobuf; carries the flow, such as Vision |
| 1 byte | Command | 1 = TCP, 2 = UDP, 3 = Mux, 4 = reverse proxy |
| 2 bytes | Port | Destination port |
| 1 byte | Address type | 1 = IPv4, 2 = domain, 3 = IPv6 |
| S bytes | Address | Destination address |
| X bytes | Request data | The application's first bytes |

The response is even smaller: the same version byte, an addons length, the addons (usually none), then the data.

There is no MAC, no timestamp and no padding in the header. That is why VLESS is cheap to run, and why it must never cross the internet without TLS or REALITY around it: anyone on the path could read the UUID and the destination of every connection.

The server checks the 16 ID bytes against its user list. If the ID is unknown, Xray logs `invalid request user id` and either closes the connection or, if `fallbacks` are configured, passes it on to another service such as a web server. Fallbacks are a VLESS and Trojan feature in Xray; they let one port serve both the proxy and a normal website.

## Does VLESS encrypt traffic?

Not unless you turn on VLESS Encryption. In a normal config the server sets `"decryption": "none"` and the client sets `"encryption": "none"`. The value cannot be left empty. A server config without it fails with `VLESS settings: please add/set "decryption":"none" to every settings`.

Since Xray-core v26.7.11, the client also refuses to send plain VLESS to a public address. If `security` is `none` and VLESS Encryption is off, the config fails with `vless without TLS or other encryption is prohibited unless the server address is a private IP or domain`. Plain VLESS is still allowed to private addresses, for example between two machines in one data centre.

VLESS Encryption itself first shipped in the Xray-core v25.8.29 pre-release, and v25.9.5 was the first stable release with it ([PR #5067](https://github.com/XTLS/Xray-core/pull/5067)). The value is a dot-separated string that starts with `mlkem768x25519plus`, a handshake that combines ML-KEM-768 and X25519, and `xray vlessenc` generates a matching server and client pair. A few facts to know before using it:

-   Xray's own transport docs say it protects the VLESS payload but does not give the ordinary HTTPS appearance of TLS or REALITY. It is not meant to replace them for censorship resistance.
-   A server with `decryption` set cannot use `fallbacks`.
-   sing-box 1.14.4 has no `encryption` option in its VLESS outbound, so links that use VLESS Encryption work only in Xray-based clients for now.

## How is VLESS different from VMess?

VMess is the older V2Ray protocol. It authenticates with the UUID plus the current time and always encrypts its own payload. VLESS dropped both.

|  | VLESS | VMess |
| --- | --- | --- |
| Authentication | UUID, sent as-is inside TLS or REALITY | UUID combined with the time |
| Clock dependency | None | Client and server within 120 seconds |
| Own encryption | None by default; optional VLESS Encryption | Always, unless set to `none` |
| XTLS Vision flow | Yes | No |
| Fallbacks | Yes | No |
| Status in Xray-core v26 | Current | Startup warning: deprecated |

The last row is recent. Since v26.1.23, Xray-core prints `The feature VMess (with no Forward Secrecy, etc.) is deprecated, not recommended for using and might be removed` at startup for any VMess inbound or outbound, and points users to VLESS Encryption. VMess still works, but new deployments have no reason to pick it.

## What is the xtls-rprx-vision flow?

Vision is the flow that VLESS carries in its addons. It does two things. First, it adds random padding to the inner TLS handshake (the handshake between the user's browser and the website), because the record sizes of TLS inside TLS form a recognisable pattern. Second, once the inner connection reaches TLS 1.3 application data, Vision stops wrapping it: the data is already encrypted, so Xray copies it directly instead of encrypting it a second time. On Linux this can use the kernel's splice, so the data never passes through Xray's memory.

The constraints are strict:

-   Vision works only on the RAW transport with TLS or REALITY. With VLESS Encryption enabled it works on any transport. Anywhere else, Xray fails with `XTLS only supports TLS and REALITY directly for now.`
-   Client values are `xtls-rprx-vision` and `xtls-rprx-vision-udp443`. The first one blocks UDP to port 443 (QUIC), so browsers fall back to HTTPS over TCP, which Vision can handle. The second one lets QUIC through, for apps that fail without it.
-   The server's user and the client must agree. A client without flow to a Vision user gets `account ... is rejected since the client flow is empty`, and a client with flow to a user without it gets `account ... is not able to use the flow xtls-rprx-vision`.

## Which transports can VLESS use?

VLESS sits on top of an Xray transport, set in `streamSettings`. The transports that matter for VLESS are:

-   **RAW** (`raw`, formerly called `tcp`): the VLESS bytes go straight onto a TCP connection. The only one that supports Vision without VLESS Encryption.
-   **XHTTP** (`xhttp`, formerly SplitHTTP): VLESS over HTTP requests, over HTTP/1.1, HTTP/2 or HTTP/3. Passes CDNs and reverse proxies, and works with REALITY.
-   **gRPC** (`grpc`): VLESS over HTTP/2 gRPC streams.
-   **WebSocket** (`ws`) and **HTTPUpgrade** (`httpupgrade`): an HTTP/1.1 upgrade, then a stream. Both pass most CDNs.
-   **mKCP** (`mkcp`): a reliable stream over UDP.

REALITY works only with RAW, XHTTP and gRPC. Current Xray-core prints a deprecation warning for gRPC, WebSocket and HTTPUpgrade and recommends XHTTP instead; they still work. Two older transports are gone: the HTTP/2 transport (`h2`/`http`) and QUIC were removed in 2024, and a link with `type=http` fails on current cores.

## What is REALITY, in brief?

REALITY replaces TLS on the server side. The server holds an X25519 private key. The client holds the matching public key (Xray now calls it `password`; in links it is `pbk`) and one of the server's short IDs.

The client sends a normal-looking TLS 1.3 Client Hello, with a browser fingerprint from the uTLS library and the SNI of a real website that is not yours. Inside the Client Hello's session ID field it hides a small encrypted block: its Xray version, the current time and its short ID. The server derives a key from the client's key share and its own private key and tries to decrypt the block. If it decrypts and the short ID is on the list, the server finishes the TLS handshake with a temporary certificate the client can verify. If not, the server forwards the whole connection to the real website (the `target`), and the visitor sees that site's genuine certificate.

That last behaviour is the point: a scanner that probes your server sees someone else's real website. The [REALITY README](https://github.com/XTLS/REALITY) sets the minimum for a target: TLS 1.3 and HTTP/2, a domain that is not used only for redirects, and a site outside the censoring country.

One current compatibility note: REALITY servers on Xray-core v26.9.9 and later also require the X25519MLKEM768 post-quantum key share in the Client Hello. Current Xray clients with the default `chrome` fingerprint send it. sing-box clients currently do not, and they fail against these servers with `reality verification failed`.

## How is a vless:// link put together?

A link is `vless://UUID@host:port?parameters#name`. The parameters carry everything else: the transport (`type`), the security layer (`security`), the TLS or REALITY fields, and transport fields such as `path`. Here is a link for VLESS over WebSocket with TLS, behind a normal domain:

```
vless://4358779f-fff8-4c7c-8051-8103abe626fb@vpn.yourdomain.com:443?type=ws&encryption=none&security=tls&sni=vpn.yourdomain.com&host=vpn.yourdomain.com&path=%2Fapi%2Fws#de-fra-ws
```

And one for RAW with REALITY and Vision:

```
vless://aafd62da-d5aa-406c-985d-17cc48f142fd@vpn.yourdomain.com:443?type=tcp&encryption=none&security=reality&sni=www.example.com&fp=chrome&pbk=DVm-D2KuvBWxtDYfTW6rxE-9YY7q53V9HQ413NryNw0&sid=48047fad2a02cd24&spx=%2F&flow=xtls-rprx-vision#de-fra-01
```

Every field, what it maps to in a config, and how to test a link layer by layer are in [how to check a VLESS key](https://tunnelhq.com/blog/how-to-check-vless-key/).

## What does a minimal Xray VLESS server and client config look like?

This is VLESS with REALITY and Vision on port 443, a common setup on panels such as 3x-ui. First, generate the values on the server:

```
xray uuid
xray x25519
openssl rand -hex 8
```

`xray x25519` prints three lines. `PrivateKey` goes in the server config. `Password (PublicKey)` goes to clients, as `password` in a config or `pbk` in a link. REALITY does not use the third line, `Hash32`. The `openssl` command gives you a 16-character short ID. The values below are examples only: generate your own, and never reuse keys from a blog post.

The server, for example `/usr/local/etc/xray/config.json` on an install made with the official script:

```json
{
  "log": { "loglevel": "warning" },
  "inbounds": [
    {
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          { "id": "aafd62da-d5aa-406c-985d-17cc48f142fd", "flow": "xtls-rprx-vision", "email": "user1" }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality",
        "realitySettings": {
          "target": "www.example.com:443",
          "serverNames": ["www.example.com"],
          "privateKey": "WIjetb6W4iVMPUNHFgXWPNIwcHmyAZmYKnjpnN5nCl0",
          "shortIds": ["48047fad2a02cd24"]
        }
      }
    }
  ],
  "outbounds": [
    { "protocol": "freedom", "tag": "direct" },
    { "protocol": "blackhole", "tag": "block" }
  ],
  "routing": {
    "rules": [
      { "ip": ["geoip:private"], "outboundTag": "block" }
    ]
  }
}
```

Replace `www.example.com` with the site you borrow, in both `target` and `serverNames`. The routing rule stops users from reaching private addresses through your server, such as its own internal network; the release zip and the install script both include the `geoip.dat` file it needs.

The client is the same in reverse: the UUID and flow in `settings`, and the REALITY public half in `realitySettings`:

```json
{
  "protocol": "vless",
  "settings": {
    "address": "vpn.yourdomain.com",
    "port": 443,
    "id": "aafd62da-d5aa-406c-985d-17cc48f142fd",
    "encryption": "none",
    "flow": "xtls-rprx-vision"
  },
  "streamSettings": {
    "network": "raw",
    "security": "reality",
    "realitySettings": {
      "serverName": "www.example.com",
      "fingerprint": "chrome",
      "password": "DVm-D2KuvBWxtDYfTW6rxE-9YY7q53V9HQ413NryNw0",
      "shortId": "48047fad2a02cd24"
    }
  }
}
```

Check both files with `xray run -test -c config.json` before you restart anything. It prints `Configuration OK.` or the exact field that is wrong. Two version notes: current docs call `network` by its new name, `method`, and v26.10.31 accepts both; the flat client `settings` block needs Xray-core v25.9.11 or later, and older cores use the `vnext` form.

The server says `warning` for the log level on purpose. When something fails, raise it to `info` for a while: that level shows rejected UUIDs and the reason for every rejected REALITY handshake.

Full disclosure: TunnelHQ monitors VLESS servers by doing what the client config above does, from outside your network. You paste the vless:// link, and it completes the REALITY or TLS handshake, authenticates and sends real requests through the tunnel every 1 to 5 minutes depending on plan. [VLESS monitoring](https://tunnelhq.com/vless-monitoring/) explains the check, and every plan starts with a 14-day free trial.

Source: https://tunnelhq.com/blog/vless-protocol-explained/
