overleaf/overleaf · warning
Service Unavailable (shutdown in progress res.sendStatus(503
Error message
Service Unavailable (shutdown in progress res.sendStatus(503))
What it means
The real-time service returns HTTP 503 on GET '/' when it is shutting down or the deployment is closed. Load balancer health checks hit this endpoint; a 503 tells them to stop routing new traffic to this instance during graceful shutdown or deployment drain.
Source
Thrown at services/real-time/app.js:143
return true
})
}
})
// Serve socket.io.js client file from imported dist folder
// The express sendFile method correctly handles conditional
// requests using the last-modified time and etag (which is
// a combination of mtime and size)
const socketIOClientFolder = socketIOClient.dist
app.get('/socket.io/socket.io.js', function (req, res) {
res.sendFile(Path.join(socketIOClientFolder, 'socket.io.min.js'))
})
// a 200 response on '/' is required for load balancer health checks
// these operate separately from kubernetes readiness checks
app.get('/', function (req, res) {
if (Settings.shutDownInProgress || DeploymentManager.deploymentIsClosed()) {
res.sendStatus(503) // Service unavailable
} else {
res.send('real-time is open')
}
})
app.get('/status', function (req, res) {
if (Settings.shutDownInProgress) {
res.sendStatus(503) // Service unavailable
} else {
res.send('real-time is alive')
}
})
app.get('/debug/events', function (req, res) {
Settings.debugEvents = parseInt(req.query.count, 10) || 20
logger.info({ count: Settings.debugEvents }, 'starting debug mode')
res.send(`debug mode will log next ${Settings.debugEvents} events`)
})View on GitHub (pinned to 28ad3b03b7)
Solutions
- Confirm the instance is intentionally shutting down (rolling deploy/drain); 503 here is expected and transient
- Check DeploymentManager.deploymentIsClosed() — if closed unintentionally, inspect deployment state/config
- If 503 persists while shutDownInProgress is false, restart the service and verify shutdown handlers run
- Exclude this instance from load balancer pools during deploys so users see no impact
Example fix
// before
if (Settings.shutDownInProgress || DeploymentManager.deploymentIsClosed()) {
res.sendStatus(503)
}
// after
// during shutdown, remove instance from LB pool and return 503 with a retry hint
res.set('Retry-After', '5')
res.sendStatus(503) Defensive patterns
Strategy: retry
Validate before calling
const isHealthy = async baseUrl => (await fetch(baseUrl + '/')).status === 200
Try / catch
// retry with backoff while instance drains
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(url)
if (res.status !== 503) return res
await sleep(1000 * 2 ** attempt)
}
throw new Error('real-time instance unavailable') Prevention
- Drain instances from the load balancer before shutdown so users never hit 503
- Monitor deployment state if 503 persists after deploys finish
- Set reasonable probe periods to avoid alert noise during rolling restarts
When it happens
Trigger: GET / while Settings.shutDownInProgress is true, or DeploymentManager.deploymentIsClosed() returns true (instance draining during a deploy/shutdown).
Common situations: Kubernetes or a load balancer polling '/' during a rolling restart; monitoring alerts firing because an instance reports 503 while being drained; misconfigured deployment manager closing deployments without traffic draining elsewhere.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- Service Unavailable (shutdown complete res.sendStatus(503))
- failed mongo ping
- notification not found in response
- Failed to close ${errors.length} connection(s): ${errors.map
- Lost connection with MongoDB
AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03).
Data as JSON: /api/errors/81243b00a9531b5d.
Report an issue: GitHub.