fatedier/frp · error

client_id [%s] for user [%s] is already online

Error message

client_id [%s] for user [%s] is already online

What it means

ControlManager.Activate calls ClientRegistry.RegisterWithControlID with the login message's User and ClientID; when the registry already holds an online entry for that (user, clientID) pair from a different run, it reports a conflict and activation fails. This is frps enforcing one online session per client identity.

Source

Thrown at server/control.go:213

	}

	loginMsg := ctl.sessionCtx.LoginMsg
	remoteAddr := ctl.sessionCtx.Conn.RemoteAddr().String()
	if host, _, err := net.SplitHostPort(remoteAddr); err == nil {
		remoteAddr = host
	}
	_, conflict := cm.registry.RegisterWithControlID(
		loginMsg.User,
		loginMsg.ClientID,
		ctl.runID,
		loginMsg.Hostname,
		loginMsg.Version,
		remoteAddr,
		ctl.sessionCtx.WireProtocol,
		uint64(entry.id),
	)
	if conflict {
		return true, fmt.Errorf("client_id [%s] for user [%s] is already online", loginMsg.ClientID, loginMsg.User)
	}

	entry.registryOnline = true
	entry.registryControlID = entry.id
	ctl.activated = true
	return true, nil
}

// completeLogin reserves ctl's current ownership with its run gate while the
// bounded successful LoginResp write runs, then transitions it to running.
// The callback must only perform that bounded write; it must not call back into
// the control manager or the same control lifecycle.
func (cm *ControlManager) completeLogin(ctl *Control, writeSuccess func() error) (bool, error) {
	entry, ok := cm.lockCurrentRun(ctl.runID, false)
	if !ok {
		return false, nil
	}
	defer entry.runMu.Unlock()

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Ensure each frpc instance has a unique client ID in its config (auth or common section), or omit the client id so it is derived uniquely.
  2. If it is a stale session, let the old connection time out (heartbeat timeout) or restart the conflicting frpc; the registry slot frees once the old control closes.
  3. Check the frps dashboard/API for the online client holding that ID and kill that session if it is truly dead.
  4. If duplicate identities are expected in your deployment, put them under different users.

Example fix

# frpc.toml — before (two instances share an id)
clientID = "box-1"

# after — unique per instance
clientID = "box-1-a"
Defensive patterns

Strategy: validation

Validate before calling

// server-side pre-check before login completes
if _, online := registry.GetByID(user, loginMsg.ClientID); online {
    return fmt.Errorf("client %s already online; pick a unique clientID", loginMsg.ClientID)
}

Try / catch

if _, err := cm.Activate(ctl); err != nil {
    if strings.Contains(err.Error(), "already online") {
        // surface actionable message to the client/operator, terminate session
    }
}

Prevention

When it happens

Trigger: A second frpc process (or a reconnecting frpc whose old TCP session is still registered) logs in with the same user + client ID while the previous session has not yet been cleaned up. RegisterWithControlID returns conflict=true and Activate returns this error, which typically terminates the new login.

Common situations: Running two frpc instances with a copied config (same client id) against the same server; a stale/half-dead TCP connection from a network drop whose server-side cleanup has not completed; NAT rebinding or quick reconnects after connectivity loss.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/4c44de4d7349733e. Report an issue: GitHub.