Skip to content

TUIC — mihomo

mihomo supports both TUIC v4 (token-based) and TUIC v5 (UUID+password) on both sides. There are two ways to declare an inbound: the standard listeners: entry, or the top-level tuic-server block.

Outbound

Entry under proxies: with type: tuic. Embeds BasicOption.

FieldTypeDefaultAllowed valuesDescription
namestring(required)<string>Unique proxy name.
serverstring(required)<host>Upstream server hostname or IP.
portint(required)<port>Upstream server port.
tokenstring(unset)<string>Legacy TUIC v4 token. Mutually exclusive with `uuid`+`password` (v5).
uuidstring(required for v5)<UUID>User UUID (TUIC v5).
passwordstring(required for v5)<string>User password (TUIC v5).
ipstring(unset)<host>Override the destination IP after resolution. Used when SNI / cert is for a different name than the actual server IP.
heartbeat-intervalint10000<milliseconds>QUIC heartbeat interval. **Milliseconds**, not seconds.
alpn[]string[h3]h3ALPN list. TUIC requires HTTP/3 ALPN.
reduce-rttboolfalsetrue | falseUse 0-RTT handshake on reconnect.
request-timeoutint8000<milliseconds>Server-response timeout for the connect frame. Milliseconds.
udp-relay-modestringnativenative | quicHow UDP packets are tunneled. `native` (datagrams) is faster; `quic` (per-packet streams) works through datagram-blocking middleboxes.
congestion-controllerstringcubiccubic | new_reno | bbr | bbr_meta_v1 | bbr_meta_v2QUIC congestion controller. `bbr` is mihomo's v2 BBR. `bbr_meta_v1`/`bbr_meta_v2` keep the older variants for cross-compatibility.
disable-sniboolfalsetrue | falseOmit the SNI extension from the TLS Client Hello entirely.
max-udp-relay-packet-sizeint1252<bytes>Maximum UDP payload size before fragmentation.
fast-openboolfalsetrue | falseSend the proxy-request frame on the same flight as the QUIC handshake.
max-open-streamsint100<int>Maximum concurrent streams per QUIC connection.
cwndint32<int>Initial congestion window in packets. Defaults to 32 (`transport/tuic/common/congestion.go:17-19`).
bbr-profilestring(unset)default | aggressiveBBR tuning profile when `congestion-controller: bbr`.
skip-cert-verifyboolfalsetrue | falseDisable TLS verification (test only).
fingerprintstring(unset)<SHA256 hex>Pin the server's TLS certificate fingerprint.
certificatestring(unset)<PEM file path>Client certificate (mTLS).
private-keystring(unset)<key file path>Private key for `certificate`.
recv-window-connint0<bytes>Per-connection initial receive window. 0 keeps the quic-go default.
recv-windowint0<bytes>Per-stream initial receive window. 0 keeps the quic-go default.
disable-mtu-discoveryboolfalsetrue | falseSkip path-MTU discovery; useful on networks that drop probes.
max-datagram-frame-sizeint1252<bytes>Maximum QUIC datagram frame size.
snistring(server)<SNI>TLS Server Name Indication.
ech-optsECHOptions(disabled)ECHOptionsEncrypted Client Hello configuration.
udp-over-streamboolfalsetrue | falseUse the UDP-over-stream framing instead of QUIC datagrams.
udp-over-stream-versionint11Framing version. Only 1 is defined today.

Source: adapter/outbound/tuic.go:34-69 · pinned at v1.19.27 (5184081)

Inbound (under listeners)

Entry under listeners: with type: tuic. Embeds BaseOption.

FieldTypeDefaultAllowed valuesDescription
token[]string[][<string>]Legacy TUIC v4 token list. Empty disables v4.
usersmap[string]string{}{<uuid>: <password>}TUIC v5 user table. Keys are UUIDs; values are passwords.
certificatestring(required)<PEM file path>TLS server certificate.
private-keystring(required)<key file path>TLS private key.
client-auth-typestring(none)no-client-cert | request-client-cert | require-any-client-cert | verify-client-cert-if-given | require-and-verify-client-certMutual-TLS client-auth mode.
client-auth-certstring(unset)<PEM file path>CA bundle accepted as client roots.
ech-keystring(unset)<ECH config>Encrypted Client Hello material.
congestion-controllerstringcubiccubic | new_reno | bbr | bbr_meta_v1 | bbr_meta_v2Server-side QUIC congestion controller.
max-idle-timeint0<milliseconds>Idle timeout for the QUIC connection. 0 keeps the QUIC default.
authentication-timeoutint1000<milliseconds>Maximum wait for the client to send an authentication frame, in milliseconds.
alpn[]string[h3]h3ALPN list offered during the TLS handshake.
max-udp-relay-packet-sizeint1252<bytes>Maximum UDP payload size accepted from clients.
cwndint32<int>Initial congestion window in packets.
bbr-profilestring(unset)default | aggressiveBBR tuning profile.
mux-optionMuxOption(disabled)MuxOptionsing-style multiplex settings.

