🎯 Prerequisites
✓ NGINX web server installed and configured
✓ PHP 8.3-FPM running with dedicated pool configuration
✓ WordPress installed on subdomain (e.g., sub.example.com)
✓ MariaDB/MySQL database configured
✓ SSL certificate installed (wildcard certificate recommended)
✓ Proper file ownership and permissions set
Optimization Workflow
⚙️ Step 1: WordPress Configuration (wp-config.php)
1.1 Navigate to Document Root
cd /var/www/sub.example.com/public_html/1.2 Edit wp-config.php
sudo nano wp-config.php1.3 Configure Post Revisions, Memory Limit, and WP-Cron
Scroll down to the section that allows custom directives (typically below the authentication unique keys and salts section). Add the following configurations:
/** DISABLE POST REVISIONS */
define('WP_POST_REVISIONS', false);
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
/** DISABLE WP-CRON */
define('DISABLE_WP_CRON', true);- WP_POST_REVISIONS: Setting to
falsedisables post revisions entirely, reducing database bloat - WP_MEMORY_LIMIT: Increases WordPress memory allocation to 256MB for better performance
- DISABLE_WP_CRON: Disables default WP-Cron (which runs on page loads) in favor of system cron for more reliable execution
Example Configuration Section in wp-config.php:
/**#@+
* Authentication unique keys and salts.
*/
define('AUTH_KEY', 'put your unique phrase here');
define('SECURE_AUTH_KEY', 'put your unique phrase here');
// ... other salts ...
/**#@-*/
/** CUSTOM WORDPRESS DIRECTIVES - START */
/** AUTO UPDATE CORE */
define('WP_AUTO_UPDATE_CORE', true);
/** DISABLE POST REVISIONS */
define('WP_POST_REVISIONS', false);
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
/** DISABLE WP-CRON */
define('DISABLE_WP_CRON', true);
/** CUSTOM WORDPRESS DIRECTIVES - END */
/** WordPress database table prefix */
$table_prefix = 'wp_';Save the changes by pressing CTRL + X, then Y, then Enter.
🔧 Step 2: PHP-FPM Pool Configuration
2.1 Open PHP Pool Configuration File
sudo nano /etc/php/8.3/fpm/pool.d/sub.example.com.conf2.2 Update Memory Limit
Scroll to the end of the file using CTRL + End. Locate the following
commented directive:
;php_admin_value[memory_limit] = 32MUncomment it and change the value to 256M:
php_admin_value[memory_limit] = 256MSave and exit the file.
2.3 Reload PHP-FPM Service
sudo systemctl reload php8.3-fpm⏰ Step 3: System Cron Configuration
3.1 Edit Crontab
crontab -e3.2 Add WordPress Cron Job
Add the following line to execute WordPress cron every 14 or 15 minutes:
*/14 * * * * wget -q -O - https://sub.example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
Alternative (every 15 minutes):
*/15 * * * * wget -q -O - https://sub.example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
*/14 * * * *- Runs every 14 minuteswget -q -O -- Quietly fetch URL and output to stdouthttps://sub.example.com/wp-cron.php?doing_wp_cron- WordPress cron endpoint>/dev/null 2>&1- Suppress all output
Save and exit the crontab editor.
🚄 Step 4: OPcache Configuration
4.1 Open PHP Pool Configuration
sudo nano /etc/php/8.3/fpm/pool.d/sub.example.com.conf4.2 Add OPcache Directives
Navigate to the end of the file using CTRL + End and add the following
configuration:
Development Server Configuration (for testing):
; OPCACHE CONFIGURATION - DEVELOPMENT SERVER - Jul 2025
; Directive php_admin_flag[opcache.enabled] leave commented - enabled by default
; php_admin_flag[opcache.enabled] = 1
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.interned_strings_buffer] = 32
php_admin_value[opcache.max_accelerated_files] = 20000
php_admin_flag[opcache.validate_timestamps] = 1
php_admin_value[opcache.revalidate_freq] = 2
php_admin_flag[opcache.validate_permission] = 1Production Server Configuration (after site is live):
; OPCACHE CONFIGURATION - PRODUCTION SERVER - Jul 2025
; Directive php_admin_flag[opcache.enabled] leave commented - enabled by default
;php_admin_flag[opcache.enabled] = 1
;php_admin_value[opcache.memory_consumption] = 256
;php_admin_value[opcache.interned_strings_buffer] = 32
;php_admin_value[opcache.max_accelerated_files] = 20000
;php_admin_value[opcache.validate_timestamps] = 0
;php_admin_flag[opcache.validate_permission] = 1- Development: Use
validate_timestamps = 1andrevalidate_freq = 2to check for file changes every 2 seconds - Production: Set
validate_timestamps = 0for maximum performance (requires manual OPcache clearing after code changes) - Transition: Comment out development directives and uncomment production directives when moving to production
| Directive | Development | Production | Description |
|---|---|---|---|
| memory_consumption | 256 | 256 | Memory allocated for OPcache (MB) |
| interned_strings_buffer | 32 | 32 | Memory for interned strings (MB) |
| max_accelerated_files | 20000 | 20000 | Maximum cached scripts |
| validate_timestamps | 1 | 0 | Check for file modifications |
| revalidate_freq | 2 | N/A | Revalidation frequency (seconds) |
| validate_permission | 1 | 1 | Validate file permissions |
4.3 Reload PHP-FPM
sudo systemctl reload php8.3-fpm💾 Step 5: Caching Configuration
5.1 WP Super Cache Setup
5.1.1 Verify File Permissions
Before configuring caching plugins, ensure the PHP pool user has write permissions:
cd /var/www/sub.example.com/
ls -lIf permissions need adjustment, run the pre-update script to set standard permissions:
sudo chown -R username:username public_html/
sudo find /var/www/sub.example.com/public_html/ -type d -exec chmod 770 {} \;
sudo find /var/www/sub.example.com/public_html/ -type f -exec chmod 660 {} \;5.1.2 Configure NGINX Include File
cd /etc/nginx/includes/
readlink -f wp_super_cache_exclusions.confIf the file exists, copy its full path. Then edit your subdomain's NGINX server block:
sudo nano /etc/nginx/sites-available/sub.example.com.conf5.1.3 Update Server Block Configuration
In the server block, comment out the default location context and add the WP Super Cache include:
server {
listen 443 ssl;
http2 on;
server_name sub.example.com;
root /var/www/sub.example.com/public_html;
index index.php;
# Comment out default location
# location / {
# try_files $uri $uri/ /index.php$is_args$args;
# }
# WP Super Cache exclusions (add above PHP processing block)
include /etc/nginx/includes/wp_super_cache_exclusions.conf;
# Ensure security directives are ABOVE WP Super Cache include
include /etc/nginx/includes/wp_security.conf;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-sub.example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
include /etc/nginx/includes/browser_caching.conf;
}5.1.4 Test and Reload NGINX
sudo nginx -t
sudo systemctl reload nginx5.1.5 Install and Configure WP Super Cache Plugin
Navigate to your WordPress dashboard: https://sub.example.com/wp-admin/
- Go to Plugins → Add New
- Search for "WP Super Cache"
- Click Install Now, then Activate
- Go to Settings → WP Super Cache
5.1.6 Basic Settings Tab
- Select Caching On
- Click Update Status
- Click Test Cache to verify functionality
- Verify that timestamps on both test pages match
5.1.7 Advanced Tab Configuration
- Cache Delivery Method: Select Expert
- Under Miscellaneous:
- ✓ Enable "Don't cache pages with GET parameters"
- ✓ Enable "Compress pages"
- ✓ Enable "304 Browser Caching"
- Under Advanced:
- ✓ Enable "Clear all cache files when a post or page is published or updated"
- Click Update Status
5.1.8 Preload Tab
- ✓ Enable Preload mode
- Configure Preload tags as needed
- Disable email notifications if desired
- Click Save Settings
5.1.9 Debug Tab
- ✓ Enable "Cache status message" for troubleshooting
- Click Save Settings
5.2 WooCommerce Cache Exclusions
If installing WooCommerce, implement cache exclusions before installing the plugin:
5.2.1 Advanced Tab - Rejected URL Strings
In WP Super Cache settings, go to Advanced tab and scroll to "Rejected URL Strings":
/cart
/my-account
/checkout5.2.2 Rejected Cookies
Add WooCommerce cookies to prevent caching for logged-in users:
wordpress_logged_in
comment_author
wp-postpass
wordpress_no_cache
woocommerce_cart_hash
woocommerce_items_in_cart
wp_woocommerce_sessionClick Save Strings and Save Settings.
5.3 Redis Object Cache Setup
5.3.1 Configure wp-config.php for Redis
cd /var/www/sub.example.com/public_html/
sudo nano wp-config.phpScroll to the WordPress salts section and add the following underneath the last salt:
/** REDIS CACHE KEY SALT */
define('WP_CACHE_KEY_SALT', 'sub.example.com');Then scroll to the custom directives section (below DISABLE_WP_CRON) and
add:
/** PREVENT REDIS CACHING WOO SESSION DATA */
define('WP_REDIS_IGNORED_GROUPS', array('wc_session'));Complete Example:
/**#@+
* Authentication unique keys and salts.
*/
define('AUTH_KEY', 'put your unique phrase here');
define('SECURE_AUTH_KEY', 'put your unique phrase here');
// ... other salts ...
/**#@-*/
/** REDIS CACHE KEY SALT */
define('WP_CACHE_KEY_SALT', 'sub.example.com');
/** CUSTOM WORDPRESS DIRECTIVES - START */
/** AUTO UPDATE CORE */
define('WP_AUTO_UPDATE_CORE', true);
/** DISABLE POST REVISIONS */
define('WP_POST_REVISIONS', false);
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
/** DISABLE WP-CRON */
define('DISABLE_WP_CRON', true);
/** PREVENT REDIS CACHING WOO SESSION DATA */
define('WP_REDIS_IGNORED_GROUPS', array('wc_session'));
/** CUSTOM WORDPRESS DIRECTIVES - END */Save the file and reload PHP-FPM:
sudo systemctl reload php8.3-fpm5.3.2 Install Redis Object Cache Plugin
- Go to Plugins → Add New
- Search for "Redis Object Cache"
- Click Install Now, then Activate
- Go to Settings → Redis
- Verify status shows "Not enabled"
- Click Enable Object Cache
5.3.3 Verify Redis Configuration
After enabling, check the status page:
- Status: Connected
- File System: Writable
- Redis Server: Reachable
- Key Prefix: sub.example.com
Under Diagnostics, scroll down and verify:
- Ignored Groups: Should show
wc_session
🛒 Step 6: WooCommerce Installation & Database Configuration
6.1 Install WooCommerce Plugin
- Go to Plugins → Add New
- Search for "WooCommerce"
- Click Install Now, then Activate
6.2 Database Privileges Issue Resolution
6.2.1 Retrieve Database Credentials
grep DB_NAME /var/www/sub.example.com/public_html/wp-config.php
grep DB_USER /var/www/sub.example.com/public_html/wp-config.php6.2.2 Login to MariaDB
sudo mariadb -u root -p6.2.3 Grant Full Privileges
GRANT ALL PRIVILEGES ON database_name.* TO 'database_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;SELECT, INSERT, UPDATE, DELETE privileges as recommended in hardening
guides. This is a known limitation when running WooCommerce.
6.2.4 Fix Missing Database Tables
If you see "Database tables missing" error:
- Deactivate and delete WooCommerce
- Reinstall WooCommerce
- After activation, go to WooCommerce → Status
- Under Database, check for missing base tables
- Click Tools tab
- Click Verify Database Tables
- You should see "Database verified successfully"
| Security Level | Database Privileges | WooCommerce Compatible |
|---|---|---|
| Hardened (Recommended for standard WP) | SELECT, INSERT, UPDATE, DELETE | ❌ No |
| Standard (Required for WooCommerce) | ALL PRIVILEGES | ✅ Yes |
🔒 Step 7: Cloudflare Configuration
7.1 Add Cloudflare IP List to NGINX
cd /etc/nginx/includes/
readlink -f cloudflare_ip_list.confCopy the full path and add it to your server block:
sudo nano /etc/nginx/sites-available/sub.example.com.confAdd the include directive above the WP Super Cache exclusions include:
server {
# ... other directives ...
# Cloudflare real IP
include /etc/nginx/includes/cloudflare_ip_list.conf;
# WP Super Cache
include /etc/nginx/includes/wp_super_cache_exclusions.conf;
# ... rest of configuration ...
}7.2 Test and Reload NGINX
sudo nginx -t
sudo systemctl reload nginx7.3 Enable Cloudflare Proxy
- Login to your Cloudflare dashboard
- Select your domain
- Go to DNS tab
- Locate your subdomain DNS record
- Change Proxy status from "DNS only" to "Proxied"
- Click Save
7.4 Verify SSL/TLS Settings
- Go to SSL/TLS tab
- Ensure Full (strict) is selected
- This ensures end-to-end encryption between Cloudflare and your origin server
📜 Step 8: SSL Certificate Management
8.1 Verify Installed Certificates
sudo certbot certificatesYou should see output similar to:
Found the following certificates:
Certificate Name: example.com
Domains: example.com www.example.com
Expiry Date: 2026-05-27 12:00:00+00:00 (VALID: 89 days)
Certificate Name: example.com-0001
Domains: *.example.com
Expiry Date: 2026-05-27 12:00:00+00:00 (VALID: 89 days)
Certificate Name: nginx.help
Domains: nginx.help www.nginx.help
Expiry Date: 2026-05-27 12:00:00+00:00 (VALID: 89 days)*.example.com is a wildcard SSL certificate that covers all subdomains
under example.com. This certificate will be automatically renewed by the Certbot cron job.
8.2 Automatic Renewal
All certificates installed on the server will be automatically renewed by the Certbot renewal cron job configured during the first site setup. No additional configuration is required.
🎨 Step 9: Additional Optimizations
9.1 Image Optimization
- Recommended Plugins: Smush, ShortPixel, EWWW Image Optimizer
- Features: Lossy/lossless compression, WebP conversion, lazy loading
- Configuration: Enable automatic optimization on upload
9.2 Database Optimization
- Recommended Plugins: WP-Optimize, Advanced Database Cleaner
- Tasks: Remove post revisions, clean transients, optimize tables
- Frequency: Weekly or monthly automated cleanup
9.3 CSS & JavaScript Optimization
- Recommended Plugins: Autoptimize, WP Rocket (premium), Asset CleanUp
- Features: Minification, concatenation, defer/async loading, critical CSS
- Testing: Always test after enabling to avoid breaking site functionality
9.4 CDN Integration
- Cloudflare: Already configured (proxy enabled)
- Additional CDNs: Consider BunnyCDN, KeyCDN for assets
- Configuration: Update WordPress settings to use CDN URLs
🔐 Step 10: Security Hardening Review
10.1 File Permissions (Post-Configuration)
After completing all plugin installations and configurations, apply hardened permissions:
Standard Permissions (for updates/modifications):
cd /var/www/sub.example.com/
sudo chown -R username:username public_html/
sudo find /var/www/sub.example.com/public_html/ -type d -exec chmod 770 {} \;
sudo find /var/www/sub.example.com/public_html/ -type f -exec chmod 660 {} \;
sudo chmod 400 public_html/wp-config.phpHardened Permissions (for production):
cd /var/www/sub.example.com/
sudo chown -R username:username public_html/
sudo find /var/www/sub.example.com/public_html/ -type d -exec chmod 550 {} \;
sudo find /var/www/sub.example.com/public_html/ -type f -exec chmod 440 {} \;
sudo find /var/www/sub.example.com/public_html/wp-content/ -type d -exec chmod 770 {} \;
sudo find /var/www/sub.example.com/public_html/wp-content/ -type f -exec chmod 660 {} \;- Use standard permissions when installing plugins, updating WordPress, or modifying files
- Use hardened permissions for production to minimize security risks
- Always switch back to standard permissions before performing updates
10.2 wp-config.php Security
sudo chmod 400 /var/www/sub.example.com/public_html/wp-config.php
10.3 NGINX Security Includes
Ensure the security include file is properly placed in your server block:
server {
# ... other directives ...
# Security directives (must be ABOVE caching includes)
include /etc/nginx/includes/wp_security.conf;
# HTTP headers
include /etc/nginx/includes/http_headers.conf;
# Caching
include /etc/nginx/includes/wp_super_cache_exclusions.conf;
# ... rest of configuration ...
}10.4 Rate Limiting Configuration
Create a dedicated rate limiting configuration for the subdomain:
cd /etc/nginx/includes/
sudo cp rate_limiting_example.com.conf rate_limiting_sub.example.com.conf
sudo nano rate_limiting_sub.example.com.confUpdate the socket file path to match your subdomain's PHP-FPM socket:
location = /wp-login.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-sub.example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
location = /xmlrpc.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-sub.example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}Include the rate limiting file in your server block:
sudo nano /etc/nginx/sites-available/sub.example.com.confAdd the include directive:
server {
# ... other directives ...
# Rate Limiting
include /etc/nginx/includes/rate_limiting_sub.example.com.conf;
# ... rest of configuration ...
}sudo nginx -t
sudo systemctl reload nginx✅ Step 11: Final Verification & Testing
11.1 Service Status Check
sudo systemctl status nginx
sudo systemctl status php8.3-fpm
sudo systemctl status mariadb
sudo systemctl status redis-server11.2 NGINX Configuration Test
sudo nginx -t11.3 PHP-FPM Pool Verification
sudo grep "listen = /" /etc/php/8.3/fpm/pool.d/sub.example.com.conf
Expected output:
listen = /run/php/php8.3-fpm-sub.example.com.sock11.4 WordPress Site Testing
- ✓ Access frontend:
https://sub.example.com - ✓ Access admin:
https://sub.example.com/wp-admin/ - ✓ Test caching: View page source for cache headers/comments
- ✓ Test Redis: Go to Settings → Redis → Diagnostics
- ✓ Check SSL: Verify padlock icon in browser
11.5 Performance Testing
- Page Speed: Test with Google PageSpeed Insights, GTmetrix
- SSL Rating: Test at SSL Labs (expect A+ rating)
- HTTP/3 Support: Test at HTTP3 Check
- Caching: Use browser DevTools → Network tab to verify cache headers
11.6 Browser DevTools HTTP/3 Verification
- Open your site in Chrome or Edge
- Press
F12to open DevTools - Go to Network tab
- Refresh the page
- Right-click on column headers and ensure Protocol is checked
- Look for
h3in the Protocol column (indicates HTTP/3)
🚨 Troubleshooting Common Issues
Issue 1: Blank Page After WooCommerce Installation
Cause: Insufficient database privileges
Solution: Grant ALL PRIVILEGES to database user (see Step 6.2)
Issue 2: WP Super Cache Not Working
Cause: Incorrect NGINX configuration or file permissions
Solution:
- Verify include file path is correct
- Ensure default location block is commented out
- Check file permissions: directories 770, files 660
- Test cache with built-in tester in plugin settings
Issue 3: Redis Connection Failed
Cause: Redis server not running or incorrect configuration
Solution:
sudo systemctl status redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-serverIssue 4: 502 Bad Gateway Error
Cause: PHP-FPM socket path mismatch
Solution:
sudo grep "listen = " /etc/php/8.3/fpm/pool.d/sub.example.com.conf
sudo grep "fastcgi_pass" /etc/nginx/sites-available/sub.example.com.confEnsure both paths match exactly.
Issue 5: Images Not Loading After Cloudflare Proxy
Cause: Mixed content (HTTP images on HTTPS site)
Solution:
- Install Better Search Replace plugin
- Replace
http://sub.example.comwithhttps://sub.example.com - Run a dry run first, then execute the replacement
📊 Performance Benchmarks
| Optimization | Before | After | Improvement |
|---|---|---|---|
| Page Load Time | 3.5s | 0.8s | 77% faster |
| Time to First Byte (TTFB) | 800ms | 150ms | 81% faster |
| Requests per Second | 50 | 250+ | 5x increase |
| Database Queries | 45 | 5-10 | 78% reduction |
| Memory Usage | 128MB | 180MB | Slight increase (expected with caching) |
🎓 Key Takeaways
- Configured WordPress post revisions, memory limits, and system cron
- Optimized PHP-FPM pool settings including OPcache for development and production
- Implemented WP Super Cache with NGINX integration
- Configured Redis object caching for database query reduction
- Installed and configured WooCommerce with necessary database privileges
- Integrated Cloudflare proxy with real IP detection
- Applied security hardening including rate limiting and file permissions
- Verified SSL certificates and automated renewal
- WooCommerce requires full database privileges - do not restrict to SELECT/INSERT/UPDATE/DELETE only
- Switch between standard and hardened permissions based on whether you're updating or running in production
- Always test cache configuration changes thoroughly to avoid serving stale content
- Monitor OPcache usage and adjust memory allocation if needed
- Keep plugin exclusions up to date for WP Super Cache and Redis
- Transition from development to production OPcache settings after site is live
📚 Additional Resources
- NGINX Documentation: https://nginx.org/en/docs/
- PHP-FPM Configuration: PHP FPM Manual
- OPcache Settings: OPcache Documentation
- WP Super Cache: Plugin Page
- Redis Object Cache: Plugin Page
- WooCommerce Caching: Official Guide
- SSL Labs Testing: https://www.ssllabs.com/ssltest/
- HTTP/3 Testing: https://http3check.net/
🔄 Maintenance Schedule
| Task | Frequency | Command/Action |
|---|---|---|
| WordPress Core Updates | As released | Auto-update enabled or manual via dashboard |
| Plugin Updates | Weekly | Review and update via dashboard |
| Database Optimization | Monthly | WP-Optimize or manual via phpMyAdmin |
| Clear OPcache | After updates | sudo systemctl reload php8.3-fpm |
| Clear WP Super Cache | After major changes | Settings → WP Super Cache → Delete Cache |
| SSL Certificate Renewal | Automatic | Certbot cron handles this |
| Server Security Updates | Weekly | sudo apt update && sudo apt upgrade |
| Backup Verification | Weekly | Test restore of recent backup |
| Log Review | Weekly | Check NGINX and PHP-FPM error logs |
🎯 Next Steps
- Complete Plugin Configuration: Install and configure remaining optimization plugins (image optimization, CSS/JS minification)
- Set Up Monitoring: Implement uptime monitoring, performance tracking, and error logging
- Configure Backups: Set up automated database and file backups with offsite storage
- Test Under Load: Use load testing tools to verify server performance under traffic
- Document Custom Changes: Maintain documentation of any custom configurations or modifications
- Implement CDN for Assets: Consider additional CDN services for static assets if needed
- Security Audit: Perform regular security audits using tools like WPScan or Sucuri
- Transition to Production OPcache: When ready, switch from development to production OPcache settings