Transport · Qeli 0.8.1

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.

Choosing a mode

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.

ModeCarrierServerClientPurpose
plainTCPobf.mode=plainproto=tcp
mode=plain
Base encrypted tunnel with no TLS-shaped layer; X25519 only
fake-tlsTCP / UDPobf.mode=fake-tlsmode=fake-tlsTLS-shaped ClientHello and records, but not genuine TLS
obfsTCP / UDPobf.mode=obfs
obf.obfs_key
mode=obfs
obfs_key
ChaCha20 wrapper with a shared PSK; WebSocket fronting is available on TCP
realityTCPobf.mode=fake-tls
reality_proxy.enabled=true
real_tls=false
mode=fake-tls
reality_sid + sni
The server recognises its client and bridges a foreign ClientHello to the target; there is no inner TLS
reality-tlsTCPobf.mode=reality-tls
reality_proxy.enabled=true
real_tls=true
mode=reality-tls
key + reality_sid + sni
Tunnel inside genuine TLS 1.3 with the target site's certificate
udp-quicUDPobf.mode=fake-tls
obf.quic.enabled=true
mode=udp-quicShorthand for UDP + fake-tls + QUIC-shaped framing
Two different defaults: the built-in configuration profile uses fake-tls, while the official 0.8.1 installer deploys reality-tls on TCP/443.
UDP restriction: 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 in release 0.8.0

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 carrierRecordizer 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.
server.conf · [profile:reality-tls]
obf.mode = reality-tls
obf.recordizer.policy = prefer
For Reality/H2 the qeli heartbeat is forcibly disabled: transport provides liveness and separate heartbeat frames would create a repeating tell. Any proxy or load balancer in front must use transparent TCP pass-through without TLS termination or H2 conversion.
Upgrade the server first. A 0.8.0 server accepts both the new H2 and legacy carrier; a 0.8.0 client requires H2 and does not downgrade. Verify the profile after upgrading, then move to recordizer.policy=prefer.
Before AUTH

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 keyClient keyRule
bind.transportprotoMust match: tcp or udp
obf.modemodeMust describe the same wire mode; reality without inner TLS uses client mode=fake-tls
obf.obfs_keyobfs_keyThe same non-empty PSK for obfs
obf.obfs_frontingfrontFor TCP obfs: websocket or none
reality_proxy.short_idsreality_sidThe client short ID must be in the allowed list
reality_proxy.targetsniFor reality/reality-tls, SNI must match the target
obf.quic.enabledquicThe server recognises QUIC framing per connection; the server flag adds quic=1 to generated links
obf.awg.jcjcFor TCP obfs, jc must match; UDP junk is sender-only
Do not edit QR links by hand. Generate the link in the panel or with qeli add-client --link --link-profile <profile>; it takes wire settings from the selected profile.
Minimal pairs

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

server.conf · [profile:reality-tls]
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
client.conf · [qeli]
proto = tcp
mode = reality-tls
key = <64-hex-server-key>
sni = www.microsoft.com
reality_sid = 0123456789abcdef

reality · TCP

