Transport modes and settings
How to choose plain, fake-tls, obfs, reality, reality-tls or udp-quic, which values must match on server and client, and which settings shape the flow.
Six modes without mixing up the terms
The carrier is configured separately from the wire mode. That is why fake-tls and obfs work over both TCP and UDP, while reality is a fake-tls profile with client recognition and forwarding for foreign connections.
| Mode | Carrier | Server | Client | Purpose |
|---|---|---|---|---|
plain | TCP | obf.mode=plain | proto=tcpmode=plain | Base encrypted tunnel with no TLS-shaped layer; X25519 only |
fake-tls | TCP / UDP | obf.mode=fake-tls | mode=fake-tls | TLS-shaped ClientHello and records, but not genuine TLS |
obfs | TCP / UDP | obf.mode=obfsobf.obfs_key | mode=obfsobfs_key | ChaCha20 wrapper with a shared PSK; WebSocket fronting is available on TCP |
reality | TCP | obf.mode=fake-tlsreality_proxy.enabled=truereal_tls=false | mode=fake-tlsreality_sid + sni | The server recognises its client and bridges a foreign ClientHello to the target; there is no inner TLS |
reality-tls | TCP | obf.mode=reality-tlsreality_proxy.enabled=truereal_tls=true | mode=reality-tlskey + reality_sid + sni | Tunnel inside genuine TLS 1.3 with the target site's certificate |
udp-quic | UDP | obf.mode=fake-tlsobf.quic.enabled=true | mode=udp-quic | Shorthand for UDP + fake-tls + QUIC-shaped framing |
fake-tls, while the official 0.8.1 installer deploys reality-tls on TCP/443.fake-tls and obfs are valid, including the udp-quic and udp-obfs aliases. plain, reality and reality-tls over UDP are rejected by validation.New Reality/H2 carrier and PACKET_MUX_V1
In 0.8.0 the mode name stays the same while its internal carrier changes: after REALITY TLS 1.3, reality-tls opens a genuine HTTP/2 stream. A shared recordizer is negotiated separately after AUTH and can run over any TCP/UDP carrier.
| 0.8.0 carrier | Recordizer policy |
|---|---|
One long-lived bidirectional POST /v1/events/stream, ALPN h2, standard SETTINGS/HEADERS/DATA/WINDOW_UPDATE and 2–8 ms batching. There is no user-facing H2 toggle. | prefer negotiates PACKET_MUX_V1 with a new client and keeps a legacy client working; required rejects a legacy client before lease; off preserves the old data plane. |
obf.mode = reality-tls
obf.recordizer.policy = prefer
recordizer.policy=prefer.Settings the server cannot push yet
Wire settings are needed for the handshake itself, so the client must know them in advance from qeli:// or client.conf. Server push arrives only after successful AUTH.
| Server key | Client key | Rule |
|---|---|---|
bind.transport | proto | Must match: tcp or udp |
obf.mode | mode | Must describe the same wire mode; reality without inner TLS uses client mode=fake-tls |
obf.obfs_key | obfs_key | The same non-empty PSK for obfs |
obf.obfs_fronting | front | For TCP obfs: websocket or none |
reality_proxy.short_ids | reality_sid | The client short ID must be in the allowed list |
reality_proxy.target | sni | For reality/reality-tls, SNI must match the target |
obf.quic.enabled | quic | The server recognises QUIC framing per connection; the server flag adds quic=1 to generated links |
obf.awg.jc | jc | For TCP obfs, jc must match; UDP junk is sender-only |
qeli add-client --link --link-profile <profile>; it takes wire settings from the selected profile.Ready server and client settings
Only transport keys are shown. TUN addresses, pool, users, NAT and DNS are added as usual and do not depend on the selected mode. Replace the example short IDs, server key and PSK with your own values.
reality-tls · TCP
bind.transport = tcp
obf.mode = reality-tls
obf.tls.server_name = www.microsoft.com
obf.tls.reality_proxy.enabled = true
obf.tls.reality_proxy.target = www.microsoft.com
obf.tls.reality_proxy.target_port = 443
obf.tls.reality_proxy.real_tls = true
obf.tls.reality_proxy.short_ids = 0123456789abcdef
obf.padding.enabled = false
proto = tcp
mode = reality-tls
key = <64-hex-server-key>
sni = www.microsoft.com
reality_sid = 0123456789abcdef
reality · TCP
[profile:reality]
bind.transport = tcp
obf.mode = fake-tls
obf.tls.server_name = www.microsoft.com
obf.tls.reality_proxy.enabled = true
obf.tls.reality_proxy.target = www.microsoft.com
obf.tls.reality_proxy.real_tls = false
obf.tls.reality_proxy.short_ids = fedcba9876543210
[qeli]
proto = tcp
mode = fake-tls
key = <64-hex-server-key>
sni = www.microsoft.com
reality_sid = fedcba9876543210
obfs · TCP
[profile:obfs]
bind.transport = tcp
obf.mode = obfs
obf.obfs_key = <strong-shared-secret>
obf.obfs_fronting = websocket
[qeli]
proto = tcp
mode = obfs
obfs_key = <strong-shared-secret>
front = websocket
udp-quic · UDP
[profile:udp-quic]
bind.transport = udp
obf.mode = fake-tls
obf.quic.enabled = true
[qeli]
mode = udp-quic
mtu = 0
mode=udp-quic expands to proto=udp + mode=fake-tls + quic=true; mode=udp-obfs expands to proto=udp + mode=obfs.Client recognition and genuine TLS
REALITY settings belong to a profile. An unrecognised ClientHello is forwarded to the target; a recognised client gets either the regular Qeli data plane or genuine TLS 1.3 when real_tls=true.
| Key | Default | Purpose |
|---|---|---|
obf.tls.server_name | www.cloudflare.com | SNI placed by the server in generated links |
reality_proxy.enabled | false | Enables client classification and forwarding for probes |
reality_proxy.target | www.cloudflare.com | Target HTTPS host; the client SNI must match |
reality_proxy.target_port | 443 | Target port |
reality_proxy.short_ids | empty | Allowed short IDs: hex, no more than 8 bytes. At least one ID is required when enabled=true |
reality_proxy.real_tls | false | true enables genuine TLS 1.3 for a recognised client |
reality_proxy.handrolled | true | Byte-grade TLS with a borrowed certificate chain and mirrored JA3S |
reality_proxy.peek_timeout_ms | 1500 | How long to wait for ClientHello before classifying the connection |
Obfs, QUIC-shaped framing and AWG junk
| Key | Default | Purpose |
|---|---|---|
obf.obfs_key | — | Required shared secret for obfs; must match client obfs_key |
obf.obfs_fronting | websocket | TCP obfs: genuine WebSocket binary frames or none |
obf.quic.enabled | false | UDP: marks generated links for QUIC-shaped framing |
obf.awg.enabled | false | Adds junk packets before the handshake on TCP obfs and any UDP profile |
obf.awg.jc | 0 | Number of junk packets, maximum 128 |
obf.awg.jmin / jmax | 40 / 300 | Size range; jmin ≤ jmax ≤ 1400, with each UDP packet capped at 1200 |
jc. On UDP the client sends junk before ClientHello and the server drops it cheaply, so the counts need not match.Padding, fragmentation, heartbeat and multipath
These features sit on top of the base mode. They change record sizes and timing or the number of streams, but do not replace a correct transport/mode pair.
| Group | Main defaults | Purpose |
|---|---|---|
obf.padding.* | enabled=true32..512probability=1.0 | Random record padding; normally disabled for reality-tls as a redundant layer |
obf.fragmentation.* | enabled=true256..1024max=4 | Splits only ServerHello during the handshake, not the data plane |
obf.heartbeat.* | enabled=true15000 ms16 B | Periodic keepalive/cover; shaping replaces the fixed heartbeat |
obf.traffic_shaping.* | enabled=falsemean=700 msbudget=16384 B/s | Non-periodic idle cover; stealth additionally caps throughput under load |
obf.traffic_normalization.* | enabled=false | Rounds record sizes to configured values |
obf.anti_fingerprinting.* | enabled=false | Adds jitter to the handshake response |
obf.multipath.* | enabled=falsemax_streams=4adaptive=false | Aggregates multiple TCP connections; not used on UDP |
perf.connection.idle_timeout_secs, otherwise a vanished UDP client may keep an IP and slot for too long.Client transport keys
| Key | Default | Purpose |
|---|---|---|
proto | tcp | TCP or UDP carrier socket |
mode | fake-tls | plain, fake-tls, obfs, reality-tls, or the udp-quic/udp-obfs aliases |
key | — | Server key pinning; required for reality-tls and require_client_key_proof/H-1 policies |
sni | auto | Fake-tls: configured name, hostname, or a random decoy when connecting by IP; REALITY: exactly the target |
reality_sid | — | Short ID for reality/reality-tls |
obfs_key | — | PSK for obfs mode |
front | websocket | TCP obfs fronting: websocket or none |
quic | false | QUIC-shaped UDP handshake; the udp-quic alias enables it automatically |
awg / jc / jmin / jmax | off / 0 / 40 / 300 | Pre-handshake junk sequence |
mtu | 0 | 0 accepts push; on UDP it starts path-MTU probing when mtu_probe=true |
recv_buffer_size | auto 4→8/16 MiB | UDP receive buffer; an explicit value disables auto-growth, while 0 keeps the OS setting |
send_buffer_size | 0 | UDP send buffer; 0 leaves the OS setting unchanged |
Validate the config pair
Validate the server and client INI files separately, then compare the generated link with the selected profile.
$ sudo qeli check-config --config /etc/qeli/server.conf
$ qeli check-config --client --config /etc/qeli/client.conf
$ sudo qeli show-identity --config /etc/qeli/server.conf
$ sudo qeli add-client <user> --password '<strong-password>' --link --host vpn.example.com:443 \
--link-profile <profile> --config /etc/qeli/server.conf
| Symptom | What to check |
|---|---|
| Timeout before handshake | bind.transport versus proto, port and firewall |
| Handshake discriminator / wire error | mode, obfs_key, front, QUIC and AWG |
| REALITY bridge instead of AUTH | reality_sid, SNI/target, key and real_tls mode |
| UDP profile rejected | Use fake-tls or obfs and disable multipath |
| UDP works only for small packets | mtu=0, mtu_probe, the ICMP/DF path and actual PMTU |
Complete settings and ready-made profiles
Links are pinned to tag v0.8.1. The multiprofile example is the easiest practical starting point, while CONFIG.md and the dedicated recordizer page contain constraints and defaults.