🛡️ WordPress Security Hardening Guide

Complete Nginx & PHP-FPM Security Configuration

🎯 Introduction to WordPress Hardening

WordPress hardening is a critical security practice that involves implementing multiple layers of protection to safeguard your website from cyber threats. As one of the most popular content management systems globally, WordPress is frequently targeted by attackers due to its widespread adoption.

💡 Why Hardening Matters: A properly hardened WordPress site protects both your data and your users' experience while maintaining site functionality and performance.

Security Architecture Overview

Multi-Layer Security Approach

Network Layer
Firewall & DDoS Protection
Server Layer
Nginx & SSL/TLS
Application Layer
PHP-FPM & WordPress
Data Layer
Database Security

Key Security Components

🔐 Access Control
PHP Pool Isolation, File Permissions, User Management
🔒 Encryption
SSL/TLS Certificates, HTTPS Enforcement, HTTP/3 Support
🚫 Attack Prevention
Rate Limiting, Nginx Firewall, Function Disabling
👁️ Monitoring
Log Analysis, Error Tracking, Security Auditing

📊 Log Monitoring & Analysis

Regular log monitoring is essential for identifying security issues, performance problems, and potential attacks before they cause serious damage.

Log File Locations

Log Type Location Purpose
Nginx Access Log /var/log/nginx/access.log.example.com All HTTP requests to your site
Nginx Error Log /var/log/nginx/error.log.example.com Server errors and warnings
PHP-FPM Error Log /var/log/fpm-php.example.com.log PHP execution errors

Viewing Log Files

# Navigate to nginx logs directory cd /var/log/nginx # List all log files ls # View error log with cat (displays entire file) sudo cat error.log.example.com # View error log with less (page-by-page navigation) sudo less error.log.example.com # View previous day's rotated logs sudo cat error.log.example.com.1
⚠️ Brute Force Detection: If you see repeated requests to xmlrpc.php or wp-login.php from the same IP addresses, you're experiencing a brute force attack. Rate limiting (covered later) will mitigate this.

Common Log File Patterns

✅ Normal Activity
Successful page loads (200 status), Legitimate bot crawlers, Regular user sessions
⚠️ Warning Signs
404 errors for sensitive files, Multiple failed login attempts, Unusual traffic spikes
🚨 Security Threats
SQL injection attempts, Directory traversal attempts, Malicious user agents

🏊 PHP-FPM Pool Configuration

PHP-FPM pools allow you to isolate different websites running on the same server. Each site gets its own pool with dedicated resources, user permissions, and security settings. This prevents one compromised site from affecting others.

Pool Isolation Benefits

How PHP Pools Isolate Sites

Site A Pool
User: siteA
Socket: php-siteA.sock
Site B Pool
User: siteB
Socket: php-siteB.sock
Site C Pool
User: siteC
Socket: php-siteC.sock

Each pool runs independently with its own user permissions and resource limits

Step-by-Step Pool Setup

  1. Create a System User

    First, create a dedicated system user for your website. This user will own the site files and run the PHP-FPM process.

    # Create new system user sudo useradd username # Add user to www-data group (and vice versa) sudo usermod -a -G username www-data sudo usermod -a -G www-data username
  2. Create Pool Configuration File

    Copy the default pool configuration and customize it for your site.

    # Navigate to pool directory cd /etc/php/8.3/fpm/pool.d/ # Copy default configuration sudo cp www.conf example.com.conf # Edit the new configuration sudo nano example.com.conf
  3. Configure Pool Settings

    Edit the pool configuration file with these critical settings:

    ; Pool name - change from [www] to your site identifier [example] ; User and group for this pool user = username group = username ; Unix socket for this pool (unique per site) listen = /run/php/php8.3-fpm-example.com.sock ; Resource limits rlimit_files = 15000 rlimit_core = 100 ; PHP settings specific to this pool php_flag[display_errors] = off php_admin_value[error_log] = /var/log/fpm-php.example.com.log php_admin_flag[log_errors] = on
  4. Create PHP Error Log File

    # Create log file sudo touch /var/log/fpm-php.example.com.log # Set ownership sudo chown username:www-data /var/log/fpm-php.example.com.log # Set permissions (read/write for owner and group) sudo chmod 660 /var/log/fpm-php.example.com.log
  5. Update Nginx Configuration

    Point Nginx to use the new PHP-FPM socket for this site.

    sudo nano /etc/nginx/sites-available/example.com.conf

    Update the fastcgi_pass directive:

    location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock; fastcgi_param HTTP_HOST $host; include /etc/nginx/includes/fastcgi_optimize.conf; }
  6. Reload Services

    # Test Nginx configuration sudo nginx -t # Reload PHP-FPM to activate new pool sudo systemctl reload php8.3-fpm # Reload Nginx sudo systemctl reload nginx

