Skip to content

Hysteria2 — Xray-core

Xray-core supports Hysteria v2 but splits the config across two blocks: the protocol-level settings (version, address/port, users) and the transport-level streamSettings.hysteriaSettings (authentication, bandwidth, port-hopping, masquerade). Both must be filled in for the outbound to be usable.

Outbound — protocol layer

settings for an outbound of "protocol": "hysteria":

FieldTypeDefaultAllowed valuesDescription
versionint32(required)2Must be exactly 2. Any other value is rejected at startup (`infra/conf/hysteria.go:19-21`).
address*Address(required)<host>Server hostname or IP.
portuint16(required)<port>Server UDP port.

Source: infra/conf/hysteria.go:13-17 · pinned at v26.6.1 (94ffd50)

Hysteria v1 is unsupported

The version field must equal 2. HysteriaClientConfig.Build (infra/conf/hysteria.go:19-21) returns errors.New("version != 2") for anything else.

Inbound — protocol layer

settings for an inbound of "protocol": "hysteria":

FieldTypeDefaultAllowed valuesDescription
versionint32(required)2Must be 2.
users[]*HysteriaUserConfig[][HysteriaUserConfig]Accepted users. `users` is the newer name and is accepted alongside `clients`.
clients[]*HysteriaUserConfig[][HysteriaUserConfig]Accepted users (legacy name; same shape as `users`).

Source: infra/conf/hysteria.go:40-44 · pinned at v26.6.1 (94ffd50)

clients[]

FieldTypeDefaultAllowed valuesDescription
authstring(required)<string>Authentication string.
leveluint320<uint32>Policy level for this user.
emailstring(unset)<string>Tag in stats/logs.

Source: infra/conf/hysteria.go:34-38 · pinned at v26.6.1 (94ffd50)

Transport layer — hysteriaSettings

Set under streamSettings.hysteriaSettings. Carries every operational knob the protocol layer omits.

FieldTypeDefaultAllowed valuesDescription
versionint32(required)2Hysteria protocol version. Must match the `version` in `settings`.
authstring(required on outbound)<string>Outbound auth string. On the inbound this field is forwarded to the validator but per-user auth lives in `settings.clients[].auth`.
congestion*string(unset)bbr | cubic | renoServer-side hint for congestion-control algorithm. Newer builds move this to QUIC params — a warning is printed if you set it here.
up*Bandwidth(unset)<bandwidth>Estimated uplink bandwidth (string with unit: `1Mbps`, `100kbps`, `50mbps`).
down*Bandwidth(unset)<bandwidth>Estimated downlink bandwidth.
udphop*UdpHop(unset)UdpHopUDP port-hopping configuration for the outbound.
udpIdleTimeoutint6460<2..600 seconds>Seconds of UDP-stream idleness before the QUIC stream is closed. Must be between 2 and 600 inclusive (`infra/conf/transport_internet.go:544-546`).
masqueradeMasquerade(unset)MasqueradeInbound-only: HTTP-response masquerade for unauthenticated traffic.

Source: infra/conf/transport_internet.go:513-524 · pinned at v26.6.1 (94ffd50)

udphop

FieldTypeDefaultAllowed valuesDescription
portsPortList(required)<JSON array or string>Port list to hop across. Accepts JSON arrays of ports/ranges or a string like `"100,200-210,400"`.
intervalInt32Range(unset)Int32RangeRange (`{From, To}`) controlling how often to switch ports, in seconds.

Source: infra/conf/transport_internet.go:494-497 · pinned at v26.6.1 (94ffd50)

masquerade

FieldTypeDefaultAllowed valuesDescription
typestring(required)file | proxy | stringSelects which sub-block applies.
dirstring(file only)<dir path>Directory served when `type: file`.
urlstring(proxy only)<URL>Upstream URL when `type: proxy`.
rewriteHostboolfalsetrue | falseRewrite the Host header when proxying (`type: proxy`).
insecureboolfalsetrue | falseSkip TLS verification on the upstream when `type: proxy`.
contentstring(string only)<text>Body returned when `type: string`.
headersmap[string]string{}{<header>: <value>}Extra response headers when `type: string`.
statusCodeint32200<int>Status code returned when `type: string`.

Source: infra/conf/transport_internet.go:499-511 · pinned at v26.6.1 (94ffd50)

The type field switches the active sub-block: file uses dir, proxy uses url/rewriteHost/insecure, string uses content/ headers/statusCode.

Bandwidth syntax

up and down are parsed by the helper in infra/conf/transport_internet.go:478-501. Accepted suffixes:

SuffixMultiplier
(empty), b, bps1
k, kb, kbps1024
m, mb, mbps1 048 576
g, gb, gbps1 073 741 824
t, tb, tbps1 099 511 627 776

The numeric part is parsed as a float64 and the result divided by 8 (bytes-per-second is what the protobuf carries, but the source unit name is bps).

Examples

Outbound:

json
{
  "outbounds": [
    {
      "tag": "hy2-out",
      "protocol": "hysteria",
      "settings": {
        "version": 2,
        "address": "example.com",
        "port": 443
      },
      "streamSettings": {
        "network": "hysteria",
        "security": "tls",
        "tlsSettings": { "serverName": "example.com" },
        "hysteriaSettings": {
          "version": 2,
          "auth": "<password>",
          "up": "100mbps",
          "down": "300mbps",
          "udpIdleTimeout": 120
        }
      }
    }
  ]
}

Inbound with two users and an HTTP-file masquerade:

json
{
  "inbounds": [
    {
      "tag": "hy2-in",
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "hysteria",
      "settings": {
        "version": 2,
        "clients": [
          { "auth": "<alice>", "email": "alice" },
          { "auth": "<bob>",   "email": "bob"   }
        ]
      },
      "streamSettings": {
        "network": "hysteria",
        "security": "tls",
        "tlsSettings": { "certificates": [{ "certificateFile": "/etc/ssl/cert.pem", "keyFile": "/etc/ssl/key.pem" }] },
        "hysteriaSettings": {
          "version": 2,
          "masquerade": {
            "type": "file",
            "dir": "/var/www"
          }
        }
      }
    }
  ]
}

Notes

  • A common gotcha: setting auth only inside settings (as if it were a username/password field). Xray reads outbound auth from streamSettings.hysteriaSettings.auth. Inbound users use settings.clients[].auth (per-user) and ignore the transport-level auth for matching.
  • congestion, up, down, and udphop will be moved into the new finalmask/quicParams blocks in a future release. A warning is printed if these are set on the legacy location (infra/conf/transport_internet.go:540-542).
  • Hysteria v1 is fully removed from Xray-core. The "v1" name in the source is now just a historical label — version != 2 is a hard failure.
  • udpIdleTimeout < 2 or > 600 triggers a startup error (infra/conf/transport_internet.go:544-546).

Cross-core notes

  • sing-box uses a single, much flatter block — no transport split. Bandwidth is in plain int Mbps (no unit string), and masquerade supports a polymorphic shape (string URL or typed object). See Hysteria2 — sing-box.
  • mihomo is also single-block, with port hopping driven by ports (range syntax) plus hop-interval. mihomo accepts unit-suffixed strings for up/down like Xray. See Hysteria2 — mihomo.

Source: infra/conf/hysteria.go:13-44 · v26.6.1 (94ffd50)

Core Tutorial by Argsment