Skip to content

Readiness vs Liveness Probes — Complete Implementation Guide

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Readiness vs Liveness Probes. We cover key concepts, practical examples, and best practices to help you master this topic.

Readiness probes determine if a service can accept traffic, while liveness probes determine if a service needs restarting. Understanding the distinction is critical for reliable Kubernetes deployments.

What You'll Learn

By the end of this tutorial, you will know when to use readiness vs liveness probes, how to configure each, and how to avoid common anti-patterns that cause cascading failures.

Why It Matters

Confusing readiness and liveness is one of the most common Kubernetes configuration errors. A misconfigured probe can cause unnecessary restarts, prevent traffic from reaching healthy instances, or fail to detect actual failures.

Real-World Use

DodaTech's platform team mandates: readiness probes check dependencies (database, cache), liveness probes check only the Process health. This separation prevents dependency failures from triggering restarts.

Readiness vs Liveness Learning Path

flowchart LR
  A[Health Check Intro] --> B[Readiness vs Liveness]
  B --> C[Readiness Probes]
  B --> D[Liveness Probes]
  B --> E[Startup Probes]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Readiness Probes in Detail

Readiness probes control whether a pod receives traffic from Kubernetes services.

class ReadinessProbe {
  constructor() {
    this.ready = false;
    this.dependencies = new Map();
  }

  addDependency(name, check) {
    this.dependencies.set(name, check);
  }

  async check() {
    const results = [];
    for (const [name, check] of this.dependencies) {
      try {
        const healthy = await check();
        results.push({ name, healthy });
        if (!healthy) {
          console.log(`Dependency ${name} unhealthy, marking not ready`);
        }
      } catch (err) {
        results.push({ name, healthy: false, error: err.message });
        console.log(`Dependency ${name} check failed: ${err.message}`);
      }
    }

    this.ready = results.every(r => r.healthy);
    return {
      ready: this.ready,
      dependencies: results,
      timestamp: new Date().toISOString()
    };
  }

  isReady() {
    return this.ready;
  }
}

const readiness = new ReadinessProbe();
readiness.addDependency("database", async () => true);
readiness.addDependency("redis", async () => true);
readiness.check().then(r => console.log("Ready:", r.ready));
// Ready: true

Liveness Probes in Detail

Liveness probes determine if the process is healthy and should continue running.

class LivenessProbe {
  constructor() {
    this.healthy = true;
    this.lastActivity = Date.now();
    this.goroutineCount = 0;
  }

  recordActivity() {
    this.lastActivity = Date.now();
  }

  async check() {
    const now = Date.now();
    const stuckDuration = now - this.lastActivity;

    if (stuckDuration > 60000) {
      console.log(`No activity for ${stuckDuration}ms, marking unhealthy`);
      this.healthy = false;
    }

    if (this.goroutineCount > 1000) {
      console.log("Too many goroutines, marking unhealthy");
      this.healthy = false;
    }

    return {
      healthy: this.healthy,
      lastActivityMs: stuckDuration,
      timestamp: now
    };
  }

  isAlive() {
    return this.healthy;
  }
}

const liveness = new LivenessProbe();
console.log("Alive:", liveness.isAlive());
liveness.lastActivity = Date.now() - 120000;
liveness.check().then(r => console.log("Alive after 2min idle:", r.healthy));
// Alive: true
// Alive after 2min idle: false

Startup Probes

Startup probes delay readiness and liveness checks until the service has finished initializing.

class StartupProbe {
  constructor() {
    this.started = false;
    this.initializationTasks = [];
  }

  addTask(name, task) {
    this.initializationTasks.push({ name, task });
  }

  async initialize() {
    console.log(`Starting ${this.initializationTasks.length} initialization tasks`);

    for (const { name, task } of this.initializationTasks) {
      console.log(`  Initializing: ${name}`);
      await task();
      console.log(`  Initialized: ${name}`);
    }

    this.started = true;
    console.log("Startup complete, probes will now pass");
  }

  async check() {
    return {
      started: this.started,
      completedTasks: this.initializationTasks.length,
      timestamp: new Date().toISOString()
    };
  }
}

const startup = new StartupProbe();
startup.addTask("connect-db", () => new Promise(r => setTimeout(r, 1000)));
startup.addTask("load-cache", () => new Promise(r => setTimeout(r, 500)));
startup.initialize();
// Starting 2 initialization tasks
//   Initializing: connect-db
//   Initialized: connect-db
//   Initializing: load-cache
//   Initialized: load-cache
// Startup complete, probes will now pass

Common Anti-Patterns

The most common Kubernetes probe configuration mistakes.

class ProbeAntiPatterns {
  static list() {
    return [
      {
        pattern: "Using readiness for liveness",
        problem: "Readiness failures remove the pod from service but don't restart it. A genuinely stuck pod stays stuck.",
        solution: "Use liveness to detect and restart stuck processes. Use readiness to handle dependency failures."
      },
      {
        pattern: "Checking dependencies in liveness",
        problem: "If the database is slow, all pods restart simultaneously, making the problem worse.",
        solution: "Only check process-level health in liveness. Check dependencies in readiness."
      },
      {
        pattern: "Using the same endpoint for both probes",
        problem: "You lose the ability to differentiate between 'don't send traffic' and 'restart me'.",
        solution: "Use separate endpoints: /readyz for readiness, /healthz for liveness."
      },
      {
        pattern: "Probe timeout too short",
        problem: "A brief spike in latency causes false failures, triggering unnecessary restarts.",
        solution: "Set timeout to at least 5 seconds, failureThreshold to at least 3."
      }
    ];
  }
}

