App Deployment·7 min read·

Practical Blue-Green Deployments with Nginx and Systemd Sockets

Achieve zero-packet-loss blue-green releases on a single Linux VPS using dual systemd service instances and atomic Nginx upstream reloads.

NB

Netbay Developer Relations

Netbay Engineering

On this page

Deploying application code updates by directly stopping and restarting a single service process invariably leads to HTTP 502 Bad Gateway errors for active users. While multi-node load balancers provide an obvious solution, maintaining multiple servers for small-to-medium web services introduces unnecessary infrastructure cost and maintenance overhead.

By configuring a dual-slot blue-green topology on a single Linux VPS using systemd template units and atomic Nginx upstream switching, you can achieve 100% zero-packet-loss releases. New code boots in the idle slot, executes automated smoke tests, and takes live production traffic via an instantaneous Nginx master configuration reload.

The Problem with Direct Service Restarts

When a deployment script executes systemctl restart myapp, the operating system sends SIGTERM to the active application process. Even if the service shuts down in 500 milliseconds and the new process boots in 2 seconds, any incoming TCP SYN packet arriving during that window will be rejected or wait until connection timeouts expire. Furthermore, if the newly deployed code contains a fatal syntax error, missing environment variable, or failed database migration, the service fails to boot, leaving production completely offline until an administrator notices the alert.

Blue-green deployments eliminate this risk by separating the deployment phase from the traffic activation phase. The new release boots and proves its health before a single production request touches it.

Creating Dual Systemd Template Services

Instead of defining two separate unit files, use systemd template units (app@.service) to manage the blue (port 8081) and green (port 8082) application slots:

ini
# /etc/systemd/system/app@.service
[Unit]
Description=Application Service Instance %I
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/srv/app/%I
EnvironmentFile=/srv/app/%I/.env
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3s
LimitNOFILE=65535

# Process sandboxing
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/srv/app/%I/tmp /srv/app/%I/logs

[Install]
WantedBy=multi-user.target

In /srv/app/blue/.env set PORT=8081, and in /srv/app/green/.env set PORT=8082. Both slots operate independently on local loopback addresses.

Configuring Dynamic Upstream Switching in Nginx

Configure Nginx to proxy traffic to an included upstream configuration file that points to a symbolic link:

nginx
# /etc/nginx/sites-available/app.conf
include /etc/nginx/conf.d/active-slot.conf;

server {
    listen 80;
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    location / {
        proxy_pass http://app_cluster;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
    }
}

The file /etc/nginx/conf.d/active-slot.conf is maintained as a symbolic link pointing to either /etc/nginx/conf.d/slot-blue.conf or /etc/nginx/conf.d/slot-green.conf:

nginx
# /etc/nginx/conf.d/slot-blue.conf
upstream app_cluster {
    server 127.0.0.1:8081;
    keepalive 32;
}

# /etc/nginx/conf.d/slot-green.conf
upstream app_cluster {
    server 127.0.0.1:8082;
    keepalive 32;
}
Expand
Blue-Green Deployment Topology
Blue Green Release Pipeline with Nginx

The Atomic Deployment and Cutover Script

Create /usr/local/bin/deploy-slot.sh to automate pulling new code, updating the idle slot, testing endpoints, and reloading Nginx:

bash
#!/usr/bin/env bash
set -euo pipefail

ACTIVE_TARGET=$(readlink -f /etc/nginx/conf.d/active-slot.conf)

if [[ "$ACTIVE_TARGET" == *"slot-blue.conf"* ]]; then
  CURRENT="blue"
  NEXT="green"
  NEXT_PORT=8082
else
  CURRENT="green"
  NEXT="blue"
  NEXT_PORT=8081
fi

echo "[$(date)] Current active slot: $CURRENT. Deploying to idle slot: $NEXT."

# Update code and dependencies in the idle directory
cd /srv/app/$NEXT
git pull origin main
npm ci --omit=dev

# Restart idle slot
sudo systemctl restart app@$NEXT

# Execute health smoke check against local port
echo "[$(date)] Smoke testing $NEXT on port $NEXT_PORT..."
for i in {1..10}; do
  if curl -sf http://127.0.0.1:$NEXT_PORT/health > /dev/null; then
    echo "Health check passed!"
    break
  fi
  sleep 1
  if [ "$i" -eq 10 ]; then
    echo "Deployment failed! Smoke test timed out on slot $NEXT." >&2
    exit 1
  fi
done

# Atomically switch symlink and gracefully reload Nginx
sudo ln -sf /etc/nginx/conf.d/slot-$NEXT.conf /etc/nginx/conf.d/active-slot.conf
sudo nginx -s reload

echo "[$(date)] Successfully switched live traffic to $NEXT slot!"

Nginx reloads its master process configuration gracefully: existing client requests finish processing on the old worker processes, while new TCP connections are immediately accepted by new workers routing to the freshly booted slot.

Single-Host Blue-Green Nginx Upstream Switching Nginx Edge Ingress - Port 80 / 443 SSL - active-slot.conf symlink - nginx -s reload - Graceful worker drain Zero dropped packets Slot Blue (127.0.0.1:8081) app@blue.service ACTIVE - Handling requests Slot Green (127.0.0.1:8082) app@green.service STAGING - Smoke testing Continuous Delivery - Git webhook trigger - Automatic rollback on fail - Zero cold-start latency - High-Speed SSD I/O Lucknow DC01 Single-server blue-green deployments guarantee zero downtime without load balancer fees
Automated Application Deployments and Health Checking
YouTube

Verifying Deployments and Monitoring Drains

Verify that your cutover script completes cleanly and inspect active connections during releases:

bash
# Run deployment script
sudo /usr/local/bin/deploy-slot.sh

# Confirm active symlink target
ls -l /etc/nginx/conf.d/active-slot.conf

# Check systemd process logs for both slots
journalctl -u app@blue.service -u app@green.service -f

Blue-green deployments eliminate deployment anxiety and deliver a flawless user experience on single-node cloud environments.

Deploy high-availability single-node web services on Netbay Linux VPS in under 60 seconds with High-Speed SSD in Lucknow DC01 — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance