Skip to content

TLS — Xray-core

Xray's TLS settings live under streamSettings.tlsSettings when streamSettings.security is "tls". The struct also covers ECH and the uTLS fingerprint family. For REALITY, see REALITY — that's a parallel streamSettings.security value with its own block.

TLS settings

FieldTypeDefaultAllowed valuesDescription
allowInsecureboolfalsetrue | falseSkip server-certificate verification. Use only for testing — production configs should never set this.
certificates[]*TLSCertConfig[][TLSCertConfig]Server certificates (inbound) or trusted-CA pinning (outbound). Multiple entries enable SNI-based selection on inbound.
serverNamestring(inferred)<hostname>Expected server name. On outbound, the SNI sent and the value verified against the server's leaf cert. On inbound, the SNI used when ACME provisions a certificate.
alpn*StringList["h2", "http/1.1"]<ALPN string>Application-layer protocol negotiation list, offered to the peer.
enableSessionResumptionboolfalsetrue | falseEnable TLS session-ticket resumption (client side).
disableSystemRootboolfalsetrue | falseIgnore the OS root-CA bundle. When true, only `certificates` entries (in `verify` mode) are accepted.
minVersionstring1.21.0 | 1.1 | 1.2 | 1.3Minimum acceptable TLS version.
maxVersionstring1.31.0 | 1.1 | 1.2 | 1.3Maximum acceptable TLS version.
cipherSuitesstring(library default)<comma-separated cipher list>Override the cipher suite list. Uses OpenSSL-style names (e.g. `TLS_AES_128_GCM_SHA256:...`). Note: ignored for TLS 1.3 — only 1.2 cipher choice is configurable.
fingerprintstring(unset)chrome | firefox | safari | edge | 360 | qq | ios | android | random | randomizeduTLS client-hello fingerprint. Drives the entire ClientHello shape (cipher suites, extensions, signature algorithms) — overrides the explicit `cipherSuites` field.
rejectUnknownSniboolfalsetrue | falseInbound — reject TLS connections whose SNI doesn't match any of the configured certificates.
curvePreferences*StringList(library default)X25519 | P-256 | P-384 | P-521 | X25519MLKEM768Override the key-exchange curve preference list. Order matters.
masterKeyLogstring(unset)<file path>SSLKEYLOGFILE-style path for capturing TLS keys (Wireshark decryption). Don't enable on production.
pinnedPeerCertSha256string(unset)<base64 SHA-256>Pin the peer's certificate. Outbound only — connection is rejected if the leaf certificate's SHA-256 doesn't match.
verifyPeerCertByNamestring(unset)<hostname>When set, verify the peer cert's Subject CN/SAN matches this name instead of `serverName`. Useful when the SNI differs from the canonical certificate name (REALITY-style setups).
verifyPeerCertInNames[]string[][<hostname>]List form of `verifyPeerCertByName` — any name in the list is acceptable.
echServerKeysstring(unset)<base64 ECHConfigList>Inbound — ECH server key set.
echConfigListstring(unset)<base64 ECHConfigList>Outbound — pinned ECH config list. When unset, ECH is auto-discovered via HTTPS-record DNS lookups.
echForceQuerystring(unset)hkdf | dnsForce a particular ECH discovery mechanism. Empty falls back to the default cascade.
echSockopt*SocketConfig(unset)SocketConfigSocket options applied to the ECH-discovery DNS request.

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

certificates[]

FieldTypeDefaultAllowed valuesDescription
certificateFilestring(unset)<PEM file path>Path to a certificate PEM file. Mutually exclusive with `certificate`.
certificate[]string(unset)[<PEM line>]Inline certificate PEM as a string array (one element per line, or the whole PEM as one element).
keyFilestring(unset)<PEM file path>Path to the private-key PEM file.
key[]string(unset)[<PEM line>]Inline private-key PEM.
usagestringenciphermentencipherment | verify | issueRole of this certificate: `encipherment` (server cert), `verify` (trusted CA for peer verification), `issue` (CA used to issue per-connection certs in some forwarding modes).
ocspStaplinguint643600<seconds>How often to refresh the OCSP staple. 0 disables.
oneTimeLoadingboolfalsetrue | falseRead the cert and key once at startup instead of every reload. Auto-set to true when using inline `certificate`/`key` (no path to reload from).
buildChainboolfalsetrue | falseAuto-fetch intermediate CAs to build a complete chain if the supplied PEM has only the leaf.

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

Examples

Outbound — verify against the system root store, force TLS 1.3, pin a chrome fingerprint:

json
{
  "streamSettings": {
    "security": "tls",
    "tlsSettings": {
      "serverName": "example.com",
      "alpn": ["h2", "http/1.1"],
      "minVersion": "1.3",
      "maxVersion": "1.3",
      "fingerprint": "chrome"
    }
  }
}

Inbound — serve a Let's Encrypt cert:

json
{
  "streamSettings": {
    "security": "tls",
    "tlsSettings": {
      "serverName": "example.com",
      "certificates": [
        {
          "certificateFile": "/etc/ssl/cert.pem",
          "keyFile": "/etc/ssl/key.pem",
          "usage": "encipherment",
          "ocspStapling": 3600
        }
      ],
      "rejectUnknownSni": true
    }
  }
}

Pinned-cert outbound (lock to a specific SHA-256):

json
{
  "streamSettings": {
    "security": "tls",
    "tlsSettings": {
      "serverName": "example.com",
      "pinnedPeerCertSha256": "<base64-SHA256-of-leaf-cert>"
    }
  }
}

Notes

  • fingerprint is uTLS — it rewrites the entire ClientHello to mimic the chosen browser. When set, cipherSuites, curvePreferences, and minVersion/maxVersion are mostly ignored because uTLS controls those itself.
  • TLS-1.3 ignores cipherSuites per the Go standard library — only TLS 1.2 cipher choice is configurable. Use curvePreferences to influence the key-exchange choice in TLS 1.3.
  • The certificates[].usage field is critical:
    • encipherment — typical server certificate.
    • verify — used as a trusted CA root on the outbound side. Pair with disableSystemRoot: true to ignore the OS bundle.
    • issue — used to issue per-connection certs in some forwarding modes (MITM HTTPS pass-through).
  • verifyPeerCertByName decouples SNI from the verified name — useful in REALITY-style setups where the SNI is a public site but the actual certificate is for the Xray server.
  • masterKeyLog is a debugging aid; the file format matches the SSLKEYLOGFILE specification that Wireshark consumes. Never enable on production.

Cross-core notes

  • sing-box uses a single tls: { ... } block embedded on every inbound and outbound. Field names are snake_case (server_name, cipher_suites), and REALITY / ECH / uTLS live as nested sub-blocks. See TLS — sing-box.
  • mihomo sprinkles TLS fields directly onto each proxy adapter — tls, sni, alpn, skip-cert-verify, etc. There is no consolidated TLS block. See TLS — mihomo.

Source: infra/conf/transport_internet.go:569-659 · v26.6.1 (94ffd50)

Core Tutorial by Argsment