Installing WordPress 7.0 on Nginx

Step 7: Rate limiting and connection limits

WordPress login pages, search results, and uncached PHP endpoints are targets for brute-force and flood attacks. Define your zones in nginx.conf and apply them in your server block. The PHP handler lives in php.conf, not inline — update it rather than adding a second PHP location:

# In nginx.conf http { } block
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/m;
limit_conn_zone $binary_remote_addr zone=phplimit:10m;

# In your WordPress server block — login page
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;
}

# In php.conf — applies to all PHP requests
location ~ \.php$ {
    limit_conn phplimit 5;
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
}

The login page gets one request per minute with a burst of two (for legitimate password errors and retries). The PHP handler caps any single IP to five concurrent PHP requests — enough for a browser loading multiple uncached pages, not enough for an attacker to exhaust your PHP-FPM pool.

These examples use $binary_remote_addr (the visitor's IP as Nginx sees it). If your site runs behind Cloudflare or another reverse proxy, you'll want to restore real visitor IPs first — otherwise you're rate-limiting Cloudflare's IPs instead of your actual visitors. See Restoring Real Visitor IPs with Cloudflare and Nginx for the complete setup.


Step 8: wp-config.php essentials

A few lines in wp-config.php that make a difference in production:

// Disable the built-in cron — use a system cron job instead.
// WordPress's default cron fires on every page load, which adds
// latency for visitors and misses scheduled tasks on low-traffic sites.
define('DISABLE_WP_CRON', true);

Replace the built-in cron with a system cron job. If you have WP-CLI installed, it avoids the PHP-FPM overhead of a curl request entirely — wp cron event run --due-now fires scheduled tasks directly at the command line with no Nginx or PHP-FPM worker involved. The WP-CLI guide covers the full cron management workflow. If you're not using WP-CLI, curl with --resolve still works:

# /etc/cron.d/wordpress — or use wp-cli: wp cron event run --due-now
*/10 * * * * www-data curl -s --resolve mysite.com:443:127.0.0.1 \
  https://mysite.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
// Increase memory limits if your default PHP pool is conservative
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '256M');

// Debugging — off in production (this is the default, but be explicit)
define('WP_DEBUG', false);

Lock down wp-config.php. After the WordPress installer creates this file, it contains your database credentials. Nginx already blocks direct web access to it via the wp.conf rules from Step 3, but the file permissions from Step 1 (644) leave it world-readable — any other system user can read your database credentials. Tighten that:

# 640 = read+write for owner (your user), read-only for group (www-data),
# nothing for others. PHP-FPM runs as www-data, so it can still read
# the credentials. No other system user can.
sudo chmod 640 /var/www/mysite.com/public_html/wp-config.php

Step 9: Redirect www and HTTP to HTTPS

# Redirect www to non-www
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.mysite.com;

    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;

    return 301 https://mysite.com$request_uri;
}

# Redirect plain HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name mysite.com www.mysite.com;
    return 301 https://mysite.com$request_uri;
}

Lock down wp-config.php. After the WordPress installer creates this file, it contains your database credentials. Nginx already blocks direct web access to it via the wp.conf rules from Step 3, and the file permissions from Step 1 (644) mean other system users can read it through the "others" bit. Tighten that:

# 640 = read+write for owner (user), read-only for group (www-data),
# nothing for others. PHP-FPM runs as www-data, so it can still read
# the credentials. No other system user can.
sudo chmod 640 /var/www/mysite.com/public_html/wp-config.php