fatedier/frp · critical

listen on quic udp address

Error message

listen on quic udp address %s error: %v

What it means

When QUICBindPort > 0, frps calls quic.ListenAddr on BindAddr:QUICBindPort with the server TLS config. Any failure (bind error, TLS misconfiguration) aborts service creation with the address echoed in the error.

Solutions

  1. Free or change quicBindPort
  2. Validate the TLS cert/key pair configured on frps (they must parse and match)
  3. Confirm UDP egress/ingress is permitted if behind NAT/firewalls
  4. Disable quicBindPort if QUIC transport is not needed
Defensive patterns

Strategy: validation

Validate before calling

// Validate TLS material and UDP port before enabling QUIC.
if _, err := tls.LoadX509KeyPair(cfg.TLSCertFile, cfg.TLSKeyFile); err != nil {
    return err
}
if c, err := net.ListenPacket("udp", addr); err != nil { return err } else { c.Close() }

Prevention

When it happens

Trigger: QUIC UDP port already in use; TLS certificate/key invalid so the cloned tlsConfig cannot back a QUIC listener; BindAddr not present on the host.

Common situations: Running QUIC on a port already used by another QUIC/HTTP3 service; self-signed or unreadable cert files; UDP blocked by cloud security groups.

Related errors


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

Appendix: source

Thrown at server/service.go:275

		address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.KCPBindPort))
		svr.kcpListener, err = netpkg.ListenKcp(address)
		if err != nil {
			return nil, fmt.Errorf("listen on kcp udp address %s error: %v", address, err)
		}
		log.Infof("frps kcp listen on udp %s", address)
	}

	if cfg.QUICBindPort > 0 {
		address := net.JoinHostPort(cfg.BindAddr, strconv.Itoa(cfg.QUICBindPort))
		quicTLSCfg := tlsConfig.Clone()
		quicTLSCfg.NextProtos = []string{"frp"}
		svr.quicListener, err = quic.ListenAddr(address, quicTLSCfg, &quic.Config{
			MaxIdleTimeout:     time.Duration(cfg.Transport.QUIC.MaxIdleTimeout) * time.Second,
			MaxIncomingStreams: int64(cfg.Transport.QUIC.MaxIncomingStreams),
			KeepAlivePeriod:    time.Duration(cfg.Transport.QUIC.KeepalivePeriod) * time.Second,
		})
		if err != nil {
			return nil, fmt.Errorf("listen on quic udp address %s error: %v", address, err)
		}
		log.Infof("frps quic listen on %s", address)
	}

	if cfg.SSHTunnelGateway.BindPort > 0 {
		sshGateway, err := ssh.NewGateway(cfg.SSHTunnelGateway, cfg.BindAddr, svr.sshTunnelListener)
		if err != nil {
			return nil, fmt.Errorf("create ssh gateway error: %v", err)
		}
		svr.sshTunnelGateway = sshGateway
		log.Infof("frps sshTunnelGateway listen on port %d", cfg.SSHTunnelGateway.BindPort)
	}

	// Listen for accepting connections from client using websocket protocol.
	websocketPrefix := []byte("GET " + netpkg.FrpWebsocketPath)
	websocketLn := svr.muxer.Listen(0, uint32(len(websocketPrefix)), func(data []byte) bool {
		return bytes.Equal(data, websocketPrefix)
	})

View on GitHub (pinned to 6c8a8d0a97)