DEV Community

Sohana Akbar
Sohana Akbar

Posted on

Kubernetes Liveness & Readiness Probes for a Static Site: Stop 404-ing Your Traffic

You just containerized your static site (HTML, CSS, JS) using an nginx:alpine image. You deployed it to Kubernetes. It works locally.

Then you get the page flip: 502 Bad Gateway or random Connection Refused errors during a rolling update.

Why? Because Kubernetes thinks your container is "alive" and "ready" simply because the Nginx process is running—even if the configuration is broken, the disk is full, or the site is compiling.

Let's fix that. Here is the only guide you need for liveness and readiness probes on static sites.

The Golden Rule of Static Sites
Liveness ≠ Readiness.

Readiness: "Can I send traffic to this Pod right now?" (Startup & reloads)

Liveness: "Is the Pod broken beyond repair?" (Hard crashes)

For a static site, we use Readiness for startup delays and Liveness for deadlocks.

The Naive Approach (Don't do this)
Most tutorials tell you to do this:

yaml
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
Why this is bad for static sites:

Slow Builds: If you use a sidecar or initContainer to fetch a huge static bundle, the probe starts failing immediately. Kubernetes kills your pod before it finishes downloading.

404s happen: What if your app serves a 200 OK for /, but your CSS bundle main.css is corrupted? The pod is "alive" but the site is broken.

The Correct Setup: Two Probes, One Health Endpoint
Step 1: Create a Custom Health Endpoint
Do not probe /. Why? Because a CDN or browser cache might return a stale 200 OK. Instead, map a unique path like /health that returns a real status.

If you're using Nginx (the static site king), add this to your nginx.conf:

nginx
server {
listen 80;

location / {
    root /usr/share/nginx/html;
    try_files $uri $uri/ /index.html;
}

# Health check endpoint
location /health {
    access_log off;
    return 200 "healthy\n";
    add_header Content-Type text/plain;
}
Enter fullscreen mode Exit fullscreen mode

}
Step 2: Configuring the Readiness Probe (The "Slow Starter")
The readiness probe determines if your pod is ready to serve traffic. For static sites, the main risk is slow asset hydration (downloading from S3, building from Git, etc.).

yaml
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 0 # Start checking immediately
periodSeconds: 5 # Check every 5 seconds
failureThreshold: 3 # Allow 3 failures (15 seconds) before marking Unready
successThreshold: 1 # One success = ready
Pro-tip: Set initialDelaySeconds: 0 and rely on failureThreshold. This way, your pod starts "NotReady" and only becomes "Ready" when Nginx actually responds.

Step 3: Configuring the Liveness Probe (The "Zombie Killer")
The liveness probe restarts the pod if it enters a deadlock. For static sites, this rarely happens, but you still need it.

Important: The liveness probe should be more conservative than readiness. You don't want Kubernetes restarting your pod during a brief, heavy load spike.

yaml
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30 # Give it time to start first
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3 # Restart after 45 seconds of failures
Step 4: The Complete Deployment
Here is a production-ready static site deployment:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: static-site
spec:
replicas: 3
selector:
matchLabels:
app: static-site
template:
metadata:
labels:
app: static-site
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
# 🟢 The magic happens here
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 15
failureThreshold: 3
# 🟢 Make sure config is mounted
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: nginx-config
configMap:

name: nginx-health-config

apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-health-config
data:
default.conf: |
server {
listen 80;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /health {
return 200 "OK\n";
access_log off;
}
}
Advanced: Probing a "Real" Resource
Do you want to ensure the actual content is valid? Change the probe path from /health to a critical asset:

yaml
readinessProbe:
httpGet:
path: /index.html # The actual homepage
port: 80
# ... rest of config
But be careful: If you use index.html as your probe, and you delete a CSS file referenced in it, Kubernetes will correctly mark the pod Unready—but it might also restart it unnecessarily. Use this only if your static site is truly atomic (all files deploy together).

Common Static Site Pitfalls
Problem Symptom Fix
Git-sync sidecar slow Pod starts, probe fails Increase initialDelaySeconds or failureThreshold on readiness probe
Nginx config typo 500/502 errors No probe will save you; use ConfigMap validation in CI
Memory leak (rare) Pod slow but alive Liveness probe will restart it
Rolling update hanging Old pods terminate, new pods never get traffic Readiness probe failing? Check your health endpoint's dependencies
The Ultimate Static Site Rule
Readiness probe = Critical.
Liveness probe = Safety net.

For 99% of static sites, a basic httpGet on a custom /health endpoint is all you need. Don't overcomplicate it with exec commands or TCP probes.

What's your static site horror story? Let me know in the comments. 🚀

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.