Advanced Pool Security Settings

1. Disable Dangerous PHP Functions

Prevent execution of functions that could be exploited by attackers:

; Add to pool configuration file php_admin_value[disable_functions] = shell_exec,opcache_get_configuration,opcache_get_status,disk_total_space,diskfreespace,dl,exec,passthru,pclose,pcntl_alarm,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_waitpid,pcntl_wait,pcntl_wexitstatus,pcntl_wifcontinued,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,popen,posix_getpwuid,posix_kill,posix_mkfifo,posix_setpgid,posix_setsid,posix_setuid,posix_uname,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,show_source,system

2. Configure Temporary Directories

# Create site-specific temp directory cd /var/www/example.com/ sudo mkdir tmp/ sudo chown username:username tmp/ sudo chmod 770 tmp/

Add to pool configuration:

php_admin_value[upload_tmp_dir] = /var/www/example.com/tmp/ php_admin_value[sys_temp_dir] = /var/www/example.com/tmp/

3. Implement open_basedir Restriction

Limit PHP file access to specific directories only:

; Restrict PHP to only access these directories php_admin_value[open_basedir] = /var/www/example.com/public_html/:/var/www/example.com/tmp/
✅ Security Benefit: With open_basedir configured, even if an attacker exploits a vulnerability, they cannot access files outside the specified directories.

🔐 SSL Certificate Setup & HTTPS Configuration

Implementing SSL/TLS certificates encrypts data transmission between your server and visitors, preventing man-in-the-middle attacks and building trust with your users.

SSL/TLS Security Flow

HTTPS Connection Process

1. Client Request
Browser requests HTTPS connection
2. Certificate
Server presents SSL certificate
3. Verification
Client validates certificate
4. Encrypted
Secure communication established

Installing Let's Encrypt Certificates

  1. Install Certbot

    # Update package list sudo apt update # Install Certbot and Cloudflare DNS plugin sudo apt install certbot python3-certbot-dns-cloudflare
  2. Obtain SSL Certificate

    # Using webroot validation method sudo certbot certonly --webroot \ -w /var/www/example.com/public_html/ \ -d example.com \ -d www.example.com
    💡 Note: Ensure your domain is already pointing to your server and Nginx is configured before running this command.
  3. Generate Strong DH Parameters

    Diffie-Hellman parameters enhance SSL/TLS security:

    # Create SSL directory cd /etc/nginx/ sudo mkdir ssl/ cd ssl/ # Generate 2048-bit DH parameters (takes several minutes) sudo openssl dhparam -out dhparam.pem 2048
  4. Create SSL Certificate Include File

    This file references your site-specific certificates:

    sudo nano /etc/nginx/ssl/ssl_certs_example.com.conf

    Add the following content:

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  5. Create Global SSL Configuration

    This configuration achieves A+ rating on SSL Labs:

    sudo nano /etc/nginx/ssl/ssl_all_sites.conf
    # A+ RATING CONFIGURATION - Updated May 2025 # SSL session settings ssl_session_cache shared:SSL:20m; ssl_session_timeout 180m; # Enable only secure protocols ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; # Strong cipher suites (single line in actual config) ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; # DH parameters for forward secrecy ssl_dhparam /etc/nginx/ssl/dhparam.pem; # Cloudflare DNS resolver resolver 1.1.1.1 1.0.0.1; resolver_timeout 15s; # Disable session tickets ssl_session_tickets off; # HSTS header (HTTP Strict Transport Security) add_header Strict-Transport-Security "max-age=31536000;" always; # Enable HTTP/3 and QUIC ssl_early_data on; add_header Alt-Svc 'h3=":$server_port"; ma=86400'; add_header x-quic 'H3'; quic_retry on;
  6. Update Nginx Server Block

    Configure your site to use HTTPS:

    # HTTP to HTTPS redirect server { listen 80; server_name example.com www.example.com; # Permanent redirect to HTTPS return 301 https://example.com$request_uri; } # HTTPS server block (primary site - includes reuseport) server { listen 443 ssl; http2 on; listen 443 quic reuseport; http3 on; server_name example.com www.example.com; root /var/www/example.com/public_html; # Include SSL certificates and configuration include /etc/nginx/ssl/ssl_certs_example.com.conf; include /etc/nginx/ssl/ssl_all_sites.conf; # ... rest of configuration ... }
    ⚠️ Important: Only use reuseport on ONE server block. Additional sites should omit this directive.
  7. Test and Reload Configuration

    # Test Nginx configuration sudo nginx -t # Reload Nginx if test passes sudo systemctl reload nginx # Verify HTTPS is working curl -I https://example.com

