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, errView on GitHub (pinned to 6c8a8d0a97)
Solutions
- Test the STUN server independently (e.g. with a STUN CLI) and replace dead entries with known-good servers
- Configure several STUN servers so discovery can fall back to the next one
- Retry discovery after a short delay — UDP loss is often transient
- 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
- List several STUN servers so one timeout is survivable
- Probe STUN reachability before enabling xtcp on restricted networks
- Remember xtcp fundamentally requires outbound UDP
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- no external address found
- get natHoleRespMsg error: %v
- discover error: %v
- not enough addresses
- classify client nat feature error: %v
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/0acdc8fb8c79e6bf.
Report an issue: GitHub.