fatedier/frp · error

start new visitor connection error: %s

Error message

start new visitor connection error: %s

What it means

The server answered the visitor's connection request with an explicit error string inside NewVisitorConnResp.Error. This is a server-side rejection, not a local failure: frps validated the request (proxy lookup, signature, negotiation) and refused it; the message string is the server's reason.

Source

Thrown at client/visitor/visitor.go:198

	}
	err = visitorConn.WriteMsg(newVisitorConnMsg)
	if err != nil {
		visitorConn.Close()
		return nil, fmt.Errorf("send newVisitorConnMsg to server error: %v", err)
	}

	_ = visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
	var newVisitorConnRespMsg msg.NewVisitorConnResp
	err = visitorConn.ReadMsgInto(&newVisitorConnRespMsg)
	if err != nil {
		visitorConn.Close()
		return nil, fmt.Errorf("read newVisitorConnRespMsg error: %v", err)
	}
	_ = visitorConn.SetReadDeadline(time.Time{})

	if newVisitorConnRespMsg.Error != "" {
		visitorConn.Close()
		return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
	}
	return visitorConn, nil
}

func wrapVisitorConn(conn io.ReadWriteCloser, cfg *v1.VisitorBaseConfig) (io.ReadWriteCloser, func(), error) {
	rwc := conn
	if cfg.Transport.UseEncryption {
		var err error
		rwc, err = libio.WithEncryption(rwc, []byte(cfg.SecretKey))
		if err != nil {
			return nil, func() {}, fmt.Errorf("create encryption stream error: %v", err)
		}
	}
	recycleFn := func() {}
	if cfg.Transport.UseCompression {
		rwc, recycleFn = libio.WithCompressionFromPool(rwc)
	}
	return rwc, recycleFn, nil

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Compare the visitor's serverName with the frpc proxy name it targets; they must match exactly.
  2. Verify the same secretKey (and auth token/user) is configured on the visitor, the proxy owner, and frps.
  3. Sync clocks via NTP on client and server so the auth timestamp stays within tolerance.
  4. In multi-user setups, check that the user/serverUser prefix logic produces the proxy name the server actually registered.

Example fix

# frpc visitor config (TOML)
# before
[[visitors]]
name = "ssh-v"
type = "stcp"
serverName = "ssh-wrong"
secretKey = "abc"

# after
[[visitors]]
name = "ssh-v"
type = "stcp"
serverName = "ssh"   # must equal the owner's proxy name
secretKey = "abc"    # must equal the proxy's secretKey
Defensive patterns

Strategy: validation

Validate before calling

// Before starting the visitor, sanity-check the name it will target
base := visitorCfg.GetBaseConfig()
target := naming.BuildTargetServerProxyName(user, base.ServerUser, base.ServerName)
_ = target // cross-check against the registered proxy names of the owner client

Try / catch

if _, err := v.newVisitorConn(ctx); err != nil {
    if strings.Contains(err.Error(), "start new visitor connection error") {
        // server rejection: config problem — do NOT retry blindly; fix name/key
        return err
    }
}

Prevention

When it happens

Trigger: Target proxy name not found on the server (the name built from user/serverUser/serverName has no matching stcp/xtcp proxy); SignKey mismatch because the visitor's secretKey differs from the server; timestamp skew beyond the allowed window; the target proxy does not accept visitors.

Common situations: Visitor's serverName does not match the proxy name of the serving frpc; visitor and server disagree on secretKey/auth token; client and server clocks drift so the timestamped signature is rejected; multi-user prefixes (user/serverUser) produce a name that differs from the registered proxy name.

Related errors


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