WIUNIX ACADEMY
Install WordPress on Nginx with a privacy-safe FastCGI cache
Page 4 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
Issue TLS before WordPress setup
Replace email/domain. Read and accept the CA terms before agree-tos. This registers a real ACME account/issues a real certificate. Existing HTTP owns the challenge. Failure stops before the editor; after success replace the site with the next panel. Never use browser certificate exceptions or curl -k for setup.
WordPress server, Bash, existing sudo administrator.
(
set -euo pipefail
sudo certbot certonly --webroot -w /var/lib/letsencrypt -d blog.example.com --email YOUR_REAL_EMAIL --agree-tos
sudo test -s /etc/letsencrypt/live/blog.example.com/fullchain.pem
sudo test -s /etc/letsencrypt/live/blog.example.com/privkey.pem
sudo nano /etc/nginx/sites-available/wordpress
)
Check succeeds; stop on an error before continuing.
LET’S BUILD
Protect setup with TLS and a temporary IP gate
Replace hostname, certificate paths and admin IP. Keep cache off. The server-level gate protects static and PHP paths. Upload PHP, configuration and hidden files are denied. HTTP redirects to one fixed canonical HTTPS hostname after challenges.
File contents; save in the named file, not the shell.
server {
listen 80;
listen [::]:80;
server_name blog.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/lib/letsencrypt;
default_type text/plain;
try_files $uri =404;
}
location / { return 301 https://blog.example.com$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name blog.example.com;
if ($host != blog.example.com) { return 444; }
ssl_certificate /etc/letsencrypt/live/blog.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blog.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
root /var/www/wordpress;
index index.php;
client_max_body_size 16m;
# Temporary installation gate. Replace with YOUR actual public admin IP.
allow 203.0.113.10;
deny all;
location / { try_files $uri $uri/ /index.php?$args; }
location ~* ^/wp-content/uploads/.*\.php(?:/|$) { return 403; }
location = /wp-config.php { deny all; }
location ~ /\. { deny all; }
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS on;
fastcgi_pass unix:/run/php/wordpress.sock;
fastcgi_cache off;
fastcgi_cache_key "$scheme|$request_method|$host|$request_uri|wp-v1";
fastcgi_cache_methods GET;
fastcgi_cache_valid 200 60s;
fastcgi_cache_lock on;
fastcgi_cache_bypass $wp_skip_method $wp_skip_cookie $wp_skip_auth $wp_skip_args $wp_skip_route $wp_private_path;
fastcgi_no_cache $wp_skip_method $wp_skip_cookie $wp_skip_auth $wp_skip_args $wp_skip_route $wp_private_path;
add_header X-WP-Cache $upstream_cache_status always;
}
}
Check succeeds; stop on an error before continuing.
LET’S BUILD
Check TLS and installer access
Validate/reload on server. Run the HTTPS curl on the allowed workstation if server egress is not permitted. Your address should get installer 200; an independent unpermitted client must get 403. Do not broaden the gate just to pass a test.
Server terminal, sudo administrator.
(
set -euo pipefail
sudo nginx -t
sudo systemctl reload nginx
sudo ss -lntp 'sport = :443'
)
Trusted hostname-matching TLS; installer restricted to the intended IP.
Require installer HTTP 200 from the permitted client
Run from the permitted workstation. TLS verification remains enabled. From a separate unpermitted network, repeat the same check expecting 403 instead; do not use that negative result to continue installation there.
Allowed workstation, Bash with curl.
(
set -euo pipefail
work=$(mktemp -d)
code=$(curl -sS --connect-timeout 5 --max-time 20 -D "$work/installer.h" -o "$work/installer.html" -w '%{http_code}' https://blog.example.com/wp-admin/install.php)
[ "$code" = 200 ]
grep -F WordPress "$work/installer.html"
)
Trusted HTTPS, exact 200 and an actual WordPress installer body.
LET’S BUILD
Finish installation behind the IP gate
From the allowed workstation open the HTTPS installer URL below. Choose a site title, unique non-default administrator name, strong password and real email; complete Install WordPress, sign in and confirm the dashboard. Keep credentials out of evidence. On STAGING, edit the existing Sample Page (do not create a conflicting slug) so its slug is sample-page and body includes PUBLIC_CACHE_MARKER. Create a separate Home page with PUBLIC_HOME_MARKER; choose it in Settings → Reading as static homepage. Choose Post name permalinks and save. Keep sample-page public; create a DIFFERENT password-protected page at locked-test with body LOCKED_CONTENT_MARKER. Use two staging accounts whose display names are CACHE_USER_A and CACHE_USER_B, with front-end toolbar enabled in both profiles. Each signed-in browser must show its own toolbar name; private browsing must show neither. These real markers drive the following body tests.
Allowed workstation browser address bar, not the server shell.
https://blog.example.com/wp-admin/install.php
Dashboard works, tables exist, public home/sample and separate locked page render with cache still off.
LET’S BUILD
Verify the installed database and public body
Use an allowed client address. Replace YOUR_PUBLIC_SITE_TITLE with your actual title. Require 200 and a matching body; a redirect back to installation is not success.
WordPress server, Bash, existing sudo administrator.
(
set -euo pipefail
code=$(curl -sS --connect-timeout 5 --max-time 20 -o /tmp/wp-installed-public.html -w '%{http_code}' https://blog.example.com/)
[ "$code" = 200 ]
grep -F 'YOUR_PUBLIC_SITE_TITLE' /tmp/wp-installed-public.html
sudo mariadb wordpress -e "SHOW TABLES LIKE 'wp_options';"
)
Homepage contains the title and wp_options exists.