Zero-Downtime Deployments: How to Update a Site Without Taking It Down
Learn practical ways to deploy without visible downtime: graceful reloads, atomic release symlinks, rolling restarts and backwards-compatible migrations.
Every deployment replaces code that is currently serving visitors. Done naively, that means a window of errors: half-copied files, a restarting application, a database that no longer matches the code. For a small site a few seconds may not matter, but for a shop or a service in use around the clock, it does. Zero-downtime deployment is not magic; it is a set of habits that make each step safe to do while traffic continues.
Why updates cause downtime
Most visible failures come from three sources:
- Non-atomic file updates. Copying files over a live folder means that for a moment the site has some new files and some old ones.
- Restarting processes. Stopping and starting an app server drops connections and leaves a gap while it boots.
- Incompatible database changes. Code and schema change at different moments, so one of them is briefly wrong.
Each has a standard remedy.
Use reload, not restart, where possible
Many servers can load new configuration or code gracefully. Nginx's systemctl reload nginx starts new workers and lets old ones finish their current requests. PHP-FPM's reload does the same for PHP workers. Gunicorn restarts its workers gracefully when it receives the HUP signal (sudo systemctl reload myapp works if your unit file defines ExecReload=/bin/kill -s HUP $MAINPID). Node.js and many other runtimes do not reload themselves, which leads to the next technique.
Atomic releases with a symlink
The most useful pattern for file-based sites is to never edit the live folder. Instead, each deployment creates a new folder, and a symlink named current points at the active one:
/var/www/myapp/
releases/
20260110-1200/
20260115-0930/
shared/ # uploads, .env, persistent data
current -> releases/20260115-0930
A deployment script prepares the new release completely (download code, install dependencies, link the shared files) and only then switches the symlink in a single step:
REL=/var/www/myapp/releases/20260116-1015
ln -s /var/www/myapp/shared/.env "$REL/.env"
ln -sfn "$REL" /var/www/myapp/current.tmp
mv -Tf /var/www/myapp/current.tmp /var/www/myapp/current
Renaming a symlink over another is atomic on Linux, so every request sees either the old release or the new one, never a mixture. Point your web server's document root at /var/www/myapp/current/public (or your app's equivalent). Rolling back means pointing the link at the previous release folder, which takes a second. Delete old releases after a while, keeping the last handful.
One PHP detail: OPcache may keep serving cached files from the old path. If you disabled timestamp validation for speed, reload PHP-FPM after switching the link so it picks up the new release.
Long-running apps and health checks
Applications with long-running processes
For a Node.js, Python or similar app, switching files is not enough, because the running process holds the old code in memory. Options include:
- Two instances behind a proxy. Run the app on two ports and let Nginx balance across them in an
upstreamblock. Restart one instance, wait until it is healthy, then restart the other. Nginx stops sending traffic to an instance that is down, so users see no outage. - Blue/green. Keep two complete environments. Deploy to the idle one, test it, then switch the proxy to it by changing the upstream address and reloading Nginx. The old one stays ready for an instant rollback.
- Graceful shutdown. Make the app handle
SIGTERMby finishing in-flight requests before exiting, so a restart does not cut responses in half.
Health checks
Automatic switching is only as good as your test that the new version works. Add a lightweight route such as /health that returns 200 only when the app can reach its database and cache. A deployment script should call it on the new instance and refuse to switch traffic if it fails. This single habit prevents many failed releases from ever reaching visitors.
Database changes and when it is worth it
Expand, then contract
The database is shared by old and new code during a deployment, so schema changes must work with both. The safe sequence is:
- Expand: add the new column or table in a way the old code ignores (nullable column, new table).
- Deploy the new code that uses it, possibly writing to both old and new structures for a while.
- Contract: in a later release, remove the old column once nothing reads it.
Avoid renaming or dropping columns in the same release that stops using them. Large migrations that lock tables for minutes deserve extra care: test them on a copy of production data first, and take a backup before running them.
Is it worth it for your site?
Zero downtime has a cost in complexity. For a brochure site, an atomic symlink swap is cheap and removes nearly all risk. For an application, add health checks and two instances when users would really notice an interruption. Choose the simplest technique that meets your actual requirement, and always rehearse the rollback.
Finally, document the procedure and practise it on a staging copy, including the rollback. Teams that rehearse their deployment find the problems at their own pace instead of in front of customers. Our knowledge base has more server guides.
Deploying on Zebra Hosting
A VPS gives you the freedom to build release folders, multiple instances and Nginx upstreams. If you are moving an existing application and want the switch handled carefully, the team offers free migration.