Automated Certificate Renewal

Let's Encrypt certificates expire after 90 days. Set up automatic renewal:

# Edit root's crontab sudo crontab -e # Add these lines (renew on 14th and 28th of each month) 00 1 14,28 * * certbot renew --force-renewal 00 2 14,28 * * systemctl reload nginx

Certificate Management Commands

# List all certificates sudo certbot certificates # Delete a certificate sudo certbot delete # Manual renewal (all certificates) sudo certbot renew # Force renewal of specific certificate sudo certbot renew --force-renewal

Verify Your SSL Configuration

🔍 SSL Labs Test
Visit ssllabs.com and test your domain. You should achieve an A+ rating with the configuration above.
🚀 HTTP/3 Verification
Use http3check.net to verify HTTP/3 and QUIC are enabled properly.
🌐 Browser DevTools
Open browser console → Network tab → Check Protocol column for "h3" indicating HTTP/3.

🛡️ HTTP Security Headers

HTTP security headers are directives sent from the server to the browser, instructing it how to handle various security aspects of the website. These headers provide defense-in-depth protection against common web vulnerabilities.

Essential Security Headers

Header Purpose Protection Against
X-Content-Type-Options Prevents MIME type sniffing Drive-by downloads, malicious file execution
X-Frame-Options Controls iframe embedding Clickjacking attacks
X-XSS-Protection Enables browser XSS filter Cross-site scripting attacks
Referrer-Policy Controls referrer information Information leakage
Permissions-Policy Controls browser features Unauthorized feature access
Strict-Transport-Security Enforces HTTPS Protocol downgrade attacks

Implementing Security Headers

  1. Create Headers Configuration File

    cd /etc/nginx/includes/ sudo nano http_headers.conf
  2. Add Header Directives

    # ------------------------------------------------------- # Referrer Policy - Choose one option # ------------------------------------------------------- # add_header Referrer-Policy "no-referrer"; add_header Referrer-Policy "strict-origin-when-cross-origin"; # add_header Referrer-Policy "unsafe-url"; # Prevent MIME type sniffing add_header X-Content-Type-Options "nosniff"; # Prevent clickjacking - only allow same origin iframes add_header X-Frame-Options "sameorigin"; # Enable XSS filter in browsers add_header X-XSS-Protection "1; mode=block"; # Control browser feature permissions add_header Permissions-Policy 'accelerometer=(), camera=(), clipboard-read=(), clipboard-write=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), fullscreen=(self "https://www.youtube.com")';
  3. Include Headers in Site Configuration

    cd /etc/nginx/sites-available/ sudo nano example.com.conf

    Add this line BEFORE your PHP processing block:

    # Include security headers include /etc/nginx/includes/http_headers.conf;
  4. Test and Apply Changes

    sudo nginx -t sudo systemctl reload nginx

Browser Caching with Security Headers

Combine caching directives with security headers for optimal performance:

sudo nano /etc/nginx/includes/browser_caching_security_headers.conf
# Cache static assets for 30 days expires 30d; # Enable ETags for cache validation etag on; # Check modification time exactly if_modified_since exact; # Public cache headers add_header Pragma "public"; add_header Cache-Control "public, no-transform"; # Try files in order try_files $uri $uri/ /index.php?$args; # Include security headers include /etc/nginx/includes/http_headers.conf; # Disable access logging for static assets access_log off;
✅ Performance Benefit: This configuration reduces server load by caching static assets while maintaining security through headers.

🔒 File Ownership & Permissions

Proper file permissions are critical for WordPress security. Incorrect permissions can allow unauthorized access or prevent your site from functioning correctly.

Permission Number System

Understanding File Permissions

Owner (User)
Read: 4
Write: 2
Execute: 1
+
Group
Read: 4
Write: 2
Execute: 1
+
Others
Read: 4
Write: 2
Execute: 1

Example: 770 = Owner (7=rwx), Group (7=rwx), Others (0=---)

Permission Levels Explained