ProbeAntiPatterns.list().forEach(p => {
  console.log(`Anti-pattern: ${p.pattern}`);
  console.log(`  Problem: ${p.problem}`);
  console.log(`  Solution: ${p.solution}\n`);
});

Kubernetes Probe Configuration

Proper configuration values for production deployments.

class ProbeConfiguration {
  static recommended(type) {
    const configs = {
      readiness: {
        initialDelaySeconds: 5,
        periodSeconds: 10,
        timeoutSeconds: 3,
        failureThreshold: 3,
        successThreshold: 1
      },
      liveness: {
        initialDelaySeconds: 30,
        periodSeconds: 15,
        timeoutSeconds: 5,
        failureThreshold: 3,
        successThreshold: 1
      },
      startup: {
        initialDelaySeconds: 0,
        periodSeconds: 5,
        timeoutSeconds: 5,
        failureThreshold: 30,
        successThreshold: 1
      }
    };

    return configs[type] || configs.readiness;
  }

  static generateYaml(type, path, port) {
    const config = this.recommended(type);
    return {
      [type + "Probe"]: {
        httpGet: { path, port },
        ...config
      }
    };
  }
}

console.log("Readiness config:", ProbeConfiguration.recommended("readiness"));
console.log("Liveness config:", ProbeConfiguration.recommended("liveness"));

Practice Questions

  1. What should a readiness probe check? Dependency health (database, cache, downstream services). The service is ready to receive traffic only when its dependencies are available.

  2. What should a liveness probe check? Process health (is the event loop responding, are goroutines healthy). The process is alive and making progress.

  3. Why should startup probes have a high failure threshold? Startup can take 2-3 minutes. A high threshold (e.g., 30 failures at 5-second intervals = 150 seconds) gives the service time to initialize.

  4. Challenge: Implement a server that differentiates between readiness, liveness, and startup probe responses.

const http = require("http");

function createThreeProbeServer() {
  let startupComplete = false;
  let readyForTraffic = false;
  let processAlive = true;

  setTimeout(() => { startupComplete = true; }, 3000);
  setTimeout(() => { readyForTraffic = true; }, 8000);

  return http.createServer((req, res) => {
    let status = 503;
    let body = { status: "not ok" };

    if (req.url === "/startupz") {
      status = startupComplete ? 200 : 503;
      body = { status: startupComplete ? "started" : "starting" };
    } else if (req.url === "/readyz") {
      status = readyForTraffic ? 200 : 503;
      body = { status: readyForTraffic ? "ready" : "not ready" };
    } else if (req.url === "/healthz") {
      status = processAlive ? 200 : 503;
      body = { status: processAlive ? "alive" : "dead" };
    }

    res.writeHead(status, { "Content-Type": "application/json" });
    res.end(JSON.stringify(body));
  });
}

createThreeProbeServer().listen(8080);

FAQ

Can I skip the startup probe?

Yes, but without it your readiness/liveness initialDelaySeconds must account for worst-case startup time. A startup probe lets probes start faster.

What happens if both readiness and liveness fail?

The pod is removed from service and restarted. This is the correct behavior for a truly unhealthy pod.

Should I use TCP or HTTP probes?

HTTP probes are preferred because they verify the application layer, not just the TCP port. Use TCP only for non-HTTP services.

How do I test probe behavior?

Deploy to a staging cluster, trigger failures manually, and observe probe responses with kubectl describe pod.

Can probes have side effects?

They should not. A probe that modifies state (e.g., incrementing a counter) creates inconsistencies when probes run at unpredictable intervals.

Mini Project

Build a probe demonstration server that goes through startup, becomes ready, experiences a dependency failure, and recovers, showing how each probe type responds.

const http = require("http");

function buildProbeDemo() {
  const state = { startup: false, ready: false, alive: true, dbHealthy: true };

  setTimeout(() => { state.startup = true; console.log("Startup complete"); }, 5000);
  setTimeout(() => { state.ready = true; console.log("Ready for traffic"); }, 10000);
  setTimeout(() => { state.dbHealthy = false; state.ready = false; console.log("DB failure"); }, 20000);
  setTimeout(() => { state.dbHealthy = true; state.ready = true; console.log("DB recovered"); }, 30000);

  const server = http.createServer((req, res) => {
    const response = { timestamp: new Date().toISOString() };

    if (req.url === "/startupz") {
      response.status = state.startup ? "started" : "starting";
      res.writeHead(state.startup ? 200 : 503);
    } else if (req.url === "/readyz") {
      response.status = state.ready ? "ready" : "not ready";
      response.dbHealthy = state.dbHealthy;
      res.writeHead(state.ready ? 200 : 503);
    } else if (req.url === "/healthz") {
      response.status = state.alive ? "alive" : "dead";
      res.writeHead(state.alive ? 200 : 503);
    }

    res.setHeader("Content-Type", "application/json");
    res.end(JSON.stringify(response));
  });

  return server;
}

buildProbeDemo().listen(8080);
console.log("Probe demo starting...");

What's Next

Now that you understand probe types, learn about different types of health checks. Then implement a simple health check endpoint.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro