← 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.

Almalinux 9.8Reviewed 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

Prepare challenge-only HTTP

The new /var/lib/letsencrypt contains public challenge tokens only. After creating it, apply the exact persistent httpd_sys_content_t mapping and restore labels before first HTTP access. Private ACME account/certificate keys stay under /etc/letsencrypt and are not relabeled as public content. Stop on a pre-existing mapping collision.

Bash

WordPress server, Bash, existing sudo administrator.

(
set -euo pipefail
sudo install -d -o root -g root -m 0755 /var/lib/letsencrypt/.well-known/acme-challenge
sudo semanage fcontext -a -t httpd_sys_content_t '/var/lib/letsencrypt(/.*)?'
sudo restorecon -Rv /var/lib/letsencrypt
sudo nano /etc/nginx/conf.d/wordpress-site.conf
)
How do I know it worked?

Check succeeds; stop on an error before continuing.

14

LET’S BUILD

Write the temporary HTTP site

Replace the hostname. The only public HTTP content is ACME challenge material; WordPress is not exposed.

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

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

Check succeeds; stop on an error before continuing.

15

LET’S BUILD

Validate and enable the challenge endpoint

The dedicated main configuration includes this challenge-only site directly. Full validation must succeed before the first Nginx start. Verify the challenge from an independent external connection as well; local DNS/reachability cannot prove CA access.

Bash

WordPress server, Bash, existing sudo administrator.

(
set -euo pipefail
sudo nginx -t
sudo systemctl unmask nginx.service
sudo systemctl enable --now nginx.service
printf 'acme-path-check\n' | sudo tee /var/lib/letsencrypt/.well-known/acme-challenge/academy-check >/dev/null
curl -fsS --connect-timeout 5 --max-time 15 http://blog.example.com/.well-known/acme-challenge/academy-check | grep -Fx acme-path-check
)
How do I know it worked?

nginx -t passes and challenge body matches acme-path-check exactly.

16

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/conf.d/wordpress-site.conf
)
How do I know it worked?

Check succeeds; stop on an error before continuing.

17

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/conf.d/wordpress-site.conf

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-fpm/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.

18

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.

Explore all tutorials ↗