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.
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
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.
WordPress server, Bash, existing sudo administrator.
(
set -euo pipefail
sudo nginx -t
sudo systemctl reload nginx
systemctl is-active nginx
)
Check succeeds; stop on an error before continuing.
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.
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"
)
Two 200 responses, MISS then HIT, identical public bodies with no user marker.
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.
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"
)
A and B bypass and see only their own identity; a following anonymous cached body contains neither.
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.
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
)
Check succeeds; stop on an error before continuing.
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.
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/
)
Home/Sample keys differ; POST has correct public body and is never served from cache; HEAD bypasses with no body.