Source: listener/inbound/tuic.go:12-29 · pinned at v1.19.27 (5184081)

Inbound (top-level tuic-server)

The top-level tuic-server block is an alternative to declaring a TUIC inbound under listeners. It exists for legacy reasons; for new configs prefer the listener form (it composes naturally with the rest of the inbound model).

FieldTypeDefaultAllowed valuesDescription
enableboolfalsetrue | falseMaster switch for the top-level TUIC inbound. When false, the rest of the block is ignored.
listenstring127.0.0.1:0<host:port>Listen address. Same syntax as elsewhere in mihomo.
token[]string[][<string>]TUIC v4 token list.
usersmap[string]string{}{<uuid>: <password>}TUIC v5 user table.
certificatestring(required)<PEM file path>TLS server certificate.
private-keystring(required)<key file path>TLS private key.
congestion-controllerstringcubiccubic | new_reno | bbr | bbr_meta_v1 | bbr_meta_v2Server-side QUIC congestion controller.
max-idle-timeint0<milliseconds>QUIC idle timeout.
authentication-timeoutint1000<milliseconds>Client auth-frame timeout.
alpn[]string[h3]h3ALPN list.
max-udp-relay-packet-sizeint1252<bytes>Maximum UDP payload size.
cwndint32<int>Initial congestion window.

Source: config/config.go:323-336 · pinned at v1.19.27 (5184081)

Examples

Outbound — TUIC v5:

yaml
proxies:
  - name: tuic-v5
    type: tuic
    server: example.com
    port: 443
    uuid: a3482e88-686a-4a58-8126-99c9df64b7bf
    password: <password>
    udp-relay-mode: native
    congestion-controller: bbr
    sni: example.com
    alpn: [h3]
    heartbeat-interval: 10000
    reduce-rtt: true

Outbound — TUIC v4 (legacy):

yaml
proxies:
  - name: tuic-v4
    type: tuic
    server: example.com
    port: 443
    token: <token>
    udp-relay-mode: native
    sni: example.com
    alpn: [h3]

Inbound under listeners:

yaml
listeners:
  - name: tuic-in
    type: tuic
    listen: 0.0.0.0
    port: 443
    users:
      a3482e88-686a-4a58-8126-99c9df64b7bf: <alice password>
      6f4e6c2c-8b1d-4b4f-8e2e-1f2b5c9e0a3b: <bob password>
    certificate: /etc/mihomo/server.crt
    private-key: /etc/mihomo/server.key
    congestion-controller: bbr
    alpn: [h3]

Top-level tuic-server (legacy shape):

yaml
tuic-server:
  enable: true
  listen: 0.0.0.0:443
  users:
    a3482e88-686a-4a58-8126-99c9df64b7bf: <password>
  certificate: /etc/mihomo/server.crt
  private-key: /etc/mihomo/server.key
  congestion-controller: bbr
  alpn: [h3]

Notes

  • mihomo's TUIC outbound exposes timeouts and intervals in milliseconds, not seconds — heartbeat-interval: 10000 is ten seconds. Both request-timeout and the inbound's authentication-timeout follow the same convention.
  • The five congestion controllers are wired in transport/tuic/common/congestion.go:20-53. bbr is the modern v2 implementation; bbr_meta_v1/bbr_meta_v2 exist purely for cross-compat with older clients.
  • cwnd: 0 is treated as 32 packets at runtime (transport/tuic/common/congestion.go:17-19).
  • The top-level tuic-server block can only declare one TUIC inbound. For multiple TUIC inbounds on different ports, use the listeners: form.

Cross-core notes

  • Xray-core does not support TUIC. See TUIC — Xray-core.
  • sing-box supports only TUIC v5 (no token form), uses snake_case field names (congestion_control, udp_relay_mode), accepts Go-style duration strings ("3s") for timeouts, and has a smaller set of congestion-control choices (cubic, new_reno, bbr). See TUIC — sing-box.

Source: adapter/outbound/tuic.go:34-69 · v1.19.27 (5184081)

Core Tutorial by Argsment