← Start of tutorial

WIUNIX ACADEMY

Install WordPress on Nginx with a privacy-safe FastCGI cache

Page 6 of 8. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Manjaro rolling · 2026-11-05Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment4 editable values
23

LET’S BUILD

Validate before reloading

A failed configuration test prevents reload. A successful graceful reload proves configuration validity, not privacy; continue with body tests.

Bash

WordPress server, Bash, existing sudo administrator.

(
set -euo pipefail
sudo nginx -t
sudo systemctl reload nginx
systemctl is-active nginx
)
How do I know it worked?

Check succeeds; stop on an error before continuing.

24

LET’S BUILD

Prove anonymous MISS then HIT

On the staging Sample page, run immediately after a new key generation before another anonymous request warms it. Expect MISS then HIT and identical public bodies. If already warm, rotate to a never-used key generation using the maintenance panels and repeat once; do not delete live cache files. Run curl from a permitted address.

Bash

Allowed staging client, Bash/curl; all cookie files are private and local to this client.

(
set -euo pipefail
umask 077
work=$(mktemp -d)
base=https://blog.example.com
for n in 1 2; do
  code=$(curl -sS --connect-timeout 5 --max-time 20 -H 'Cookie:' -H 'Authorization:' -D "$work/$n.h" -o "$work/$n.html" -w '%{http_code}' "$base/sample-page/")
  [ "$code" = 200 ]
  grep -i '^X-WP-Cache:' "$work/$n.h"
done
grep -Eiq '^X-WP-Cache: MISS[[:space:]]*$' "$work/1.h"
grep -Eiq '^X-WP-Cache: HIT[[:space:]]*$' "$work/2.h"
cmp "$work/1.html" "$work/2.html"
grep -F 'PUBLIC_CACHE_MARKER' "$work/2.html"
if grep -Fq 'CACHE_USER_A' "$work/2.html"; then
  echo 'Unexpected cached/private content or status' >&2; exit 1
else
  rc=$?; [ "$rc" = 1 ] || exit "$rc"
fi
printf 'Retained anonymous evidence: %s\n' "$work"
)
How do I know it worked?

Two 200 responses, MISS then HIT, identical public bodies with no user marker.

25

LET’S BUILD

Check real sessions and cross-user isolation

Privately export the two EXISTING staging logins as Netscape-format cookie jars using your approved session tooling. Run this in Bash/curl on the allowed workstation; jar paths are local to that client. Fake logged-in cookies are not authentication evidence. Toolbar/name must prove WordPress recognized each real user; BYPASS alone is insufficient. Preserve response bodies privately. The synthetic postpass/cart/plugin cookies additionally check the Nginx policy only, not real login or checkout.

Bash

Allowed workstation, Bash/curl, private existing staging cookie jars.

(
set -euo pipefail
umask 077
work=$(mktemp -d)
base=https://blog.example.com
# Use an EXISTING private Netscape-format jar exported from your staging login.
# It must be root/user readable only. Never paste cookies into source or logs.
jar=$HOME/wp-staging-user-a.cookies
[ -s "$jar" ]
chmod 0600 "$jar"
for n in 1 2; do
  code=$(curl -sS --connect-timeout 5 --max-time 20 -b "$jar" -D "$work/a$n.h" -o "$work/a$n.html" -w '%{http_code}' "$base/sample-page/")
  [ "$code" = 200 ]
  grep -Eiq '^X-WP-Cache: BYPASS[[:space:]]*$' "$work/a$n.h"
  grep -F 'id="wpadminbar"' "$work/a$n.html"
  grep -F 'CACHE_USER_A' "$work/a$n.html"
  if grep -Fq 'CACHE_USER_B' "$work/a$n.html"; then
    echo 'Unexpected cached/private content or status' >&2; exit 1
  else
    rc=$?; [ "$rc" = 1 ] || exit "$rc"
  fi
done
code=$(curl -sS --connect-timeout 5 --max-time 20 -H 'Cookie:' -D "$work/public.h" -o "$work/public.html" -w '%{http_code}' "$base/sample-page/")
[ "$code" = 200 ]
grep -F 'PUBLIC_CACHE_MARKER' "$work/public.html"
if grep -Fq 'CACHE_USER_A' "$work/public.html"; then
  echo 'Unexpected cached/private content or status' >&2; exit 1
else
  rc=$?; [ "$rc" = 1 ] || exit "$rc"
fi
for cookie in 'wp-postpass_demo=test' 'woocommerce_items_in_cart=1' 'plugin_session=test'; do
  code=$(curl -sS --connect-timeout 5 --max-time 20 -b "$cookie" -D "$work/bypass.h" -o "$work/bypass.html" -w '%{http_code}' "$base/sample-page/")
  [ "$code" = 200 ]
  grep -Eiq '^X-WP-Cache: BYPASS[[:space:]]*$' "$work/bypass.h"
done
jar_b=$HOME/wp-staging-user-b.cookies
[ -s "$jar_b" ]
chmod 0600 "$jar_b"
code=$(curl -sS --connect-timeout 5 --max-time 20 -b "$jar_b" -D "$work/b.h" -o "$work/b.html" -w '%{http_code}' "$base/sample-page/")
[ "$code" = 200 ]
grep -Eiq '^X-WP-Cache: BYPASS[[:space:]]*$' "$work/b.h"
grep -F 'CACHE_USER_B' "$work/b.html"
if grep -Fq 'CACHE_USER_A' "$work/b.html"; then
  echo 'Unexpected cached/private content or status' >&2; exit 1
else
  rc=$?; [ "$rc" = 1 ] || exit "$rc"
fi
code=$(curl -sS --connect-timeout 5 --max-time 20 -H 'Cookie:' -D "$work/public-after-b.h" -o "$work/public.html" -w '%{http_code}' "$base/sample-page/")
[ "$code" = 200 ]
grep -F PUBLIC_CACHE_MARKER "$work/public.html"
if grep -Fq CACHE_USER_A "$work/public.html"; then
  echo 'Unexpected cached/private content or status' >&2; exit 1
else
  rc=$?; [ "$rc" = 1 ] || exit "$rc"
fi
if grep -Fq 'CACHE_USER_B' "$work/public.html"; then
  echo 'Unexpected cached/private content or status' >&2; exit 1
else
  rc=$?; [ "$rc" = 1 ] || exit "$rc"
fi
printf 'Private evidence retained at %s; inspect locally, do not publish cookies/bodies.\n' "$work"
)
How do I know it worked?

