AlistGo/alist · info

server has shutdown

Error message

server has shutdown

What it means

Returned by FtpMainDriver.ClientConnected when a new FTP client connects after the server has begun (or completed) shutdown. The driver uses an isShutdown flag plus a write-locked RWMutex: Stop() sets the flag and takes the write lock, so new connections fail either the flag check or the TryRLock. It protects in-flight client bookkeeping from racing with the shutdown loop that closes all clients.

Source

Thrown at server/ftp.go:101

			SiteHandlers: map[string]ftpserver.SiteHandler{
				"SIZE": ftp.HandleSIZE,
			},
		},
		proxyHeader:  header,
		clients:      make(map[uint32]ftpserver.ClientContext),
		shutdownLock: sync.RWMutex{},
		isShutdown:   false,
		tlsConfig:    tlsConf,
	}, nil
}

func (d *FtpMainDriver) GetSettings() (*ftpserver.Settings, error) {
	return d.settings, nil
}

func (d *FtpMainDriver) ClientConnected(cc ftpserver.ClientContext) (string, error) {
	if d.isShutdown || !d.shutdownLock.TryRLock() {
		return "", errors.New("server has shutdown")
	}
	defer d.shutdownLock.RUnlock()
	d.clients[cc.ID()] = cc
	return "AList FTP Endpoint", nil
}

func (d *FtpMainDriver) ClientDisconnected(cc ftpserver.ClientContext) {
	err := cc.Close()
	if err != nil {
		utils.Log.Errorf("failed to close client: %v", err)
	}
	delete(d.clients, cc.ID())
}

func (d *FtpMainDriver) AuthUser(cc ftpserver.ClientContext, user, pass string) (ftpserver.ClientDriver, error) {
	var userObj *model.User
	var err error
	if user == "anonymous" || user == "guest" {

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Treat it as expected during shutdown: stop/retry-loop the FTP client and wait for the server to come back before reconnecting
  2. If hit outside a restart, check whether the service is crash-looping (inspect logs for a preceding fatal error that called Stop)
  3. Operations: drain the load balancer before restarting the AList instance so clients are not routed to a stopping server
Defensive patterns

Strategy: retry

Try / catch

// In the FTP client: treat 'server has shutdown' as transient only while reconnecting
if err != nil && strings.Contains(err.Error(), "server has shutdown") {
    time.Sleep(backoff.Next())
    continue // reconnect loop
}

Prevention

When it happens

Trigger: Any FTP control-channel connection attempt (AUTH/USER handshake start) that arrives after ftpserver's Stop() was called, e.g. during process exit, service reload, or a graceful shutdown triggered from the admin API. Also occurs when a client reconnects while the server is draining connections.

Common situations: Clients with aggressive auto-reconnect polling a server that is restarting; orchestration systems (systemd/docker) sending SIGTERM while load balancers still route FTP traffic to the instance; tests that stop the FTP server and then dial it again.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/d42d5f179c59e370. Report an issue: GitHub.