← Start of tutorial

WIUNIX ACADEMY

Install WordPress on Nginx with a privacy-safe FastCGI cache

Page 3 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.

Rhel 10.2Reviewed 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

Generate independent authentication salts

Run privately; keep output out of screenshots/logs/Git. Use one different value for each of the eight key/salt placeholders below.

Bash

WordPress server, Bash, existing sudo administrator.

for n in $(seq 1 8); do openssl rand -hex 48; done
How do I know it worked?

Check succeeds; stop on an error before continuing.

Create the protected configuration file

Fresh installation only: the guard refuses an existing configuration. Do not rerun this on a live site. Save the following file panel, not shell commands.

Bash

WordPress server, Bash, existing sudo administrator.

(
set -euo pipefail
sudo test ! -e /var/www/wordpress/wp-config.php
sudo test ! -L /var/www/wordpress/wp-config.php
sudo install -o root -g wp-php -m 0640 /dev/null /var/www/wordpress/wp-config.php
sudo nano /var/www/wordpress/wp-config.php
)
How do I know it worked?

Check succeeds; stop on an error before continuing.

11

LET’S BUILD

Connect WordPress to MariaDB

Replace every placeholder and both hostname values. The table prefix is not a security control. Direct TLS termination is assumed; never trust arbitrary forwarded-HTTPS headers. The dashboard code editor is disabled. Root-owned core/themes/plugins need administrator-controlled timely updates; do not make the whole tree writable to solve an update prompt.

PHP · /var/www/wordpress/wp-config.php

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

<?php
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wp_site' );
define( 'DB_PASSWORD', 'REPLACE_WITH_THE_DATABASE_PASSWORD' );
define( 'DB_HOST', 'localhost:/var/lib/mysql/mysql.sock' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );
// Replace every placeholder with a DIFFERENT local random value.
define( 'AUTH_KEY',         'REPLACE_1' );
define( 'SECURE_AUTH_KEY',  'REPLACE_2' );
define( 'LOGGED_IN_KEY',    'REPLACE_3' );
define( 'NONCE_KEY',        'REPLACE_4' );
define( 'AUTH_SALT',        'REPLACE_5' );
define( 'SECURE_AUTH_SALT', 'REPLACE_6' );
define( 'LOGGED_IN_SALT',   'REPLACE_7' );
define( 'NONCE_SALT',       'REPLACE_8' );
$table_prefix = 'wp_';
define( 'WP_HOME', 'https://blog.example.com' );
define( 'WP_SITEURL', 'https://blog.example.com' );
define( 'FORCE_SSL_ADMIN', true );
define( 'DISALLOW_FILE_EDIT', true );
define( 'WP_DEBUG', false );
if ( ! defined( 'ABSPATH' ) ) { define( 'ABSPATH', __DIR__ . '/' ); }
require_once ABSPATH . 'wp-settings.php';
How do I know it worked?

Check succeeds; stop on an error before continuing.

12

LET’S BUILD

Check syntax and ownership

These checks do not print private configuration values.

Bash

WordPress server, Bash, existing sudo administrator.

(
set -euo pipefail
sudo php -l /var/www/wordpress/wp-config.php
if sudo grep -q REPLACE_ /var/www/wordpress/wp-config.php; then
  echo 'Replace all configuration placeholders first' >&2; exit 1
fi
sudo -u wp-php test -r /var/www/wordpress/wp-config.php
sudo -u wp-php test ! -w /var/www/wordpress/wp-config.php
sudo -u wp-php test -w /var/www/wordpress/wp-content/uploads
)
How do I know it worked?

PHP reads but cannot write config; uploads is writable; no placeholders remain.

Prepare the cache directory and policy

The worker owns this new cache directory. The complete dedicated nginx.conf panel explicitly includes conf.d in http context, so the map/cache declarations have a defined location on every family.

Bash

WordPress server, Bash, existing sudo administrator.

(
set -euo pipefail
sudo install -d -o nginx -g nginx -m 0700 /var/cache/nginx/wordpress
sudo nano /etc/nginx/conf.d/wordpress-cache.conf
)
How do I know it worked?

Check succeeds; stop on an error before continuing.

13

LET’S BUILD

Opt in only known public pages

Only homepage and sample-page are eligible. All other paths, including admin/login, preview, API/XML-RPC, cron, cart/checkout/account, feeds, searches and password-protected pages, stay outside the allowlist. Any cookie, Authorization, query string, non-GET method or percent-encoded path bypasses both lookup and storage. This covers WordPress login/post-password/comment cookies and unknown plugin sessions without maintaining an incomplete cookie-name list. Remove an allowed page and rotate the key BEFORE protecting or personalizing it. Never add geo/device/private/plugin-personalized content without separately assessing keys and response controls. UTM parameters also bypass deliberately; ignoring them requires application review.

Nginx · /etc/nginx/conf.d/wordpress-cache.conf

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

# http context: included by the dedicated nginx.conf
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:20m
    max_size=512m inactive=10m use_temp_path=off;
map $request_method $wp_skip_method { default 1; GET 0; }
# Every cookie is private until separately assessed, including WordPress,
# wp-postpass, comment_author, WooCommerce and plugin session cookies.
map $http_cookie $wp_skip_cookie { default 1; "" 0; }
map $http_authorization $wp_skip_auth { default 1; "" 0; }
map $args $wp_skip_args { default 1; "" 0; }
# Opt in ONLY known public HTML URLs. Everything else stays dynamic.
map $request_uri $wp_skip_route {
    default 1;
    "/" 0;
    "/sample-page/" 0;
}
# Defense in depth for the standard private/dynamic endpoints.
map $request_uri $wp_private_path {
    default 0;
    ~*^/(wp-admin|wp-login\.php|wp-cron\.php|wp-json|xmlrpc\.php|cart|checkout|my-account|account)(/|\?|$) 1;
    ~*% 1;
}
How do I know it worked?

512 MB bounded cache and 20 MB keys zone declared; virtual host caching is still off.

Explore all tutorials ↗