A and B bypass and see only their own identity; a following anonymous cached body contains neither.

26

LET’S BUILD

Check queries and dynamic endpoints

Preview may redirect to login. The bounded status set allows application access/redirect behavior, but requires BYPASS and rejects 5xx/transport failures. Inspect saved body for the intended route too. Every query string, including UTM, bypasses rather than being silently stripped.

Bash

Allowed staging client, Bash/curl; all cookie files are private and local to this client.

(
set -euo pipefail
work=$(mktemp -d)
n=0
for target in '/sample-page/?preview=true' '/sample-page/?utm_source=qa' '/wp-login.php' '/wp-json/'; do
  n=$((n + 1))
  code=$(curl -sS --connect-timeout 5 --max-time 20 -D "$work/$n.h" -o "$work/$n.body" -w '%{http_code}' "https://blog.example.com$target")
  case "$code" in 200|301|302|401|403|404) ;; *) echo "Unexpected status $code" >&2; exit 1;; esac
  grep -Eiq '^X-WP-Cache: BYPASS[[:space:]]*$' "$work/$n.h"
  printf '%s -> %s, BYPASS\n' "$target" "$code"
done
)
How do I know it worked?

Check succeeds; stop on an error before continuing.

27

LET’S BUILD

Separate rewritten page keys and request methods

The key contains scheme, request method, validated host, ORIGINAL request URI and a generation. Permalink rewrites must not collapse all pages onto index.php. Only GET is cache-eligible. HEAD is deliberately bypassed too, avoiding a header-only response populating a GET key. The empty anonymous POST to this staging public page has no form action and must bypass. Nginx skips its cache engine for POST, so X-WP-Cache can be absent rather than BYPASS; it must never be a cached HIT/STALE response.

Bash

Allowed staging client, Bash/curl; all cookie files are private and local to this client.

(
set -euo pipefail
work=$(mktemp -d)
code=$(curl -sS --connect-timeout 5 --max-time 20 -D "$work/home.h" -o "$work/home.html" -w '%{http_code}' https://blog.example.com/)
[ "$code" = 200 ]
grep -F PUBLIC_HOME_MARKER "$work/home.html"
if grep -Fq PUBLIC_CACHE_MARKER "$work/home.html"; then
  echo 'Unexpected cached/private content or status' >&2; exit 1
else
  rc=$?; [ "$rc" = 1 ] || exit "$rc"
fi
code=$(curl -sS --connect-timeout 5 --max-time 20 -X POST -d '' -D "$work/post.h" -o "$work/post.html" -w '%{http_code}' https://blog.example.com/sample-page/)
[ "$code" = 200 ]
if grep -Eiq '^X-WP-Cache: (HIT|STALE|UPDATING|REVALIDATED)[[:space:]]*$' "$work/post.h"; then
  echo 'Unexpected cached/private content or status' >&2; exit 1
else
  rc=$?; [ "$rc" = 1 ] || exit "$rc"
fi
grep -F PUBLIC_CACHE_MARKER "$work/post.html"
curl -sS --head --connect-timeout 5 --max-time 20 https://blog.example.com/sample-page/
)
How do I know it worked?

Home/Sample keys differ; POST has correct public body and is never served from cache; HEAD bypasses with no body.

Explore all tutorials ↗