fatedier/frp · error

wait response from stun server timeout

Error message

wait response from stun server timeout

What it means

Returned by doSTUNRequest in pkg/nathole/discovery.go when the STUN binding transaction fails with a network error whose Timeout() is true — i.e. the read deadline set on the connection (responseTimeout) expired before the STUN server answered. It is the discovery layer's way of saying a STUN server did not respond in time.

Source

Thrown at pkg/nathole/discovery.go:105

}

func (c *discoverConn) doSTUNRequest(addr string) (*stunResponse, error) {
	serverAddr, err := net.ResolveUDPAddr("udp4", addr)
	if err != nil {
		return nil, err
	}
	transaction, err := stun.NewBindingTransaction(serverAddr)
	if err != nil {
		return nil, err
	}
	if err := c.conn.SetReadDeadline(time.Now().Add(responseTimeout)); err != nil {
		return nil, err
	}
	response, err := c.client.Do(transaction)
	if err != nil {
		var netErr net.Error
		if errors.As(err, &netErr) && netErr.Timeout() {
			return nil, fmt.Errorf("wait response from stun server timeout")
		}
		return nil, err
	}

	resp := &stunResponse{}
	if response.MappedAddr != nil {
		resp.externalAddr = response.MappedAddr.String()
	}
	if response.OtherAddr != nil {
		resp.otherAddr = response.OtherAddr.String()
	}
	return resp, nil
}

func (c *discoverConn) discoverFromStunServer(addr string) ([]string, error) {
	resp, err := c.doSTUNRequest(addr)
	if err != nil {
		return nil, err

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Test the STUN server independently (e.g. with a STUN CLI) and replace dead entries with known-good servers
  2. Configure several STUN servers so discovery can fall back to the next one
  3. Retry discovery after a short delay — UDP loss is often transient
  4. If all UDP is blocked by the network, NAT hole punching cannot work; use a relayed proxy type instead

Example fix

# before (frps.toml)
natholeSTUNServer = ["stun.example.invalid:3478"]

# after
natHoleSTUNServer = ["stun.easyvoip.com:3478", "stun.l.google.com:19302"]
Defensive patterns

Strategy: retry

Try / catch

addrs, localAddr, err := nathole.Discover(servers, "")
if err != nil {
    if strings.Contains(err.Error(), "stun server timeout") {
        // rotate to the next STUN server or retry after a pause; persistent failure means UDP is blocked
    }
}

Prevention

When it happens

Trigger: discoverConn.doSTUNRequest(addr) with conn.SetReadDeadline(now + responseTimeout), then client.Do(transaction) times out. Causes: STUN server down or slow, UDP packets dropped by a firewall/NAT, wrong server address, or an extremely congested link.

Common situations: Dead or blocked STUN server entries in natHoleSTUNServer/natHoleAnalysisSTUNServer; corporate/campus firewalls dropping outbound UDP; DNS resolving a STUN hostname to an unreachable IP; mobile networks throttling UDP.

Understand the failure class

Related errors


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