← Start of tutorial

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.

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
13

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.

Bash

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

Check succeeds; stop on an error before continuing.

14

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.

Nginx · /etc/nginx/sites-available/wordpress

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

Check succeeds; stop on an error before continuing.

15

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.

Bash

Server terminal, sudo administrator.

(
set -euo pipefail
sudo nginx -t
sudo systemctl reload nginx
sudo ss -lntp 'sport = :443'
)
How do I know it worked?

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.

Bash

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

Trusted HTTPS, exact 200 and an actual WordPress installer body.

16

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.

URL

Allowed workstation browser address bar, not the server shell.

https://blog.example.com/wp-admin/install.php
How do I know it worked?

Dashboard works, tables exist, public home/sample and separate locked page render with cache still off.

17

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.

Bash

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';"
)
How do I know it worked?

Homepage contains the title and wp_options exists.

Explore all tutorials ↗