Skip to content

Zero-downtime deployment, explained for normal sites

Deploys cause downtime in three ways: files replaced under running requests, migrations that lock live tables, and cache clears that stampede the origin. The standard patterns that close those windows, what zero-downtime honestly means for WordPress versus a Node service, and when a short maintenance page is the correct call.

Two trains crossing parallel bridges while one bridge is rebuilt mid-span, a vermilion rail on the finished bridge.

By Sam Moreton

First-hand + researched

Deployments cause downtime through three mechanisms: files get replaced while requests are in flight, a database migration locks a table the live site needs, and a cache clear sends every visitor to a cold origin at once. Zero-downtime deployment is the family of patterns that closes those windows: atomic release switches, blue-green or rolling restarts, and migrations written to be compatible with both the old and new code. It is a solvable problem for most sites. And for a small site with one operator, deliberately taking thirty seconds of maintenance page during a risky deploy can be the correct engineering decision.

What breaks during a deploy

File replacement under load is the classic. The naive deploy overwrites files on the live server, and for a moment the running code is half old and half new: a request loads code from two versions of the application, and the errors it produces match neither release. Every serious deploy pattern starts by making the switch atomic, so any given request sees one version or the other, never a blend.

Migrations are the second window. A schema change on a large table can hold a lock while it runs, and the live site queues behind the lock. The deploy itself took a second; the migration behind it was the outage. The standard discipline is expand and contract: add the new column or table while both versions run, deploy the code that uses it, and remove the old shape in a later deploy, once nothing reads it.

Cache clears are the sneakiest. Flushing a full-page cache in front of real traffic sends every incoming request to the origin, which now renders every page cold. A site that carried its traffic comfortably on cached pages can fall over the moment the cache is empty, and the "outage" is a fast site doing all of its work at once. Warm the cache after the switch, or clear it gradually, and the window closes.

The standard patterns

The atomic symlink switch is the small-scale workhorse. Each deploy builds into its own timestamped directory, and a single symlink points the web root at the current one. Switching releases is one move, instant and reversible: the previous directory still exists, so rollback points the symlink back. Requests that started on the old code finish on it; new requests get the new code entirely.

Blue-green is the same idea with room to warm up. Two copies of the application sit behind a proxy: build and start the new one, check it, then switch traffic at the proxy, and keep the old copy running for as long as your nerves require. At small scale the two copies can be two directories or two containers on one box. Rolling deploys are the multi-server version, updating servers one at a time behind a load balancer, and they inherit blue-green's requirement: for a window, two versions serve traffic at once, so migrations and in-flight work have to tolerate both.

What it means for a WordPress site

WordPress on PHP has a structural advantage here: requests are short, and each request reads the files it uses. An atomic switch of the web root mostly just works. Two caveats. Opcode caches hold compiled files briefly after the switch, so the PHP layer usually wants a reload as part of the deploy, and during that reload you are effectively running a fast rolling restart. The database is the real exposure: plugin and core updates run their schema changes on activation, and an update applied directly on the live site is a migration with no rehearsal and no compatibility window.

Which points at the actual WordPress answer: staging. Deploying WordPress from Git, covered in the GitHub deploy guide, combined with one-click staging and cloning, turns a risky deploy into a rehearsed one: the update runs on a copy first, someone clicks through it, and what goes live has already run once. On Cloudways, where this site's production servers live, staging and cloning are single clicks, and the review covers what that is worth during a real week.

What it means for a Node service

A Node process holds its code in memory until it exits, so replacing files changes nothing; the restart is the deploy. Zero-downtime therefore needs choreography: start the new process, switch the proxy to it, then stop the old process gracefully, letting it finish in-flight requests and close its database connections on the way out. Blue-green at small scale is the usual shape.

Workers add a wrinkle the runbook has to name. A queue worker mid-job keeps executing the old code until the job ends, which is usually harmless. A worker restarted at the wrong moment can re-queue or drop a job mid-flight. The safe habits are a restart step in every deploy that touches worker code, and a queue backend whose jobs survive a restart.

When the maintenance page is correct

Some deploys cannot be made compatible. Dropping a column, changing a table's engine, or a bulk rewrite that locks what it touches will have a window no pattern removes, and dressing that up as zero-downtime is theatre. For a site with modest traffic and one operator, the correct move can be the plain one: schedule the work for the quietest hour, put up the maintenance page, keep the window short, keep a tested rollback beside you, and take the page down when it passes. A planned thirty seconds beats an unplanned scramble.