Zero Downtime on One Server: What Matters
Zero downtime deployment without Kubernetes comes down to four things on one server: a real readiness gate, rolling restarts, graceful shutdown, and rollback.
Gagan Deep Singh
Founder | GLINR Studios
Zero downtime deployment without Kubernetes is possible on a single server. You start the new version next to the old one, send it traffic only after a real readiness check passes, let the old one drain and exit cleanly, and keep the previous version around so a failed deploy changes nothing for users. Four things matter: a readiness gate, a rolling restart, graceful shutdown, and rollback.
I learned this while building Levelrail, a self-hosted deploy platform. This post is my analysis, not a tutorial. The step by step commands live in the Levelrail guides, which I list at the end.
TL;DR#
- Readiness gate: a new version gets traffic only after it proves it can serve, not when its process starts.
- Rolling restart: start the new container before stopping the old one. Stop then start drops requests.
- Graceful shutdown: handle SIGTERM. Without a handler, Docker kills your app after a default 10 second grace period.
- Rollback: a refused deploy should leave the old version serving. Keep it running until the new one is proven.
- Numbers: my own local test runs are below. They are not benchmarks.
What zero downtime deployment means on a single server#
Zero downtime deployment means no request fails because you shipped a new version. On one server there is no scheduler moving pods around, so the guarantee comes from ordering: new version up and verified first, old version stopped last.
That is the whole idea. Everything else in this post is about the places where the ordering quietly breaks.
Readiness vs liveness: why a passing health check can lie#
A liveness check asks "should this container be restarted?" A readiness check asks "should this container receive traffic?" Kubernetes documents them that way: readiness probes determine when a container is ready to accept traffic, while liveness probes determine when to restart one. You do not need Kubernetes to borrow the distinction.
The failure I keep seeing is one endpoint doing both jobs. A route that returns 200 as soon as the web framework boots passes both checks while the database pool is still empty or a model is still loading. The process is alive, so it looks healthy, and it is not ready. Traffic arrives and fails.
Docker's own HEALTHCHECK is a good example of the limit. Per the Dockerfile reference, a container's health status starts as starting, becomes healthy when a check passes, and becomes unhealthy after a number of consecutive failures. That is a status label. Nothing in plain Docker reroutes traffic because of it. The proxy or deploy tool in front has to read the status and act on it.
My rule: make the readiness endpoint assert something real, such as a dependency answering a trivial query, and gate the cutover on it.
Rolling restart vs stop then start#
A rolling restart brings up the replacement before taking the old one down. A plain stop then start leaves a gap while the new container boots.
In my test run, restarting a demo app with apps restart in Levelrail is rolling. It served 152 requests with 0 failures while the restart happened. The same app with a plain stop followed by a start dropped 111 requests over about 32 seconds.
Those are numbers from one local Docker setup with a small demo app. The gap you see depends on how long your app takes to boot, which is exactly why the ordering matters more than the tool.
Graceful shutdown: SIGTERM, SIGKILL and exit 137#
When Docker stops a container, the main process receives SIGTERM, and after a grace period SIGKILL. The default grace period is 10 seconds on Linux.
If your app has no SIGTERM handler, it never starts shutting down, so it waits out the full period and gets killed. In my test, that app was SIGKILLed after the 10 second default, with exit code 137. An app with a handler stopped accepting new connections, finished in flight work, and exited 0 in about 1.5 seconds.
Two consequences:
- Requests in flight when SIGKILL lands are lost, even in a "rolling" deploy.
- Every deploy takes 10 seconds longer than it should.
The fix lives in your app, not your platform. Listen for SIGTERM, stop accepting new connections, finish current requests, close pools, exit.
Rollback: what happens when the new version fails#
The test that convinced me the gate was worth the effort: I deployed a version whose readiness check could never pass. The deploy was refused and the previous version kept serving 2,597 requests with 0 errors during the attempt.
That outcome only happens if the old version is still running while the new one is being judged. If you stop the old one first, "rollback" means starting it again, and that is downtime with extra steps.
Related trap: restart loops. Docker's restart policy waits an increasing delay, doubling from 100 milliseconds up to a maximum of 1 minute. That protects the host, but a crash looping app is still down. A restart policy is not a deploy strategy.
Single server approaches compared#
| Approach | Old version stays up during deploy | Gated on readiness | Rollback |
|---|---|---|---|
| Stop, then start | No | No | Redeploy the old build |
| Restart policy | No | No | None, it only restarts crashes |
| Blue green by hand | Yes | Only if you script it | Switch the proxy back |
| Gated platform | Yes | Yes | Automatic on a refused deploy |
Blue green by hand, with a reverse proxy and a script, is a perfectly good answer. "Gated platform" is the approach I built into Levelrail. What I wanted was the gate and the rollback to be defaults rather than things I remember to script at 11pm.
Frequently asked questions#
Can you do zero downtime deployment without Kubernetes?#
Yes. Start the new version beside the old one, gate traffic on a readiness check, and stop the old version only after the new one is serving. A reverse proxy plus a script can do it.
What is the difference between a readiness and a liveness check?#
Readiness decides whether a container receives traffic. Liveness decides whether it gets restarted. Use separate endpoints, and make readiness check real dependencies.
Why did my deploy drop requests if the health check passed?#
Usually the check only proved the process was running. Other causes: no SIGTERM handler so in flight requests were killed, or the old container stopped before the new one was ready.
How long does Docker wait before killing a container?#
Ten seconds by default on Linux, then it sends SIGKILL. You can change this with the stop timeout setting, but handling SIGTERM is the better fix.
Does Docker restart a crashing container forever?#
With an always style restart policy it keeps retrying, with a delay that doubles from 100 milliseconds up to 1 minute. It does not make a broken release work.
Where these numbers come from#
Every Levelrail figure above comes from my own test runs on 2026-10-24: Docker, a local machine, a small demo app. They show behavior, not performance on your hardware. The source is open at github.com/glincker/levelrail and the project site is levelrail.com. The Levelrail guides cover the exact commands for readiness checks, rolling restarts, graceful shutdown and rollback.
If you are weighing self-hosting against a managed platform, my reasoning is in Introducing Levelrail. If you care about being cited by AI answer engines, the structure of this post follows what I wrote in Generative Engine Optimization.