diff --git a/server/udpserver.go b/server/udpserver.go
index 70de25b3c243b326dc50e237f1b5bb9927835c82..af7d3686cf53b925eb5d7d7e7bfee6d611fecf4e 100644
--- a/server/udpserver.go
+++ b/server/udpserver.go
@@ -55,14 +55,28 @@ func (s *UDPServer) Start() error {
 	if s.cfg.HoppingEnabled {
 		listeners = make([]net.Listener, s.cfg.PortCount)
 		portHopRange := int(s.cfg.MaxHopPort - s.cfg.MinHopPort)
+		listenerCount := 0
 		for i := 0; i < int(s.cfg.PortCount); i++ {
 			portOffset := r.Intn(portHopRange)
 			addr := net.JoinHostPort(s.cfg.Obfs4ListenIP, fmt.Sprint(portOffset+int(s.cfg.MinHopPort)))
-			listeners[i], err = listenConfig.Listen(s.ctx, addr)
+			listener, err := listenConfig.Listen(s.ctx, addr)
 
 			if err != nil {
 				s.logger.Printf("Error binding to %s: %v", addr, err)
+				continue
 			}
+			listeners[listenerCount] = listener
+			listenerCount++
+		}
+
+		if listenerCount < int(float64(s.cfg.PortCount)*0.9) {
+			// if too many of the required ports are already occupied, clients will suffer from
+			// bad throughput due to many dialing errors
+			return fmt.Errorf("too many port binding errors: %d/%d", len(listeners), int(s.cfg.PortCount))
+		} else if listenerCount < int(s.cfg.PortCount) {
+			// adjust the listener slice size so that we avoid nil entries
+			listeners = listeners[:listenerCount]
+			s.debug.Printf("WARNING: listening on less ports then clients expect: %d / %d", len(listeners), int(s.cfg.PortCount))
 		}
 	} else {
 		listenAddr := net.JoinHostPort(s.cfg.Obfs4ListenIP, strconv.Itoa(s.cfg.Obfs4ListenPort))