DEV Community

Taylor Wang
Taylor Wang

Posted on

The Health Check Killed My Free Server Before the Model Could Answer

The Health Check Killed My Free Server Before the Model Could Answer

Last week, my free server started dying every few minutes. The logs showed a single line — Health check failed — and then the container would restart, taking my little bot down with it. I checked the model API, the database, and my own code, and everything looked fine. The only clue was that the restarts always happened right after a model call.

I was hosting a Telegram bot on MonkeyCode's free server option, using their free model access to summarize incoming messages. The bot worked fine for days, then suddenly entered a death loop. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The investigation

I started with the usual suspects:

  1. Memory or CPU spikes? No. The server's resource usage was flat.
  2. A bug in my code? No. The same code ran fine locally.
  3. The model API being down? No. A manual call from my laptop worked instantly.

Then I added logging to the /health endpoint. That's when I saw it: the endpoint was slow only when a model call was in progress. The model call itself took 8–10 seconds due to cold start, but the health check timeout was 5 seconds. The platform pings /health every 5 seconds, and after two consecutive failures, it kills the container.

So the sequence was:

  1. A new message triggers a model call.
  2. The model call blocks the event loop (or the worker process).
  3. The health check times out.
  4. The platform marks the container unhealthy.
  5. The container is restarted, killing the model call.
  6. The next message starts the cycle again.

The root cause

This was a race condition between the health check timeout and the combined cold start of the server and the model. The platform assumed that a slow response meant an unhealthy process. In reality, the process was busy waiting for an external service — and the external service was just slow to wake up.

Reproduce it locally

I built a small simulation to understand the mechanics. Here's a server that takes 10 seconds to become ready:

# cold_start_server.py
import time
from http.server import BaseHTTPRequestHandler, HTTPServer

start_time = time.time()

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if time.time() - start_time < 10:
            self.send_response(503)
            self.end_headers()
            self.wfile.write(b"not ready")
        else:
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"ok")

    def log_message(self, *args):
        pass

HTTPServer(("127.0.0.1", 8888), Handler).serve_forever()
Enter fullscreen mode Exit fullscreen mode

Now a health check script that mimics the platform's behavior: check every 5 seconds, restart after two failures.

# health_check_simulator.py
import subprocess, time, urllib.request

def check():
    try:
        with urllib.request.urlopen("http://127.0.0.1:8888/health", timeout=2) as resp:
            return resp.status == 200
    except Exception:
        return False

failures = 0
while True:
    ok = check()
    print(f"health check: {'ok' if ok else 'fail'}")
    if ok:
        failures = 0
    else:
        failures += 1
        if failures >= 2:
            print("restarting container...")
            subprocess.run(["pkill", "-f", "cold_start_server.py"])
            time.sleep(1)
            subprocess.Popen(["python", "cold_start_server.py"])
            failures = 0
    time.sleep(5)
Enter fullscreen mode Exit fullscreen mode

Run both scripts and watch the loop. The server never stays up long enough to become healthy, because the health check keeps killing it during the cold start window. This is exactly what happened on my free server.

The fix

I had several options, and the right one depends on your platform.

Option 1: Adjust the health check settings

If your platform lets you change the timeout or the failure threshold, do that. A timeout of 15 seconds and a threshold of 3 failures would have solved my problem. But not every platform exposes those knobs.

Option 2: Use a startup probe

Kubernetes has this concept: a startup probe runs before the liveness probe, and it's allowed to take as long as needed. If your platform supports it, use a startup probe with a generous timeout, and keep the liveness probe strict after startup.

Option 3: Warm up the model

I chose this one because it was the simplest. On application startup, I fire a background request to the model endpoint. That way, by the time the health check starts, the model connection is already warm. The first real request doesn't have to pay the cold start penalty.

Here's a minimal warm-up pattern:

import threading

def warm_up():
    try:
        call_model("ping")
    except Exception:
        pass

threading.Thread(target=warm_up, daemon=True).start()
Enter fullscreen mode Exit fullscreen mode

Option 4: Make the health endpoint independent

Don't let a model call block the health check. If your server is single-threaded, move the model call to a background worker or a separate process. The health endpoint should only verify that the process is alive, not that every dependency is reachable.

Limitations

The warm-up approach consumes one model call per server start. If you're on a strict quota, that might matter. Also, if the model endpoint is genuinely down, warm-up won't help — you'll still fail health checks, and the platform will keep restarting you. In that case, you need a circuit breaker, not a warm-up.

The reusable lesson

Health checks are a good thing, but their default settings are designed for always-on services, not cold-start environments. When your server restarts for no apparent reason, look at the health check configuration before you blame the model or your code. The problem might be that your service is simply not ready fast enough for the platform's expectations.

Have you ever debugged a similar restart loop? How did you separate the health check from slow external dependencies? I'd love to hear your approach.

Top comments (0)