LogoPly
Middleware

Health Checks

Liveness and readiness handlers for Ply services.

Health Checks

Ply provides two handlers for orchestrators and load balancers.

app.Get("/healthz", middleware.Health())
app.Get("/readyz", middleware.Readiness(
	func() error { return db.Ping() },
))

Both handlers set Cache-Control: no-store.

Liveness

Health always returns 200:

{
  "status": "ok"
}

Use it to show that the process is running. Keep it free of database and network checks. A failed dependency should not restart an otherwise healthy process.

Readiness

Readiness runs the checks in order and returns 200 when all succeed:

{
  "status": "ok"
}

If any check returns an error, the response is 503:

{
  "status": "unavailable"
}

The error value is not included in the response. Log it in the check or in middleware if you need the details.

Readiness with no checks always returns 200.

Async checks

Checks run synchronously in the request. A slow database ping delays the readiness response. Make checks cheap, use a short internal timeout, and cache the dependency state when the check is expensive.

app.Get("/readyz", middleware.Readiness(func() error {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	return db.PingContext(ctx)
}))

Shutdown

Stop reporting ready when shutdown starts. Set an atomic flag in your signal handler and include it in the readiness route:

var shuttingDown atomic.Bool

app.Get("/readyz", middleware.Readiness(func() error {
	if shuttingDown.Load() {
		return errors.New("shutting down")
	}
	return nil
}))

This gives the load balancer time to remove the instance before the listener closes. See Graceful Shutdown.

Access control

Health routes are often public. Return only a status value, not build details, configuration, or dependency errors. Restrict /metrics separately.