Kate works at a WordPress and Nginx cache demonstration display in an illustrative WIUNIX Academy workspace.

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.

Intermediate85 min readUpdated
Make this guide yours

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Debian 12 (LTS)Reviewed 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

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.

01HTTPS clientTLS + temporary setup IP gate
02NginxKnown-public GET → bounded cache; all sessions/private routes → bypass
03PHP-FPM + MariaDBDedicated Unix socket, WordPress, local database
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.2 from this release’s own repository; a dedicated wordpress.sock is created by the pool step. PHP 8.2 upstream security support ends 2026-12-31. Debian 12 is in LTS with package/architecture limits, and PHP 8.2 is below WordPress’s recommended 8.3. Prefer Debian 13 for a new long-lived deployment; review actual LTS coverage rather than assuming every package is covered.
01

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.

Bash

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

Selected OS matches the server, DNS is correct and tutorial paths/ports do not collide with another installation.

02

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.

Bash

WordPress server, Bash, existing sudo administrator.

(
set -euo pipefail
sudo apt-get update
sudo apt-get install -y nginx mariadb-server php8.2-fpm php8.2-cli php8.2-mysql php8.2-curl php8.2-gd php8.2-mbstring php8.2-xml php8.2-zip php8.2-intl curl ca-certificates tar rsync openssl nano certbot
php8.2 -v
mariadb --version
nginx -v
sudo systemctl enable --now mariadb php8.2-fpm nginx
sudo ss -lntp 'sport = :3306'
)
How do I know it worked?

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.

Bash

WordPress server, Bash, existing sudo administrator.

sudo env MYSQL_HISTFILE=/dev/null mariadb
How do I know it worked?

Check succeeds; stop on an error before continuing.

03

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.

SQL

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

SHOW GRANTS lists wordpress.* permissions, with no global administrative grant.

04

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.

Bash

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

Database is wordpress, identity is wp_site@localhost, listener remains local.

05

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.

Bash

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

SHA1 and SHA256 report OK, absent destination is populated, verified download retained.

Explore all tutorials ↗