✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

You need a URL a load balancer can poll to decide whether a Strapi instance is alive, without authentication and without pulling real content. Strapi has one built in.

GET /_health

It responds with HTTP 204 No Content and a strapi: You are so French! response header. No authentication, no body, no database round trip.

$ curl -I https://cms.example.com/_health
HTTP/2 204
strapi: You are so French!

That is the whole endpoint. The rest of this post is about using it correctly.

What it does and does not tell you

/_health is a liveness signal: the Node process is running and the HTTP server is accepting and routing requests. That is genuinely useful: it distinguishes "the process crashed" and "the container never started" from "the app is fine".

It is not a readiness or dependency check. A 204 from /_health does not tell you that:

  • the database is reachable,
  • migrations completed,
  • your upload provider is configured,
  • the admin panel was built.

Strapi will not finish booting if the database is unreachable at startup, so in practice a 204 shortly after boot does imply the database was reachable at boot. But a database that goes away an hour later will not change the health response. If you need dependency checking, build it (see below).

Docker

Use it as the container healthcheck so orchestrators know when to restart:

HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:1337/_health || exit 1

--start-period matters. Strapi's boot includes loading the schema, running migrations, and syncing the database schema, which on a large project takes well over the default grace period. Too short a start period and Docker kills the container mid-migration, repeatedly.

In Compose, gate dependent services on it:

services:
  strapi:
    build: .
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:1337/_health"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3
    depends_on:
      postgres:
        condition: service_healthy

Kubernetes

Use the same path for both probes, with different timings:

livenessProbe:
  httpGet:
    path: /_health
    port: 1337
  initialDelaySeconds: 60
  periodSeconds: 30
  timeoutSeconds: 5
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /_health
    port: 1337
  initialDelaySeconds: 20
  periodSeconds: 10
  timeoutSeconds: 3
  failureThreshold: 3

startupProbe:
  httpGet:
    path: /_health
    port: 1337
  failureThreshold: 30
  periodSeconds: 10

The startupProbe is the important one for Strapi. It gives the process up to five minutes to finish booting while keeping the liveness probe's aggressive timings once it is up. Without it, a slow first boot, whether from a cold database or a large schema sync, gets killed and restarted in a loop.

AWS ALB / NLB target groups

Protocol:              HTTP
Path:                  /_health
Success codes:         204
Healthy threshold:     2
Unhealthy threshold:   3
Timeout:               5 seconds
Interval:              30 seconds

The value that catches people out is success codes. ALB target groups default to expecting 200. Strapi returns 204, so the default configuration marks every healthy target as unhealthy and drains your entire service. Set it to 204, or the range 200-299.

Nginx upstream checks

location = /_health {
    proxy_pass http://strapi_upstream/_health;
    access_log off;
}

access_log off keeps a 30-second poll from filling your log volume.

Uptime monitors

Point Pingdom, Better Stack, UptimeRobot, or Checkly at https://cms.example.com/_health and configure them to treat 204 as success. As with the ALB, several default to "expect 200" and will page you continuously about a perfectly healthy service.

If your monitor cannot be configured to accept 204, use a real content endpoint instead, as described below.

Building a deeper health check

When you need to know that the database is genuinely responding, add your own route. Create a custom route and controller, or register a route in src/index:

export default {
  register({ strapi }) {
    strapi.server.routes([
      {
        method: 'GET',
        path: '/_health/deep',
        handler: async (ctx) => {
          const checks: Record<string, string> = {};
          let ok = true;

          try {
            await strapi.db.connection.raw('SELECT 1');
            checks.database = 'ok';
          } catch (error) {
            checks.database = 'error';
            ok = false;
            strapi.log.error('Health check: database unreachable', error);
          }

          ctx.status = ok ? 200 : 503;
          ctx.body = {
            status: ok ? 'ok' : 'degraded',
            uptime: process.uptime(),
            checks,
          };
        },
        config: {
          auth: false,
        },
      },
    ]);
  },

  bootstrap() {},
};

config.auth = false is what makes it public. Without it the route requires authentication and your load balancer gets a 401.

Two cautions:

  • Do not put this on the load balancer's liveness probe. A brief database blip would then take every instance out of rotation at once, turning a recoverable incident into a full outage. Use /_health for liveness and a deep check for your monitoring dashboard and alerting.
  • Do not leak internals. Return status names, not connection strings, versions, or error messages. This endpoint is public.

Alternative: use a real content route

If you want a check that exercises the full stack (routing, middleware, database, permissions), poll a small published collection through the REST API:

GET /api/settings?fields[0]=id&pagination[pageSize]=1

Grant the Public role read access to that one content type. This returns a 200 with a tiny body, works with monitors that insist on 200, and fails when anything in the request path is broken. The trade-off is that it hits the database on every poll, so keep the interval sane.

Summary

NeedUse
Is the process alive?GET /_health204
Container healthcheckwget --spider http://localhost:1337/_health
K8s startup probe/_health, failureThreshold: 30
ALB target group/_health, success codes 204
Is the database responding?Custom /_health/deep route
Monitor that requires 200A small public content endpoint

Further reading

Theodore Kelechukwu OnyejiakuDevRel and Community | Software Developer | Technical Writer

Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.

"Cannot destructure property 'client' of 'db.config.connection'" in Strapi
EcosystemIntermediate·6 min read

"Cannot destructure property 'client' of 'db.config.connection'" in Strapi

This error means Strapi loaded no database configuration at all. The file is missing from the build, excluded from the container, or never compiled.

·September 9, 2026
"Cannot send secure cookie over unencrypted connection" in Strapi
EcosystemAdvanced·6 min read

"Cannot send secure cookie over unencrypted connection" in Strapi

Your site is on HTTPS but Strapi thinks the request came over HTTP. The fix is one config key, proxy.koa, plus the proxy headers that make it work.

·September 9, 2026
"Strapi is in production mode" when you are running development mode
EcosystemBeginner·5 min read

"Strapi is in production mode" When you are Running Development Mode

The Content-Type Builder is disabled and Strapi claims production, but your terminal says development. The rule Strapi actually applies, and the fix.

·September 9, 2026