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

  1. Confirm the instance is intentionally shutting down (rolling deploy/drain); 503 here is expected and transient
  2. Check DeploymentManager.deploymentIsClosed() — if closed unintentionally, inspect deployment state/config
  3. If 503 persists while shutDownInProgress is false, restart the service and verify shutdown handlers run
  4. 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

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

Related errors


AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03). Data as JSON: /api/errors/81243b00a9531b5d. Report an issue: GitHub.