grpc/grpc-go · error · ErrServerStopped

grpc: the server has been stopped

Error message

grpc: the server has been stopped

What it means

ErrServerStopped is a package-level sentinel (server.go:856) returned by Server.Serve when the server has already been stopped via Stop or GracefulStop, i.e. s.lis is nil. It signals the operation is now illegal because the server's lifecycle has ended. Serve also closes the passed listener before returning it.

Source

Thrown at server.go:856

		for m, d := range srv.streams {
			methods = append(methods, MethodInfo{
				Name:           m,
				IsClientStream: d.ClientStreams,
				IsServerStream: d.ServerStreams,
			})
		}

		ret[n] = ServiceInfo{
			Methods:  methods,
			Metadata: srv.mdata,
		}
	}
	return ret
}

// ErrServerStopped indicates that the operation is now illegal because of
// the server being stopped.
var ErrServerStopped = errors.New("grpc: the server has been stopped")

type listenSocket struct {
	net.Listener
	channelz *channelz.Socket
}

func (l *listenSocket) Close() error {
	err := l.Listener.Close()
	channelz.RemoveEntry(l.channelz.ID)
	channelz.Info(logger, l.channelz, "ListenSocket deleted")
	return err
}

// Serve accepts incoming connections on the listener lis, creating a new
// ServerTransport and service goroutine for each. The service goroutines
// read gRPC requests and then call the registered handlers to reply to them.
// Serve returns when lis.Accept fails with fatal errors.  lis will be closed when
// this method returns.

View on GitHub (pinned to 03255a9237)

Solutions

  1. Construct a fresh grpc.NewServer() for each new Serve lifecycle; do not reuse a stopped server.
  2. Guard Serve with your own state flag or only call it once per server instance.
  3. Compare the returned error to grpc.ErrServerStopped and treat it as expected during shutdown rather than a fatal error.
  4. Coordinate shutdown so Serve has fully returned before you attempt any post-stop operations.

Example fix

// before
srv := grpc.NewServer()
srv.Serve(lis)
srv.Stop()
srv.Serve(lis2) // returns ErrServerStopped

// after
srv := grpc.NewServer()
go srv.Serve(lis)
// on reload:
srv.GracefulStop()
srv = grpc.NewServer() // new instance
srv.Serve(lis2)
Defensive patterns

Strategy: try-catch

Try / catch

if err := srv.Serve(lis); err != nil {
    if errors.Is(err, grpc.ErrServerStopped) {
        // expected during shutdown; ignore
        return
    }
    log.Fatalf("Serve failed: %v", err)
}

Prevention

When it happens

Trigger: Calling s.Serve(newListener) after s.Stop() or s.GracefulStop() has run; a restart/reload loop that reuses a stopped Server instance instead of constructing a new one.

Common situations: Graceful restart logic that keeps the old Server; calling Serve in a goroutine that races with shutdown; integration tests that Stop then Serve again on the same Server.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/b10b7c6e4cd4f6af. Report an issue: GitHub.