← All Articles ⚙️ Configuration & DevOps Medium By Nishant Kumar Verma Updated

No Health Endpoint, No Uptime: Why Your App Needs /health

AI-generated apps ship without a /health endpoint. Without one, your monitoring, load balancer, and container orchestrator are flying blind.

CFG-008

Quick Answer

Every production app needs a /health endpoint that returns a 200 status code when the service is healthy. Without one, your load balancer, container orchestrator, and monitoring tools have no way to know if your app is running. AI code generators almost never create health endpoints. Add one that checks your critical dependencies (database, Redis, external APIs) and returns a structured response with status and latency for each.

Why Health Endpoints Matter

A health endpoint is the single most important operational feature in a production application. Every piece of infrastructure depends on it:

  • Load balancers (AWS ALB, Nginx, Caddy) use health checks to route traffic only to healthy instances
  • Container orchestrators use health checks to catch containers that are running but stuck: Kubernetes restarts a container that fails its liveness probe, and Docker marks it unhealthy
  • Monitoring tools (Uptime Kuma, Datadog, Pingdom) use health checks to alert you when something is down
  • Zero-downtime deploys wait for the new container to pass health checks before stopping the old one

Without a health endpoint, your infrastructure is flying blind. Without a health check, a failed container can go unnoticed until users report the problem, which drags out recovery. The Uptime Institute's Annual Outage Analysis 2024 found that 54% of survey respondents said their most recent significant, serious or severe outage cost more than $100,000, and 16% said it cost more than $1 million.

Health checks also decide what happens during a cloud outage. When a power failure took a Google Cloud zone in London offline on 20 October 2026, traffic had to move to the healthy zones. Read what the Google Cloud London outage shows about failover.

What AI Code Generators Miss

When you ask Bolt, Cursor, or Lovable to build a web app, you get routes for your features but no operational routes. AI tools build what you describe in your prompt. Nobody prompts "and add a health check endpoint" - so it does not exist.

// ❌ BAD - AI-generated Express app with no health endpoint
const app = express();
app.use(express.json());

app.get('/api/users', getUsers);
app.post('/api/users', createUser);
app.get('/api/posts', getPosts);

app.listen(3000);
// Load balancer has no way to check if this is alive
// ✅ GOOD - Health endpoint with dependency checks
const withTimeout = (promise, ms = 2000) =>
  Promise.race([
    promise,
    new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms)),
  ]);

app.get('/health', async (req, res) => {
  const checks = {};

  // Check database
  try {
    const dbStart = Date.now();
    await withTimeout(db.query('SELECT 1'));
    checks.database = { status: 'ok', latency: Date.now() - dbStart };
  } catch (err) {
    checks.database = { status: 'error', error: err.message };
  }

  // Check Redis
  try {
    const redisStart = Date.now();
    await withTimeout(redis.ping());
    checks.redis = { status: 'ok', latency: Date.now() - redisStart };
  } catch (err) {
    checks.redis = { status: 'error', error: err.message };
  }

  const allHealthy = Object.values(checks).every(c => c.status === 'ok');

  res.status(allHealthy ? 200 : 503).json({
    status: allHealthy ? 'healthy' : 'unhealthy',
    uptime: process.uptime(),
    timestamp: new Date().toISOString(),
    checks
  });
});

Liveness vs Readiness Checks

Docker and Docker Compose have a single healthcheck, which marks the container healthy or unhealthy. Kubernetes distinguishes between two types of health checks (plus a startup probe for slow-starting apps), and conflating them causes production incidents:

Check Type What It Answers On Failure Endpoint
Liveness Is the process alive and responding? Container is killed and restarted /health or /healthz
Readiness Is the app ready to serve traffic? Traffic is stopped, but container stays alive /ready

Critical mistake: If your liveness check queries the database and the database is temporarily down, the orchestrator kills and restarts your app container repeatedly - making the outage worse. The liveness check should verify the process is alive (in-memory check). The readiness check should verify dependencies are available.

// Liveness - is the process alive? (lightweight, no external deps)
app.get('/healthz', (req, res) => {
  res.status(200).json({ status: 'alive' });
});

// Readiness - can this instance serve traffic? (checks dependencies)
app.get('/ready', async (req, res) => {
  try {
    await db.query('SELECT 1');
    await redis.ping();
    res.status(200).json({ status: 'ready' });
  } catch {
    res.status(503).json({ status: 'not ready' });
  }
});

Docker Compose Health Check Configuration

Docker Compose has built-in health check support that works with your endpoint. The check runs inside the container, so the command must exist in your image: the Alpine Node images ship wget but not curl, and the slim images ship neither.

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "3000:3000"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

Common Mistakes

  1. Checking auth on the health endpoint. Health checks must be unauthenticated. Your load balancer does not have a JWT token. Keep /health public but do not expose sensitive internal details.
  2. Slow health checks. If your health check takes 5 seconds because it runs a complex database query, your orchestrator will think the service is down. Use SELECT 1 or PING, not a full table scan.
  3. No timeout on dependency checks. If your database is hanging (not refusing connections, just not responding), the health check hangs too. Always set a timeout on dependency checks (1-2 seconds max).
  4. Returning 200 when unhealthy. Some implementations return { status: 'error' } with a 200 status code. Load balancers check the HTTP status code, not the body. Return 503 when unhealthy.

VibeDoctor's Vibe Check looks through the code of server projects (Express, Fastify, Flask, Django, FastAPI, Rails, Spring Boot, Laravel, Go routers and others) for a health route such as /health, /healthz or /ping, and reports a low-severity configuration issue (CFG-008) when it finds none. Free to sign up.

FAQ

Should the health endpoint be authenticated?

No. Health checks are called by infrastructure (load balancers, orchestrators, monitoring) that does not have user credentials. The endpoint should be unauthenticated but should not expose sensitive information like database connection strings or internal IP addresses. Return status and latency, not configuration details.

What URL path should I use?

The most common conventions are /health, /healthz (Kubernetes convention), and /ping. Pick one and configure your infrastructure to use it. If you need both liveness and readiness checks, use /healthz for liveness and /ready for readiness.

How often should health checks run?

Every 10-30 seconds is standard. More frequent checks (every 5 seconds) detect failures faster but add load. Less frequent checks (every 60 seconds) mean failures go undetected longer. For most apps, 15-30 second intervals balance detection speed with overhead.

What should the health check return?

At minimum: HTTP 200 with { "status": "ok" } when healthy, HTTP 503 when unhealthy. Better: include uptime, timestamp, and per-dependency status with latency. Do not include secrets, connection strings, or stack traces in the response.

Do I need a health endpoint for a static site?

For a pure static site (HTML/CSS/JS served by Nginx or a CDN), a health endpoint is less critical because there are no application-level dependencies to check. But if you are using Nginx, a simple location /health { return 200 'ok'; } enables monitoring tools to verify the web server itself is running.

Diagnose your codebase - free

VibeDoctor checks for CFG-008 and 148 other issues across 21 diagnostic areas - security, performance, code quality, and more.

SCAN MY APP →