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
- Treat it as expected during shutdown: stop/retry-loop the FTP client and wait for the server to come back before reconnecting
- If hit outside a restart, check whether the service is crash-looping (inspect logs for a preceding fatal error that called Stop)
- 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
- Use bounded exponential backoff reconnect loops in long-lived FTP clients
- Drain load balancers of FTP traffic before restarting the AList instance
- Log the error at info level — during a restart it is expected noise, not a defect
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.