Permission Numeric Description Use Case
rwx (Full) 7 Read, Write, Execute Owner on writable directories
rw- (Read/Write) 6 Read and Write only Standard files, config files
r-x (Read/Execute) 5 Read and Execute Hardened directories
r-- (Read only) 4 Read access only Sensitive files (wp-config.php)
--- (No access) 0 No permissions Block public access

Standard Permission Configuration

Use this configuration for development or when plugins require write access:

# Navigate to site directory cd /var/www/example.com/ # Set ownership to site user sudo chown -R username:username public_html/ # Set directory permissions to 770 (rwxrwx---) sudo find /var/www/example.com/public_html/ -type d -exec chmod 770 {} \; # Set file permissions to 660 (rw-rw----) sudo find /var/www/example.com/public_html/ -type f -exec chmod 660 {} \; # Lock down wp-config.php to read-only for owner sudo chmod 400 public_html/wp-config.php
💡 Standard Permissions: Allow both the user and www-data group to read/write, but block public access completely.

Hardened Permission Configuration

Maximum security configuration - use after site setup is complete:

# Set ownership cd /var/www/example.com/ sudo chown -R username:username public_html/ # Harden all directories to 550 (r-xr-x---) sudo find /var/www/example.com/public_html/ -type d -exec chmod 550 {} \; # Harden all files to 440 (r--r-----) sudo find /var/www/example.com/public_html/ -type f -exec chmod 440 {} \; # Allow write access ONLY to wp-content (uploads, cache, etc.) sudo find /var/www/example.com/public_html/wp-content/ -type d -exec chmod 770 {} \; sudo find /var/www/example.com/public_html/wp-content/ -type f -exec chmod 660 {} \;
⚠️ Critical: Hardened permissions prevent file modifications through WordPress admin. You'll need to temporarily change permissions to update plugins/themes.

Permission Strategy Comparison

📝 Standard Permissions
Pros: Easy plugin/theme updates, automatic updates work
Cons: Slightly less secure
Use when: Actively developing or frequently updating
🔐 Hardened Permissions
Pros: Maximum security, prevents unauthorized changes
Cons: Manual permission changes needed for updates
Use when: Site is stable and changes are infrequent

Temporary Permission Changes

When you need to update plugins with hardened permissions:

# 1. Temporarily relax permissions cd /var/www/example.com/ sudo find public_html/ -type d -exec chmod 770 {} \; sudo find public_html/ -type f -exec chmod 660 {} \; # 2. Perform your updates in WordPress admin # 3. Re-apply hardened permissions sudo find public_html/ -type d -exec chmod 550 {} \; sudo find public_html/ -type f -exec chmod 440 {} \; sudo find public_html/wp-content/ -type d -exec chmod 770 {} \; sudo find public_html/wp-content/ -type f -exec chmod 660 {} \;

🚧 Nginx Security Directives

Nginx security directives act as a firewall layer, blocking common attack patterns and protecting WordPress core files from unauthorized access.

Create Security Configuration File

cd /etc/nginx/includes/ sudo nano nginx_security_directives.conf

WordPress Security Ruleset

## # WORDPRESS-SAFE NGINX 8G (based) FIREWALL Ruleset # Low false-positive risks # Updated December 2026 ## # Disable favicon logging (reduces log clutter) location = /favicon.ico { access_log off; log_not_found off; } # Deny access to sensitive core and config files location = /wp-config.php { deny all; } location = /wp-admin/install.php { deny all; } location ~* ^/(readme|license|licence)\.(txt|html)$ { deny all; } location ~* \.ini$ { deny all; } # Harden WordPress core - block direct PHP execution location ~* ^/wp-includes/[^/]+\.php$ { deny all; } location ~* ^/wp-includes/js/tinymce/langs/.+\.php$ { deny all; } location ~* ^/wp-includes/theme-compat/ { deny all; } # Prevent PHP execution in uploads, themes, and plugins location ~* ^/wp-content/uploads/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; } location ~* ^/wp-content/plugins/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; } location ~* ^/wp-content/themes/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; } # Protect upgrade and backup directories location ~* ^/wp-content/(upgrade|backup-.*)/.*\.(php[1-8]?|pht|phtml?|phps)$ { deny all; } # Block development and dependency files/directories location ~* (composer\.(json|lock)|package\.json|yarn\.lock|/vendor/|/node_modules/) { deny all; } # Block dangerous HTTP methods (optional) if ($request_method ~* ^(TRACE|DELETE|TRACK)$) { return 403; } # Block known vulnerability scanners (optional) if ($http_user_agent ~* (nikto|sqlmap|masscan|nmap|dirbuster|acunetix|openvas)) { return 444; }

