XTLS/Xray-core · error
Failed to build Hysteria config.
Error message
Failed to build Hysteria config.
What it means
Thrown while building the top-level streamSettings when the 'hysteria' transport block is present but HysteriaSettings.Build() fails. This is a wrapper error: the real cause is chained via .Base(err) and comes from the Hysteria transport config builder itself (e.g. invalid bandwidth strings or missing required fields). It aborts config compilation before any network activity starts.
Source
Thrown at infra/conf/transport_internet.go:188
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "websocket",
Settings: serial.ToTypedMessage(ts),
})
}
if c.HTTPUPGRADESettings != nil {
hs, err := c.HTTPUPGRADESettings.Build()
if err != nil {
return nil, errors.New("Failed to build HTTPUpgrade config.").Base(err)
}
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "httpupgrade",
Settings: serial.ToTypedMessage(hs),
})
}
if c.HysteriaSettings != nil {
hs, err := c.HysteriaSettings.Build()
if err != nil {
return nil, errors.New("Failed to build Hysteria config.").Base(err)
}
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "hysteria",
Settings: serial.ToTypedMessage(hs),
})
}
if c.SocketSettings != nil {
ss, err := c.SocketSettings.Build()
if err != nil {
return nil, errors.New("Failed to build sockopt.").Base(err)
}
config.SocketSettings = ss
}
if c.FinalMask != nil {
for _, mask := range c.FinalMask.Tcp {
u, err := mask.Build(true)
if err != nil {View on GitHub (pinned to 7d214f8b09)
Solutions
- Read the chained cause after 'Failed to build Hysteria config.': the Base(err) text names the exact invalid field; fix that field.
- Compare your hysteria transportSettings keys against the HysteriaSettings struct in infra/conf (field names and units).
- Regenerate/validate the config with `xray run -test -c config.json` (or equivalent) before deploying.
Example fix
// before
"transportSettings": { "brutalUp": "100mbps" } // malformed/unsupported value
// after
"transportSettings": { "brutalUp": "100 mbps" } // correct syntax per Bandwidth parser Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate the hysteria block shape before Build()
func hysteriaSettingsLookSane(h map[string]any) bool {
if h == nil { return true } // absent is fine
for _, k := range []string{"brutalUp", "brutalDown"} {
if v, ok := h[k]; ok && v == nil { return false }
}
return true
} Type guard
func isHysteriaBlockPresent(s map[string]any) bool {
ts, ok := s["transportSettings"].(map[string]any)
if !ok { return false }
_, present := ts["hysteria"]
return present
} Try / catch
cfg, err := jsonToStreamSettings(raw)
if err != nil {
if strings.Contains(err.Error(), "Failed to build Hysteria config.") {
// unwrap errors.Unwrap(err) to reach the field-level cause and report it
}
return fmt.Errorf("config build failed: %w", err)
} Prevention
- Run `xray run -test -c config.json` in CI for every config change.
- Keep hysteria transportSettings schema in sync with the exact binary version deployed.
- Log the full error chain (use %w / errors.Unwrap), not just the top-level message.
When it happens
Trigger: A JSON config with "streamSettings": {"transport": "hysteria", "transportSettings": {...}} (parsed into HysteriaSettings) whose inner Build() returns an error, e.g. a malformed BrutalUp/BrutalDown bandwidth value or another invalid sub-field.
Common situations: Copying a Hysteria block from a different fork/version whose schema differs; typoing a bandwidth string ("100mbps" vs "100 mbps"); enabling hysteria transport without required parameters.
Related errors
- bridge tag is empty
- bridge domain is empty
- portal tag is empty
- portal domain is empty
- unknown action: {}
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/204c4a1ffa247ca2.
Report an issue: GitHub.