fatedier/frp · error

wait proxy status ready timeout

Error message

wait proxy status ready timeout

What it means

waitProxyStatusReady polls statusExporter every 100ms until the virtual client's proxy reaches ProxyPhaseRunning, fails (ProxyPhaseStartErr/ProxyPhaseClosed), or the caller's timeout expires. This error means the proxy was created but never reported Running within the timeout — typically because the server never finished bringing the proxy up (bind failure, port conflict, slower proxy negotiation).

Source

Thrown at pkg/ssh/server.go:401

	defer timer.Stop()

	statusExporter := s.vc.Service().StatusExporter()

	for {
		select {
		case <-ticker.C:
			ps, ok := statusExporter.GetProxyStatus(name)
			if !ok {
				continue
			}
			switch ps.Phase {
			case proxy.ProxyPhaseRunning:
				return ps, nil
			case proxy.ProxyPhaseStartErr, proxy.ProxyPhaseClosed:
				return ps, errors.New(ps.Err)
			}
		case <-timer.C:
			return nil, fmt.Errorf("wait proxy status ready timeout")
		case <-s.doneCh:
			return nil, fmt.Errorf("ssh tunnel server closed")
		}
	}
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Check frps logs for a proxy start error — often a port bind conflict (remotePort already bound) which will also surface as ProxyPhaseStartErr once observed.
  2. Choose a different --remotePort for the ssh tunnel command.
  3. Retry once; races during frps proxy registration can exceed a short timeout.
  4. If you call waitProxyStatusReady from code, pass a timeout generous enough for proxy creation (several seconds).

Example fix

# before — two clients share port 6000
ssh v0@frps "tcp 127.0.0.1:22 --remotePort 6000"

# after — unique ports per tunnel
ssh v0@frps "tcp 127.0.0.1:22 --remotePort 6001"
Defensive patterns

Strategy: retry

Validate before calling

# caller-side: pick a free remote port before requesting the tunnel
# (avoids bind conflicts that stall the proxy in a non-Running phase)
python3 - <<'EOF'
import socket
s = socket.socket(); s.bind(('', 0))
print("--remotePort", s.getsockname()[1]); s.close()
EOF

Try / catch

if err != nil && strings.Contains(err.Error(), "wait proxy status ready timeout") {
    time.Sleep(2 * time.Second)
    // re-check proxy status once via statusExporter; only then report failure
}

Prevention

When it happens

Trigger: Remote port already in use so frps takes long to report the failure; large numbers of proxies making status propagation slow; timeout passed by the caller too small; proxy stuck between phases due to a stalled control connection.

Common situations: Two SSH tunnels grabbing the same remotePort; frps host with the port firewalled or already bound; slow links where the new-proxy workflow exceeds the wait budget.

Understand the failure class

Related errors


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