Skip to content

Production Varnish bfcache change ​

This procedure is prepared but has not been executed in production. Run it on the production Varnish edge, not on mzo-webserver. The backend is confirmed as 10.0.6.215:8080; mzo-webserver owns that private address and Nginx listens on port 8080 there.

The production VCL must continue to use this backend:

vcl
backend default {
    .host = "10.0.6.215";
    .port = "8080";
    .first_byte_timeout = 600s;

Do not copy the staging or dev backend into production. Do not change the probe or PURGE ACL as part of this deployment.

Exact VCL change ​

In vcl_deliver, replace this complete block:

vcl
    # Not letting browser to cache non-static files.
    if (resp.http.Cache-Control !~ "private" && req.url !~ "^/(pub/)?(media|static)/") {
        set resp.http.Pragma = "no-cache";
        set resp.http.Expires = "-1";
        set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
    }

with:

vcl
    # Allow public HTML in the browser back/forward cache. Private HTML keeps no-store.
    if (req.url !~ "^/(pub/)?(media|static)/") {
        set resp.http.Pragma = "no-cache";
        set resp.http.Expires = "-1";
        if (resp.http.Cache-Control ~ "public") {
            set resp.http.Cache-Control = "no-cache, must-revalidate, max-age=0";
        } else {
            set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
        }
    }

This removes no-store only from public HTML. Cart, account, checkout and every other response not marked public retain no-store.

Backup and prepare ​

The commands below discover the -f file from the running varnishd process. They stop if no readable VCL is found or if the production backend does not match.

bash
LIVE_VCL="$(sudo ps -ww -eo args= | awk '
  /[v]arnishd/ {
    for (i = 1; i <= NF; i++) {
      if ($i == "-f") { print $(i + 1); exit }
      if ($i ~ /^-f\//) { sub(/^-f/, "", $i); print $i; exit }
    }
  }
')"

test -n "${LIVE_VCL}" && sudo test -r "${LIVE_VCL}" || {
  echo "STOP: the live VCL path was not detected"
  exit 1
}

sudo grep -q '10\.0\.6\.215' "${LIVE_VCL}" && \
sudo grep -q '\.port = "8080"' "${LIVE_VCL}" || {
  echo "STOP: this is not the confirmed production VCL"
  exit 1
}

STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="/var/backups/varnish/${STAMP}"
CANDIDATE="/tmp/default-bfcache-${STAMP}.vcl"
OLD_ACTIVE="$(sudo varnishadm vcl.list | awk '$1 == "active" { print $NF; exit }')"

sudo install -d -m 0750 "${BACKUP_DIR}"
sudo cp -a "${LIVE_VCL}" "${BACKUP_DIR}/default.vcl"
sudo sha256sum "${BACKUP_DIR}/default.vcl" | sudo tee "${BACKUP_DIR}/default.vcl.sha256"
sudo cp -a "${LIVE_VCL}" "${CANDIDATE}"
printf 'live=%s\nbackup=%s\nold_active=%s\n' \
  "${LIVE_VCL}" "${BACKUP_DIR}/default.vcl" "${OLD_ACTIVE}" | \
  sudo tee "${BACKUP_DIR}/deployment.txt"

Apply the exact replacement to the candidate. The command aborts unless the old block occurs exactly once.

bash
sudo python3 - "${CANDIDATE}" <<'PY'
from pathlib import Path
import sys

path = Path(sys.argv[1])
text = path.read_text()
old = '''    # Not letting browser to cache non-static files.
    if (resp.http.Cache-Control !~ "private" && req.url !~ "^/(pub/)?(media|static)/") {
        set resp.http.Pragma = "no-cache";
        set resp.http.Expires = "-1";
        set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
    }
'''
new = '''    # Allow public HTML in the browser back/forward cache. Private HTML keeps no-store.
    if (req.url !~ "^/(pub/)?(media|static)/") {
        set resp.http.Pragma = "no-cache";
        set resp.http.Expires = "-1";
        if (resp.http.Cache-Control ~ "public") {
            set resp.http.Cache-Control = "no-cache, must-revalidate, max-age=0";
        } else {
            set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
        }
    }
'''
if text.count(old) != 1:
    raise SystemExit("STOP: expected production block was not found exactly once")
path.write_text(text.replace(old, new))
PY

sudo diff -u "${LIVE_VCL}" "${CANDIDATE}"

The diff must contain only the cache-policy block above.

Verify and activate ​

Compile with the production Varnish binary before changing the active VCL. A non-zero exit code stops the deployment.

bash
sudo varnishd -C -f "${CANDIDATE}" >/dev/null

VCL_NAME="bfcache_${STAMP}"
sudo varnishadm vcl.load "${VCL_NAME}" "${CANDIDATE}"
sudo varnishadm vcl.use "${VCL_NAME}"
sudo varnishadm vcl.list
sudo varnishadm backend.list

Check the public homepage and a private route:

bash
curl -sS -D - -o /dev/null https://www.menzzo.fr/ | \
  grep -Ei '^(HTTP/|cache-control:|age:|x-cache-hits:|x-magento-cache-debug:)'

curl -sS -D - -o /dev/null https://www.menzzo.fr/checkout/cart/ | \
  grep -Ei '^(HTTP/|location:|cache-control:)'

Acceptance requires all of the following:

  • backend.list reports the backend as healthy.
  • The homepage remains successful and its public response no longer contains no-store.
  • The cart response still contains no-store.
  • No new Varnish or Magento error appears during the check.

Persist the validated candidate only after those checks pass:

bash
sudo cp -a "${CANDIDATE}" "${LIVE_VCL}"
sudo varnishd -C -f "${LIVE_VCL}" >/dev/null

No Varnish restart is required: vcl.load and vcl.use activate the change in memory, while the final copy makes it survive the next service restart.

Rollback ​

Use the backup directory printed during deployment. This compiles and activates the saved VCL, then restores the file used at the next restart.

bash
BACKUP_DIR=/var/backups/varnish/YYYYMMDDTHHMMSSZ
LIVE_VCL="$(sudo awk -F= '$1 == "live" { print $2 }' "${BACKUP_DIR}/deployment.txt")"
ROLLBACK_NAME="rollback_$(date -u +%Y%m%dT%H%M%SZ)"

sudo varnishd -C -f "${BACKUP_DIR}/default.vcl" >/dev/null
sudo varnishadm vcl.load "${ROLLBACK_NAME}" "${BACKUP_DIR}/default.vcl"
sudo varnishadm vcl.use "${ROLLBACK_NAME}"
sudo cp -a "${BACKUP_DIR}/default.vcl" "${LIVE_VCL}"
sudo varnishadm vcl.list
sudo varnishadm backend.list

Repeat the homepage and cart header checks. The homepage should return to its previous no-store policy, and the backend must remain healthy.

Internal developer documentation for Menzzo systems.