Apply Security Configuration

# Edit your site configuration cd /etc/nginx/sites-available/ sudo nano example.com.conf # Add BEFORE your PHP processing block: include /etc/nginx/includes/nginx_security_directives.conf; # Test and reload sudo nginx -t sudo systemctl reload nginx

Allowing Plugin PHP Execution (When Needed)

Some plugins require PHP execution. Create specific exceptions:

Testing PHP Blocking: Create a test file to verify PHP execution is blocked in the plugins directory.
# Create test PHP file cd /var/www/example.com/ sudo nano public_html/wp-content/plugins/test556.php

Content of test file:

<?php phpinfo(); ?>

Access via browser: https://example.com/wp-content/plugins/test556.php

Result: Should return 403 Forbidden (PHP execution blocked ✓)

Creating Exceptions for Specific Plugins

If a plugin legitimately needs PHP execution, add an exception:

# Add AFTER your main PHP processing block location = /wp-content/plugins/plugin-name/file.php { allow all; include snippets/fastcgi-php.conf; fastcgi_param HTTP_HOST $host; fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock; include /etc/nginx/includes/fastcgi_optimize.conf; }
# Test and restart services sudo nginx -t sudo systemctl reload nginx && sudo systemctl restart php8.3-fpm
✅ Security Principle: Block all PHP execution by default, then whitelist only specific files that legitimately need it.

Security Directives Impact

🛡️ Protected Files
wp-config.php, readme files, .ini files, backup directories
🚫 Blocked Attacks
Direct PHP uploads, theme/plugin vulnerabilities, scanner bots
⚡ Performance
Reduced server load from blocked malicious requests

⏱️ Rate Limiting & Brute Force Protection

Rate limiting restricts the number of requests a client can make in a given time period, effectively preventing brute force attacks on login pages and XML-RPC.

How Rate Limiting Works

Request Flow with Rate Limiting

Request 1-30
Allowed (within limit)
Request 31-50
Burst queue (delayed)
Request 51+
Blocked (444 error)

Zone: 30 requests/minute, Burst: 20 additional requests

Configure Global Rate Limit Zone

  1. Edit Main Nginx Configuration

    cd /etc/nginx/ sudo nano nginx.conf
  2. Add Rate Limit Zone

    Add this in the http context (before any server blocks):

    ## # Rate Limiting Configuration ## # Creates a 10MB zone tracking client IPs # Allows 30 requests per minute per IP limit_req_zone $binary_remote_addr zone=wp:10m rate=30r/m;

Create Site-Specific Rate Limit Rules

cd /etc/nginx/includes/ sudo nano rate_limiting_example.com.conf

Add the following content (customize the socket path):

# Rate limit wp-login.php to prevent brute force attacks location = /wp-login.php { # Use wp zone, allow burst of 20, process immediately limit_req zone=wp burst=20 nodelay; # Return 444 (close connection) when limit exceeded limit_req_status 444; include snippets/fastcgi-php.conf; fastcgi_param HTTP_HOST $host; # MODIFY: Update socket path for your site fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock; include /etc/nginx/includes/fastcgi_optimize.conf; } # Rate limit xmlrpc.php (commonly abused for attacks) location = /xmlrpc.php { limit_req zone=wp burst=20 nodelay; limit_req_status 444; include snippets/fastcgi-php.conf; fastcgi_param HTTP_HOST $host; # MODIFY: Update socket path for your site fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock; include /etc/nginx/includes/fastcgi_optimize.conf; }

Apply Rate Limiting to Your Site

# Edit site configuration cd /etc/nginx/sites-available/ sudo nano example.com.conf # Add this include directive in your server block: include /etc/nginx/includes/rate_limiting_example.com.conf; # Test configuration sudo nginx -t # Reload Nginx sudo systemctl reload nginx

Rate Limiting Parameters Explained

Parameter Value Explanation
zone=wp wp Name of the rate limit zone defined in nginx.conf
rate 30r/m 30 requests per minute per IP address
burst 20 Allow 20 additional requests in queue
nodelay - Process burst requests immediately (no delay)
limit_req_status 444 Return 444 (close connection) when exceeded
✅ Protection Achieved: Legitimate users can log in normally, but attackers attempting hundreds of login attempts per minute are blocked.

Testing Rate Limiting

You can verify rate limiting is working by monitoring your logs during a simulated attack or by using tools like Apache Bench:

