Environment
- Panel / backend: 2.8.0
- Node: 2.8.0, core: rw-core / Xray-core 26.6.27
- Client tested: Happ (bundled core)
What we’d like
When a Hysteria2 inbound uses an inline self-signed certificate and the host has pinnedPeerCertSha256 set, please add the certificate pin to the generated subscription so that strict clients can validate the connection without enabling insecure.
Per the Hysteria2 URI scheme, the server certificate pin is passed as pinSHA256=<hex-sha256>. Today the base64 subscription (XRAY_BASE64) omits this parameter, so the pin configured on the host never reaches the client.
Current output (base64 subscription)
hysteria2://<uuid>@<ip>:443/?sni=www.twinmotion.com&fm=%7B%22quicParams%22%3A%7B%22debug%22%3Afalse%2C%22congestion%22%3A%22bbr%22%7D%7D#<name>
→ no pinSHA256, no insecure → strict clients can’t validate the self-signed cert.
Desired output (verified to work in Happ — cert accepted, no insecure)
hysteria2://<uuid>@<ip>:443/?security=tls&sni=www.twinmotion.com&fm=...&insecure=0&pinSHA256=<HEX_SHA256_OF_LEAF_CERT>#<name>
i.e. hex-encode the host’s pinnedPeerCertSha256 and add it as pinSHA256 (hex, per the Hysteria2 URI spec). This lets self-signed Hysteria2 work out of the box, no manual insecure.
Note on XRAY_JSON (encoding mismatch to consider)
With serveJsonAtBaseSubscription=true, XRAY_JSON emits tlsSettings.pinnedPeerCertSha256 in base64 (e.g. hLzIuDGgIzwCNP1YbnFz14PXIYwR966VK6PEkZCD/lE=). Happ’s bundled Xray core hex-decodes this field and fails to build the TLS config:
infra/conf: Failed to build TLS config. > encoding/hex: invalid byte: U+0068 'h'
Encoding differs by context: Xray-core v26.1.23 uses base64 for pinnedPeerCertSha256 in xray-json tlsSettings, while the Hysteria2 URI spec uses hex for pinSHA256. It would help to emit the correct encoding per output format (hex for the URI).
Steps to reproduce the current behavior
- Hysteria2 inbound with inline self-signed cert (e.g. CN=www.twinmotion.com),
alpn: ["h3"], congestion bbr. - Host with
pinnedPeerCertSha256(base64) set,securityLayer=TLS, sni, alpn h3. - Add the inbound to a squad; assign to a user.
- Fetch the base64 subscription (client UA +
x-hwid), base64-decode → thehysteria2://line has nopinSHA256.
Why it matters
Self-signed Hysteria2 is a stateless, domain-free setup (no ACME needed). Emitting the pin the client already trusts would make it usable in strict clients (e.g. Happ) without insecure — the pin is already configured on the host and only needs to be serialized into the link.
