← Start of tutorial

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.

Ubuntu 26.10Reviewed 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

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.

Bash

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
How do I know it worked?

Check succeeds; stop on an error before continuing.

18

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.

Nginx · /etc/nginx/sites-available/wordpress (existing PHP location)

File contents; save in the named file, not the shell.

# In the PHP location, replace ONLY the existing cache-off line:
fastcgi_cache WORDPRESS;
How do I know it worked?

Check succeeds; stop on an error before continuing.

19

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.

20

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.

21

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.

22

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.

Explore all tutorials ↗