fatedier/frp · error
token in heartbeat doesn't match token from configuration
Error message
token in heartbeat doesn't match token from configuration
What it means
Identical static-token check to error 147 but applied to Ping messages, and only when 'HeartBeats' is included in the additional auth scopes (authentication.additionalAuthScopes). frps recomputes md5(token + ping.Timestamp) and compares with pingMsg.PrivilegeKey in constant time; mismatch rejects the heartbeat.
Source
Thrown at pkg/auth/token.go:77
newWorkConnMsg.Timestamp = time.Now().Unix()
newWorkConnMsg.PrivilegeKey = util.GetAuthKey(auth.token, newWorkConnMsg.Timestamp)
return nil
}
func (auth *TokenAuthSetterVerifier) VerifyLogin(m *msg.Login) error {
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
return fmt.Errorf("token in login doesn't match token from configuration")
}
return nil
}
func (auth *TokenAuthSetterVerifier) VerifyPing(m *msg.Ping) error {
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeHeartBeats) {
return nil
}
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
return fmt.Errorf("token in heartbeat doesn't match token from configuration")
}
return nil
}
func (auth *TokenAuthSetterVerifier) VerifyNewWorkConn(m *msg.NewWorkConn) error {
if !slices.Contains(auth.additionalAuthScopes, v1.AuthScopeNewWorkConns) {
return nil
}
if !util.ConstantTimeEqString(util.GetAuthKey(auth.token, m.Timestamp), m.PrivilegeKey) {
return fmt.Errorf("token in NewWorkConn doesn't match token from configuration")
}
return nil
}
View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Keep authentication.token identical on both sides and include HeartBeats in additionalAuthScopes on both
- If writing a custom client, set Ping.PrivilegeKey = md5(token || Ping.Timestamp) whenever the scope is enabled
- Restart both frpc and frps after any token change so no live session mixes old and new keys
Example fix
# before — scopes enabled only on frps # frps.toml authentication.additionalAuthScopes = ["HeartBeats"] # after — mirror on frpc # frpc.toml authentication.additionalAuthScopes = ["HeartBeats"]
Defensive patterns
Strategy: validation
Validate before calling
expected := md5hex(authToken + strconv.FormatInt(pingMsg.Timestamp, 10))
if expected != pingMsg.PrivilegeKey {
return errors.New("heartbeat auth failed; check token and additionalAuthScopes parity")
} Prevention
- Set additionalAuthScopes identically on frps and frpc
- Sign every message type you enable in the scopes when writing custom clients
- Restart both peers after token rotation
When it happens
Trigger: HeartBeats scope enabled on frps and the ping PrivilegeKey was computed with a different token than frps's authentication.token — typically the Login succeeded (tokens matched then) but a second frpc process, or an edited config, sends pings with another token; or a custom client forgot to authenticate pings when the scope is enabled.
Common situations: Enabling additionalAuthScopes=["HeartBeats"] on frps while a custom/SDK client only authenticates Login; token rotated on frpc but frps not restarted (or vice versa) mid-session.
Related errors
- token in login doesn't match token from configuration
- token in NewWorkConn doesn't match token from configuration
- couldn't generate OIDC token for login: %v
- invalid OIDC token in login: %v
- invalid OIDC token in ping: %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/4bff633dcc3d31e1.
Report an issue: GitHub.