Kubernetes Probes - Startup Probes
May 14, 2026
The short version
Kubernetes gives you three probe types. Most teams use liveness and readiness. The third one - startupProbe - solves a specific problem that the other two can’t handle cleanly:
- Apps with slow or variable startup times (migrations, cache warming, large dataset loads) need a long window before Kubernetes declares them dead
- But a long liveness
failureThresholdmeans slow failure detection in production - the wrong tradeoff startupProbegives you a generous startup window AND aggressive liveness detection once running, because it disables the other probes until it passes
The three probe types
livenessProbe - runs for the container’s entire lifetime. If it fails failureThreshold times consecutively, Kubernetes kills and restarts the container. Use this to detect a process that has deadlocked or otherwise become unrecoverable.
readinessProbe - also runs continuously. If it fails, the pod is removed from the Service’s endpoint list - it stops receiving traffic - but is NOT restarted. Use this to signal temporary unavailability (dependency down, still warming up).
startupProbe - runs only during startup. While it’s active, liveness and readiness probes are disabled entirely. The moment it succeeds once, it steps aside and the other two take over. If it never succeeds within failureThreshold × periodSeconds, Kubernetes kills the container.
The startup problem
Consider an app that runs database migrations before it starts serving traffic. On a small DB it takes 10 seconds. On a large one after a big deploy, it might take 3 minutes. The startup time is variable and you can’t predict it precisely.
Without startupProbe, you configure liveness something like this:
livenessProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 36
periodSeconds: 5
36 × 5 = 180 seconds - three minutes of startup room. This works, but now your liveness probe won’t declare a dead pod dead for three minutes either. If the app hangs in production, traffic hammers a broken pod for three minutes before Kubernetes acts.
Tighten it up:
livenessProbe:
failureThreshold: 3
periodSeconds: 10
3 × 10 = 30 seconds - fast failure detection. But now during a slow migration startup, Kubernetes kills the pod after 30 seconds, the migration is half-done, the DB is in an inconsistent state, and the next pod starts and immediately fails too. Crashloop.
You can’t win with liveness alone on slow-starting apps.
startupProbe: the best of both
startupProbe breaks the tradeoff. Set it generously for startup, set liveness aggressively for runtime:
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 60
periodSeconds: 5
initialDelaySeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 3
periodSeconds: 10
startupProbe: 10 second head start, then checks every 5 seconds for up to 60 failures - 60 × 5 = 300 seconds (5 minutes) total startup window.
livenessProbe: completely suppressed while startupProbe is running. Once the app’s /health returns 200 for the first time, startupProbe steps aside. From that point on, liveness checks every 10 seconds and kills the pod after 3 consecutive failures - 30 seconds to detect a dead pod.
The sizing formula is simple: failureThreshold × periodSeconds = total startup budget. Set it to your worst-case startup time plus a reasonable buffer.
A real example: apps that run migrations
We run Metabase in Kubernetes. Metabase runs Liquibase database migrations on every startup. On our database, this takes anywhere from 30 seconds to several minutes depending on how many new migrations are in the release.
Without a startup probe, Kubernetes would kill the pod mid-migration. A half-applied Liquibase migration leaves DATABASECHANGELOG in a state where neither the old image nor the new one can start cleanly. The recovery is manual.
Our full probe config:
startupProbe:
httpGet:
path: /api/health
port: 3000
failureThreshold: 60
periodSeconds: 5
initialDelaySeconds: 10
livenessProbe:
httpGet:
path: /api/health
port: 3000
failureThreshold: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/health
port: 3000
failureThreshold: 2
periodSeconds: 5
5 minutes for startup. 30 seconds to detect a dead pod in production. The pod won’t receive traffic until the startup probe has passed and the readiness probe confirms it’s serving.
The same pattern applies to any app with variable startup: Elasticsearch nodes loading large shards, services that pre-warm caches from a remote store, anything that does substantial work before it can serve its first request.
Health vs ready endpoints
Using the same endpoint for liveness and readiness is a common mistake. They have different semantics and conflating them causes problems.
/health (liveness): should return 200 if the process is alive and capable of handling requests. It should NOT check external dependencies like your database or cache. If your database goes down and your liveness probe checks the DB connection, Kubernetes will restart every pod simultaneously - which doesn’t fix the database and makes everything worse.
/ready (readiness): should return 200 only when the pod can actually serve traffic. This CAN check external dependencies. If the pod can’t reach the database, mark it not-ready so the Service stops routing to it. Other pods that can reach the database continue serving. The pod recovers silently when the database comes back.
The rule of thumb: liveness answers “is this process recoverable by restarting?” Readiness answers “should this pod receive traffic right now?”
When it breaks
Pod crashloops immediately on new deploys but works after a few restarts.
The startupProbe failureThreshold is too low for your actual worst-case startup time. Check how long migrations take against a production-sized DB, multiply by periodSeconds, and increase failureThreshold accordingly.
Pod is marked ready but returns errors.
No readinessProbe, or the readiness endpoint doesn’t actually reflect service health. Add a readiness probe that checks real dependencies the pod needs to serve traffic.
Pod takes 3+ minutes to be killed after a hang in production.
Your livenessProbe failureThreshold is sized for startup, not runtime. Use startupProbe for the startup window and keep liveness tight (failureThreshold: 3, periodSeconds: 10).
DB is in a partially-migrated state after a failed deploy.
A pod was killed mid-migration - either no startupProbe, or it wasn’t generous enough. Restore from a snapshot if needed. Going forward, set startupProbe’s budget to at least 2× your observed worst-case migration time.
Liveness and readiness fire during a transient dependency outage, causing a restart storm.
Liveness is checking external dependencies it shouldn’t. Move dependency checks to readiness only. Liveness should only fail if the process itself is broken.
