WIUNIX ACADEMY
Install WordPress on Nginx with a privacy-safe FastCGI cache
Page 5 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
Keep a verified configuration checkpoint
The root-only checkpoint preserves paths and compares both files before opening the editor. Keep its printed location. This is a configuration backup, not a database/uploads backup.
WordPress server, Bash, existing sudo administrator.
sudo sh -eu <<'SAVE'
umask 077
install -d -m 0700 /root/academy-backups
backup=$(mktemp -d /root/academy-backups/wp-nginx-XXXXXXXX)
cp -a --parents /etc/nginx/sites-available/wordpress /etc/nginx/conf.d/wordpress-cache.conf "$backup/"
cmp /etc/nginx/sites-available/wordpress "$backup/etc/nginx/sites-available/wordpress"
cmp /etc/nginx/conf.d/wordpress-cache.conf "$backup/etc/nginx/conf.d/wordpress-cache.conf"
printf 'Verified pre-cache configuration: %s\n' "$backup"
SAVE
sudo nano /etc/nginx/sites-available/wordpress
Check succeeds; stop on an error before continuing.
LET’S BUILD
Enable the reviewed public cache
In the existing PHP location replace only the cache-off line below. Keep both bypass and no-cache and the staging IP gate. Default Nginx handling of Set-Cookie, Cache-Control private/no-cache/no-store, Vary and X-Accel-Expires must remain intact. Never add fastcgi_ignore_headers or hide cookies to force HIT. A public successful 200 gets a fallback 60-second TTL; upstream expiration controls can take precedence. Personalized plugins without correct cookies/headers cannot be made safe by a cache header alone; keep such URLs outside the allowlist.
File contents; save in the named file, not the shell.
# In the PHP location, replace ONLY the existing cache-off line:
fastcgi_cache WORDPRESS;
Check succeeds; stop on an error before continuing.
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.