fatedier/frp · error
invalid transport.heartbeatTimeout, heartbeat timeout should
Error message
invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval
What it means
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.
Source
Thrown at pkg/config/v1/validation/client.go:151
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
errs = AppendError(errs, err)
}
}
if err := c.TokenSource.Validate(); err != nil {
errs = AppendError(errs, fmt.Errorf("invalid auth.oidc.tokenSource: %v", err))
}
return errs
}
func validateTransportConfig(c *v1.ClientTransportConfig) (Warning, error) {
var (
warnings Warning
errs error
)
if c.HeartbeatTimeout > 0 && c.HeartbeatInterval > 0 {
if c.HeartbeatTimeout < c.HeartbeatInterval {
errs = AppendError(errs, fmt.Errorf("invalid transport.heartbeatTimeout, heartbeat timeout should not less than heartbeat interval"))
}
}
if !lo.FromPtr(c.TLS.Enable) {
checkTLSConfig := func(name string, value string) Warning {
if value != "" {
return fmt.Errorf("%s is invalid when transport.tls.enable is false", name)
}
return nil
}
warnings = AppendError(warnings, checkTLSConfig("transport.tls.certFile", c.TLS.CertFile))
warnings = AppendError(warnings, checkTLSConfig("transport.tls.keyFile", c.TLS.KeyFile))
warnings = AppendError(warnings, checkTLSConfig("transport.tls.trustedCaFile", c.TLS.TrustedCaFile))
}
if !slices.Contains(SupportedTransportProtocols, c.Protocol) {
errs = AppendError(errs, fmt.Errorf("invalid transport.protocol, optional values are %v", SupportedTransportProtocols))View on GitHub (pinned to 6c8a8d0a97)
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
Example fix
# before [transport] heartbeatInterval = 90 heartbeatTimeout = 30 # after [transport] heartbeatInterval = 30 heartbeatTimeout = 90
Defensive patterns
Strategy: validation
Validate before calling
func heartbeatOK(t, i int) bool {
return t <= 0 || i <= 0 || t >= i
} Prevention
- Set timeout as a multiple of interval (2-3x) by convention
- Centralize heartbeat tuning in one template variable pair
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- %s is invalid when transport.tls.enable is false
- invalid transport.protocol, optional values are %v
- invalid transport.wireProtocol, optional values are %v
- exec configuration is required when type is 'exec'
- file path cannot be empty
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/b6db2905aa637ae3.
Report an issue: GitHub.