KBeezie

There's no place like ::1

Menu
  • Home
  • Start Here
  • Security Series
  • About

Installing WordPress 6.9.4 on Nginx

2026/05/11 in CMS Guides

Step 7: Rate limiting and connection limits

WordPress login pages, search results, and uncached PHP endpoints are targets for brute-force and flood attacks. Rate limit them:

# 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
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.3-fpm.sock;
}

location ~ \.php$ {
    limit_conn phplimit 5;
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.3-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);

Add a system cron job to hit wp-cron.php every 10 minutes (or as often as your site needs):

# /etc/cron.d/wordpress — or add to your user's crontab
*/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

Using curl with --resolve sends the request through your site's local Nginx → PHP-FPM stack at full speed, without leaving the server. It's faster than an external HTTP request and works even if you don't have DNS resolution pointing to the server yet.

// 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);

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;
}

  • ← Previous
  • 1
  • 2
  • 3
  • 4
  • Next →
Tags: wordpress, nginx, php-fpm, caching, wp-config, hardening
©2026 KBeezie | Disclaimer | Privacy Notice