server.conf + client.conf
[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

server.conf + client.conf
[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

server.conf + client.conf
[profile:udp-quic]
bind.transport = udp
obf.mode = fake-tls
obf.quic.enabled = true

[qeli]
mode = udp-quic
mtu = 0
Client aliases: mode=udp-quic expands to proto=udp + mode=fake-tls + quic=true; mode=udp-obfs expands to proto=udp + mode=obfs.
REALITY

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.

KeyDefaultPurpose
obf.tls.server_namewww.cloudflare.comSNI placed by the server in generated links
reality_proxy.enabledfalseEnables client classification and forwarding for probes
reality_proxy.targetwww.cloudflare.comTarget HTTPS host; the client SNI must match
reality_proxy.target_port443Target port
reality_proxy.short_idsemptyAllowed short IDs: hex, no more than 8 bytes. At least one ID is required when enabled=true
reality_proxy.real_tlsfalsetrue enables genuine TLS 1.3 for a recognised client
reality_proxy.handrolledtrueByte-grade TLS with a borrowed certificate chain and mirrored JA3S
reality_proxy.peek_timeout_ms1500How long to wait for ClientHello before classifying the connection
SNI is not rotated in REALITY. For reality and reality-tls it must match the single target. Create multiple profiles for multiple front domains.
Additional wrappers

Obfs, QUIC-shaped framing and AWG junk

KeyDefaultPurpose
obf.obfs_keyRequired shared secret for obfs; must match client obfs_key
obf.obfs_frontingwebsocketTCP obfs: genuine WebSocket binary frames or none
obf.quic.enabledfalseUDP: marks generated links for QUIC-shaped framing
obf.awg.enabledfalseAdds junk packets before the handshake on TCP obfs and any UDP profile
obf.awg.jc0Number of junk packets, maximum 128
obf.awg.jmin / jmax40 / 300Size range; jmin ≤ jmax ≤ 1400, with each UDP packet capped at 1200
AWG synchronisation differs: on TCP obfs, server and client must use the same jc. On UDP the client sends junk before ClientHello and the server drops it cheaply, so the counts need not match.
Flow shape

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.

GroupMain defaultsPurpose
obf.padding.*enabled=true
32..512
probability=1.0
Random record padding; normally disabled for reality-tls as a redundant layer
obf.fragmentation.*enabled=true
256..1024
max=4
Splits only ServerHello during the handshake, not the data plane
obf.heartbeat.*enabled=true
15000 ms
16 B
Periodic keepalive/cover; shaping replaces the fixed heartbeat
obf.traffic_shaping.*enabled=false
mean=700 ms
budget=16384 B/s
Non-periodic idle cover; stealth additionally caps throughput under load
obf.traffic_normalization.*enabled=falseRounds record sizes to configured values
obf.anti_fingerprinting.*enabled=falseAdds jitter to the handshake response
obf.multipath.*enabled=false
max_streams=4
adaptive=false
Aggregates multiple TCP connections; not used on UDP
Do not disable every UDP liveness policy. If heartbeat and shaping are off, set a finite perf.connection.idle_timeout_secs, otherwise a vanished UDP client may keep an IP and slot for too long.
client.conf and qeli://

Client transport keys

KeyDefaultPurpose
prototcpTCP or UDP carrier socket
modefake-tlsplain, fake-tls, obfs, reality-tls, or the udp-quic/udp-obfs aliases
keyServer key pinning; required for reality-tls and require_client_key_proof/H-1 policies
sniautoFake-tls: configured name, hostname, or a random decoy when connecting by IP; REALITY: exactly the target
reality_sidShort ID for reality/reality-tls
obfs_keyPSK for obfs mode
frontwebsocketTCP obfs fronting: websocket or none
quicfalseQUIC-shaped UDP handshake; the udp-quic alias enables it automatically
awg / jc / jmin / jmaxoff / 0 / 40 / 300Pre-handshake junk sequence
mtu00 accepts push; on UDP it starts path-MTU probing when mtu_probe=true
recv_buffer_sizeauto 4→8/16 MiBUDP receive buffer; an explicit value disables auto-growth, while 0 keeps the OS setting
send_buffer_size0UDP send buffer; 0 leaves the OS setting unchanged
Cryptography: fake-tls, obfs, reality/reality-tls and UDP use hybrid X25519 + ML-KEM-768. Plain uses X25519 without ML-KEM.
Before restart

Validate the config pair

Validate the server and client INI files separately, then compare the generated link with the selected profile.

validation and link generation
$ 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
SymptomWhat to check
Timeout before handshakebind.transport versus proto, port and firewall
Handshake discriminator / wire errormode, obfs_key, front, QUIC and AWG
REALITY bridge instead of AUTHreality_sid, SNI/target, key and real_tls mode
UDP profile rejectedUse fake-tls or obfs and disable multipath
UDP works only for small packetsmtu=0, mtu_probe, the ICMP/DF path and actual PMTU

Full stage-by-stage troubleshooting →

Primary sources

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.