WIUNIX ACADEMY
Install WordPress on Nginx with a privacy-safe FastCGI cache
Install WordPress, PHP-FPM and MariaDB over HTTPS, then cache only reviewed public pages and prove that sessions and password-protected content stay separate.
Make this guide yoursYOUR 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
THE OUTCOME
What will you have at the end?
A working single-site WordPress installation over HTTPS, with a dedicated PHP identity and a bounded public cache validated through actual response bodies and user isolation.
Supported operating systems and versions
Reviewed 2026-10-20. Real WordPress 7.1.3 installation, PHP-FPM 8.3.6 (Ubuntu package 8.3.6-0ubuntu0.24.04.12), MariaDB 10.11.14 and Nginx 1.24.0 were exercised in a private network namespace on Ubuntu 24.04.4 amd64 with scratch-only paths and extracted official APT packages. Body/session/password isolation, upstream cache controls, failure, invalidation and cache-off rollback passed. This is not an APT/systemd end-to-end install, public ACME/firewall test, full OS matrix, backup restore or WooCommerce test. All OS paths remain documented. DEMO images are illustrative.
Before you begin
- Fresh systemd Ubuntu/Debian server, existing sudo/SSH access, at least 2 GB RAM and 10 GB free disk for a small single-site lab; size production separately.
- Hostname under your control, correct A/AAAA, reachable 80/443, approved ACME email/account and your workstation public IP.
- Bash/curl test client and staging accounts with private cookie jars. No existing site/database, CDN/proxy or public FPM listener is assumed.
- PHP 8.5 from this release’s own repository; a dedicated wordpress.sock is created by the pool step.
LET’S BUILD
Confirm the fresh host and DNS
This is a fresh single-site installation, not a migration. Replace blog.example.com everywhere with your real hostname, YOUR_REAL_EMAIL with your email, 203.0.113.10 with your workstation public IP (add its exact IPv6 too if used). DNS A/AAAA must reach this host. Use an existing firewall policy that permits 80/443 and preserves SSH. Do not overwrite another site/database or blindly enable a new firewall. No CDN/reverse proxy is assumed; that requires a separate trusted-proxy design. Keep the installer IP-restricted until setup ends.
WordPress server, Bash, existing sudo administrator.
cat /etc/os-release
uname -m
sudo ss -lntup
sudo nginx -T 2>/dev/null | head -80
getent ahosts blog.example.com
Selected OS matches the server, DNS is correct and tutorial paths/ports do not collide with another installation.
LET’S BUILD
Install the distribution packages
Use signed repositories for the selected release, never another release’s repo or an unreviewed PPA. Ubuntu 26.10/26.04 use PHP 8.5, Ubuntu 24.04 uses 8.3, Debian 13 uses 8.4 and Debian 12 uses 8.2. A dedicated pool creates wordpress.sock below, avoiding guesses about default sockets. Package installation may start services; the WordPress web root is still absent. Port 3306 must stay absent or loopback-only.
WordPress server, Bash, existing sudo administrator.
(
set -euo pipefail
sudo apt-get update
sudo apt-get install -y nginx mariadb-server php8.5-fpm php8.5-cli php8.5-mysql php8.5-curl php8.5-gd php8.5-mbstring php8.5-xml php8.5-zip php8.5-intl curl ca-certificates tar rsync openssl nano certbot
php8.5 -v
mariadb --version
nginx -v
sudo systemctl enable --now mariadb php8.5-fpm nginx
sudo ss -lntp 'sport = :3306'
)
APT signature/dependency checks pass; record printed versions. No public 3306 listener.
Open the local database console
Fresh Ubuntu/Debian MariaDB commonly uses Unix-socket authentication for local root. Open without SQL history and never pass a real password in a shell argument.
WordPress server, Bash, existing sudo administrator.
sudo env MYSQL_HISTFILE=/dev/null mariadb
Check succeeds; stop on an error before continuing.
LET’S BUILD
Create a schema-scoped application user
In the database console replace the password with a unique random 64-character hexadecimal password from your password manager (only 0–9/a–f, safe in both SQL/PHP string literals). The account has only the schema permissions needed to install/update WordPress tables, no global administration, remote login or GRANT OPTION. GRANT takes effect immediately; no FLUSH PRIVILEGES is needed. Keep this password for wp-config. Stop on an existing schema/user collision.
MariaDB administrator console opened in the previous panel, not Bash.
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_site'@'localhost' IDENTIFIED BY 'REPLACE_WITH_A_LONG_RANDOM_PASSWORD';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX
ON wordpress.* TO 'wp_site'@'localhost';
SHOW GRANTS FOR 'wp_site'@'localhost';
EXIT;
SHOW GRANTS lists wordpress.* permissions, with no global administrative grant.
LET’S BUILD
Check identity and database exposure
Back in the server shell, enter the password only at the interactive prompt. If 3306 is public, stop and correct MariaDB binding/network policy first; this site needs no public database port.
WordPress server, Bash, existing sudo administrator.
mariadb -u wp_site -p wordpress -e 'SELECT DATABASE(), CURRENT_USER();'
sudo mariadb -e 'SELECT @@bind_address;'
sudo ss -lntp 'sport = :3306'
Database is wordpress, identity is wp_site@localhost, listener remains local.
LET’S BUILD
Verify WordPress before installing files
The archive contains architecture-independent PHP code. HTTPS and both checksums must pass before extraction/copy. SHA1 comes from the official release endpoint; SHA256 was calculated from that validated archive during this review and is not an upstream signature. Neither protects against a compromised publisher. Recheck the official release archive before reusing this pinned version later. Root owns code; only uploads is writable by the dedicated PHP identity.
WordPress server, Bash, existing sudo administrator.
(
set -euo pipefail
umask 077
work=$(mktemp -d)
cd "$work"
curl --proto '=https' --proto-redir '=https' -fL --connect-timeout 10 --max-time 180 -o wordpress-7.1.3.tar.gz https://wordpress.org/wordpress-7.1.3.tar.gz
curl --proto '=https' --proto-redir '=https' -fL --connect-timeout 10 --max-time 60 -o wordpress.sha1 https://wordpress.org/wordpress-7.1.3.tar.gz.sha1
[ "$(tr -d '\r\n ' < wordpress.sha1)" = 491a78c7676b15dffcc69130c6054b8f49228a91 ]
printf '%s %s\n' 491a78c7676b15dffcc69130c6054b8f49228a91 wordpress-7.1.3.tar.gz | sha1sum --check --strict -
printf '%s %s\n' d2a09acb6a15e3b9c471d72557753c266d6f41a79ed19bfe04cbfe49e283b2a5 wordpress-7.1.3.tar.gz | sha256sum --check --strict -
tar -xzf wordpress-7.1.3.tar.gz
sudo test ! -e /var/www/wordpress
sudo adduser --system --no-create-home --group wp-php
sudo install -d -o root -g root -m 0755 /var/www/wordpress
sudo rsync -a --chown=root:root wordpress/ /var/www/wordpress/
sudo find /var/www/wordpress -type d -exec chmod 0755 {} +
sudo find /var/www/wordpress -type f -exec chmod 0644 {} +
sudo install -d -o wp-php -g www-data -m 0755 /var/www/wordpress/wp-content/uploads
printf 'Retained verified download: %s\n' "$work"
)
SHA1 and SHA256 report OK, absent destination is populated, verified download retained.