{"record":{"id":"b6db2905aa637ae3","repo":"fatedier/frp","slug":"invalid-transport-heartbeattimeout-heartbeat-time","errorCode":null,"errorMessage":"invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval","messagePattern":"invalid transport\\.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/validation/client.go","lineNumber":151,"sourceCode":"\t\tif err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {\n\t\t\terrs = AppendError(errs, err)\n\t\t}\n\t}\n\tif err := c.TokenSource.Validate(); err != nil {\n\t\terrs = AppendError(errs, fmt.Errorf(\"invalid auth.oidc.tokenSource: %v\", err))\n\t}\n\treturn errs\n}\n\nfunc validateTransportConfig(c *v1.ClientTransportConfig) (Warning, error) {\n\tvar (\n\t\twarnings Warning\n\t\terrs     error\n\t)\n\n\tif c.HeartbeatTimeout > 0 && c.HeartbeatInterval > 0 {\n\t\tif c.HeartbeatTimeout < c.HeartbeatInterval {\n\t\t\terrs = AppendError(errs, fmt.Errorf(\"invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval\"))\n\t\t}\n\t}\n\n\tif !lo.FromPtr(c.TLS.Enable) {\n\t\tcheckTLSConfig := func(name string, value string) Warning {\n\t\t\tif value != \"\" {\n\t\t\t\treturn fmt.Errorf(\"%s is invalid when transport.tls.enable is false\", name)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\twarnings = AppendError(warnings, checkTLSConfig(\"transport.tls.certFile\", c.TLS.CertFile))\n\t\twarnings = AppendError(warnings, checkTLSConfig(\"transport.tls.keyFile\", c.TLS.KeyFile))\n\t\twarnings = AppendError(warnings, checkTLSConfig(\"transport.tls.trustedCaFile\", c.TLS.TrustedCaFile))\n\t}\n\n\tif !slices.Contains(SupportedTransportProtocols, c.Protocol) {\n\t\terrs = AppendError(errs, fmt.Errorf(\"invalid transport.protocol, optional values are %v\", SupportedTransportProtocols))","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/validation/client.go#L133-L169","documentation":"frpc rejects a transport config where heartbeatTimeout is strictly less than heartbeatInterval (both explicitly set > 0). A timeout shorter than the interval means the client would time out before the next heartbeat is even sent, guaranteeing spurious disconnects.","triggerScenarios":"transport.heartbeatTimeout = 30 and transport.heartbeatInterval = 90 (units: seconds/milliseconds per config docs) — any positive pair with timeout < interval triggers it. Only fires when both are > 0.","commonSituations":"Tuning heartbeat values independently while debugging NAT keepalives; unit confusion (seconds vs ms) making the interval unexpectedly large; copying interval from one config and timeout from another.","solutions":["Set transport.heartbeatTimeout >= transport.heartbeatInterval (e.g. interval 30, timeout 90)","Or delete one of the two keys so the default ratio applies","Double-check units — frp durations in TOML config are seconds unless specified"],"exampleFix":"# before\n[transport]\nheartbeatInterval = 90\nheartbeatTimeout = 30\n\n# after\n[transport]\nheartbeatInterval = 30\nheartbeatTimeout = 90","handlingStrategy":"validation","validationCode":"func heartbeatOK(t, i int) bool {\n    return t <= 0 || i <= 0 || t >= i\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set timeout as a multiple of interval (2-3x) by convention","Centralize heartbeat tuning in one template variable pair"],"tags":["frp","config","transport","heartbeat","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}