# Send 100 requests to wp-login.php ab -n 100 -c 10 https://example.com/wp-login.php # Check access log for 444 responses sudo tail -f /var/log/nginx/access.log.example.com
📈 Expected Behavior
First 30 requests: Processed normally
Next 20 requests: Delayed but processed
Remaining requests: Blocked with 444 status
🎯 Tuning Tips
Increase rate for high-traffic sites
Decrease burst for tighter security
Monitor logs to find optimal settings

🗄️ Database Security Hardening

WordPress database users often have excessive privileges. Restricting database permissions follows the principle of least privilege, reducing the impact of SQL injection vulnerabilities.

Database Permission Levels

Privilege Required Purpose
SELECT ✅ Yes Read data from tables
INSERT ✅ Yes Add new records (posts, comments, etc.)
UPDATE ✅ Yes Modify existing records
DELETE ✅ Yes Remove records from tables
CREATE ⚠️ Optional Only needed for plugin installation
ALTER ⚠️ Optional Only needed for plugin updates
INDEX ⚠️ Optional Only needed for performance optimization
DROP ❌ No Dangerous - can delete entire tables

Restrict Database Privileges

  1. Connect to MySQL

    # Log in to MySQL as root sudo mysql -u root -p
  2. Revoke All Current Privileges

    -- Revoke all privileges from database user REVOKE ALL PRIVILEGES ON site_db.* FROM 'site_user'@'localhost';
  3. Grant Minimal Required Privileges

    -- Grant only SELECT, INSERT, UPDATE, DELETE GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'localhost'; -- Apply changes FLUSH PRIVILEGES;
  4. Temporary Privileges for Plugin Installation

    When installing plugins that create tables, temporarily grant additional privileges:

    -- Temporarily add CREATE, ALTER, INDEX for plugin installation GRANT CREATE, ALTER, INDEX ON site_db.* TO 'site_user'@'localhost'; FLUSH PRIVILEGES; -- After installation, revoke again: REVOKE CREATE, ALTER, INDEX ON site_db.* FROM 'site_user'@'localhost'; FLUSH PRIVILEGES;
💡 Best Practice: Keep a separate database user with full privileges for maintenance, but use the restricted user for WordPress runtime operations.

Verify Database Privileges

-- Show current privileges for user SHOW GRANTS FOR 'site_user'@'localhost';

Expected output for secure configuration:

GRANT USAGE ON *.* TO 'site_user'@'localhost' GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'localhost'

Additional WordPress Security Measures

Disable File Modifications from Dashboard

Add this constant to wp-config.php to prevent file editing through WordPress admin:

// Disable file edit/modification via dashboard define('DISALLOW_FILE_MODS', true);
⚠️ Important: After adding this constant, you cannot install/update plugins or themes through the dashboard. All changes must be done via SFTP or command line.

Reload PHP-FPM After wp-config Changes

# Reload PHP-FPM to apply configuration changes sudo systemctl reload php8.3-fpm

Security Hardening Summary

✅ Completed
PHP Pool Isolation ✓
SSL/TLS Encryption ✓
Security Headers ✓
File Permissions ✓
Nginx Firewall ✓
Rate Limiting ✓
Database Restrictions ✓
📋 Maintenance
Monitor logs regularly
Update SSL certificates
Review permissions after updates
Audit database privileges
Test backups regularly

🎓 Conclusion

By implementing these comprehensive security measures, you have significantly hardened your WordPress installation against common and advanced threats. This multi-layered approach ensures defense in depth, where even if one security measure is bypassed, others remain in place to protect your site.

🎯 Achievement Unlocked: Your WordPress site now implements enterprise-grade security practices including isolation, encryption, access control, attack prevention, and monitoring.

Ongoing Security Practices

  • Review log files weekly for suspicious activity
  • Keep WordPress core, plugins, and themes updated
  • Regularly test backups and disaster recovery procedures
  • Monitor SSL certificate expiration and renewal
  • Audit file permissions after major changes
  • Review and update security headers as standards evolve
  • Test rate limiting effectiveness periodically
  • Document all custom configurations for future reference

Your Security Stack

Layer 1: Cloudflare / CDN (DDoS protection, WAF)

Layer 2: Nginx (SSL/TLS, Security headers, Rate limiting)

Layer 3: PHP-FPM (Pool isolation, Function restrictions)

Layer 4: WordPress (File permissions, Constants)

Layer 5: Database (Restricted privileges)

Layer 6: Monitoring (Log analysis, Alerts)

Remember: Security is not a one-time task but an ongoing process. Stay informed about new threats and continuously improve your security posture.