Putting it all together
The production server block includes a listen.conf snippet that consolidates SSL listening, HSTS, and .well-known paths into one include that always runs first. The full file is covered in the Securing Nginx and PHP guide — the short version is that it handles interfaces, TLS settings, and the ACME renewal path so the rest of the server block doesn't have to think about SSL infrastructure.
server {
include snippets/listen.conf;
server_name mysite.com;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
root /var/www/mysite.com/public_html;
access_log /var/log/nginx/mysite.access.log combined if=$log_ip;
error_log /var/log/nginx/mysite.error.log;
# --- WordPress-specific blocks ---
include snippets/wp.conf;
include snippets/wp-installed.conf;
# --- WordPress front controller ---
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# --- Rate-limited login ---
location = /wp-login.php {
limit_req zone=login burst=2 nodelay;
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
}
# --- Housekeeping ---
include snippets/drop.conf;
# --- Static caching ---
include snippets/static.conf;
# --- PHP processing ---
include snippets/php.conf;
}
WordPress is now running behind Nginx with CMS-specific blocks for security and routing, directory protection, aggressive static caching, wp-cron offloaded to the system scheduler, and rate limits on both the login page and all PHP handlers. PHP runs as www-data with group-write access only to the four directories it actually needs — uploads, plugins, themes, and upgrades — while the rest of the install remains read-only to the web server.
This guide is maintained as part of a modular, SSL-first framework. Each configuration is audited for production stability and modern security standards.
Compatibility: Tested against current stable releases. While optimized for the stack above, core logic remains relevant for Nginx 1.26+ and PHP 8.2+ environments.
2026-06-11: Replaced blanket www-data:www-data ownership with user:www-data — files owned by the SSH/SFTP user, group www-data. Base permissions 755/644 across the install; only wp-content/uploads, plugins, themes, and upgrade get 775/664 so PHP (running as www-data) can write only where needed and nowhere else. Added chmod 640 on wp-config.php to close the world-readable hole.
2026-06-07: Restructured the server block to use modular snippets with hyphenated naming (wp.conf, wp-installed.conf, static.conf, php.conf). Moved client_max_body_size from the server-block level into the /wp-admin location where uploads actually happen — bumped to 16M with a note about matching php.ini upload/post limits. Added explicit try_files with $is_args$args to /wp-admin. Removed ssl_trusted_certificate — Let's Encrypt phased out OCSP stapling and the chain is bundled in fullchain.pem. Moved ACME renewal out of drop.conf into listen.conf. Expanded the sensitive-file deny block (added wp-settings.php, wp-mail.php; removed defunct wp-links-opml.php). Added WP-CLI cron cross-link as the preferred alternative to curl. Introduced listen.conf as the consolidated SSL/HSTS/ACME include. FastCGI buffers tuned to 16 16k to match current production.
2026-05-26: Added fastcgi.conf note to Step 6 — distros that ship it (Debian/Ubuntu) include the full CGI variable set; fastcgi_params users should verify SCRIPT_FILENAME and HTTP_HOST are present.
2026-05-21: Bumped to WordPress 7.0. Added MySQL 8.0+ / MariaDB 10.6+ prerequisite (new database floor in WP 7.0). Added sitemap location rules. Updated stack versions: Nginx 1.30.1, PHP-FPM 8.5.6.
2026-05-11: Initial publication covering security blocks, directory protection, static caching, FastCGI tuning, rate limiting, and wp-cron offloading.