How to Do a Blue-Green Deployment With Nginx

A blue-green deployment starts the new version on a second port, waits until it is healthy, switches the nginx upstream in one graceful reload, then drains the old instance - so no request ever hits a dead backend during a release.

DevOps Engineernginxdeploymentzero-downtime

The core pattern

Instead of stopping the old instance and starting the new one in sequence (which causes a gap), blue-green keeps both running simultaneously for a few seconds:

  1. Start new instance on port 8081 while old instance on 8080 still serves traffic
  2. Poll the new instance's /health endpoint until it returns 200
  3. Swap the nginx upstream block from 8080 to 8081 and run nginx -s reload
  4. Send SIGTERM to the old process and wait for it to exit cleanly

At no point is there a moment with zero live backends.

The deploy.sh script

#!/bin/bash
set -u
cd /workspace

NEW_PORT=8081
OLD_PORT=8080

# 1. Start the new instance in the background
nohup python3 app.py $NEW_PORT >/tmp/app-${NEW_PORT}.log 2>&1 </dev/null &
disown

# 2. Wait up to 30 seconds for the new instance to become healthy
for i in $(seq 1 30); do
    if curl -sf "http://127.0.0.1:${NEW_PORT}/health" > /dev/null 2>&1; then
        echo "[deploy] healthy after ${i}s"
        break
    fi
    sleep 1
    [ "$i" -eq 30 ] && { echo "[deploy] FAIL: never healthy"; exit 1; }
done

# 3. Switch nginx upstream and gracefully reload (no restart, no downtime)
sed -i "s/127\.0\.0\.1:${OLD_PORT}/127.0.0.1:${NEW_PORT}/" /etc/nginx/nginx.conf
nginx -s reload
sleep 2   # let nginx finish draining in-flight requests

# 4. Drain and stop the old instance
OLD_PID=$(pgrep -f "python3 app.py $OLD_PORT" || true)
if [ -n "$OLD_PID" ]; then
    kill -TERM $OLD_PID
    for i in $(seq 1 10); do
        kill -0 $OLD_PID 2>/dev/null || break
        sleep 1
    done
fi

echo "[deploy] done - traffic now on :$NEW_PORT"

Why nginx -s reload and not restart

nginx -s reload sends SIGHUP to the master process. The master spins up new workers with the updated config, then drains and shuts down the old workers after their current requests finish. Existing connections are never dropped. A full service nginx restart or nginx -s stop && nginx causes a brief gap where the socket is closed - defeating the purpose.

The health-check gate is non-negotiable

Skipping the poll and immediately swapping nginx to the new port means traffic hits the new instance before it is ready to serve - turning a safe deploy into an outage. The 30-second retry loop is cheap insurance: if startup fails, the script exits with a non-zero code and the old instance keeps serving.

Nginx upstream block

The script rewrites the upstream server line in place:

upstream app {
    server 127.0.0.1:8080;   # sed rewrites 8080 -> 8081
}

After nginx -s reload, the upstream reads 127.0.0.1:8081 and all new connections go to the fresh instance.

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

What is a blue-green deployment?

Blue-green deployment runs two versions of an application simultaneously - old (blue) on one port, new (green) on another. Once the new version passes health checks, the load balancer switches over and the old version is stopped. No requests are dropped during the cutover.

How do I switch nginx to a new upstream without downtime?

Update the upstream block in nginx.conf to point at the new backend, then run nginx -s reload. The master process starts new workers with the updated config and gracefully drains the old ones - existing connections finish normally, no socket gap.

How do I wait for a new app instance to be ready before switching traffic?

Poll the new instance's health endpoint in a loop - e.g. curl -sf http://127.0.0.1:8081/health - and only proceed when it returns HTTP 200. Set a timeout (30 retries with 1-second sleeps) and exit non-zero if it never becomes healthy.

What is the difference between blue-green and canary deployments?

Blue-green switches all traffic from the old version to the new one at once (instant cutover, easy rollback). Canary shifts a small percentage first, watches metrics, then ramps up. Blue-green is simpler; canary limits blast radius.

Why is it called blue-green deployment?

The two identical environments are arbitrarily labeled blue and green. One serves live traffic while the other gets the new release, and you switch traffic between them - the names just distinguish the two slots.

Keep learning

Fix the Nginx 502 Bad GatewayDevOps projectRecover a Crashed Linux ServiceDevOps projectCorrect a Postgres Port MismatchDevOps projectDevOps roadmapStep by step to hiredDevOps interview questionsSTAR answersAll DevOps projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →