📋 Table of Contents
🎯 Overview
This guide provides a comprehensive approach to hardening a WordPress subdomain installation by implementing dedicated PHP-FPM pools, proper file permissions, SSL certificates, and various security measures. The process isolates each subdomain with its own user account, PHP pool, and security configurations.
Security Architecture Diagram
(store)
(Dedicated)
(Server Block)
(Subdomain)
- Enhanced security through user isolation
- Improved resource management
- Better logging and debugging capabilities
- Simplified SSL management with wildcard certificates
👤 User Setup & Group Configuration
The first critical step in hardening your WordPress subdomain is creating a dedicated system user that will own all WordPress files and directories. This isolation prevents potential security breaches from affecting other sites on the same server.
Step 1: Create Dedicated User
Create a new system user specifically for this subdomain. In this example, we'll use "store" as the username:
Step 2: Configure Group Memberships
Proper group configuration ensures the web server can read files while maintaining security boundaries:
Explanation: Adds the "store" user to the "www-data" group, allowing NGINX to read files owned by this user.
Explanation: Adds the "www-data" user to the "store" group, enabling two-way communication.
Explanation: Adds your non-root server user to the "store" group for administrative access. Replace "yourusername" with your actual username.
Step 3: Apply Changes
Log out and then log back in to enable the group modifications.
Step 4: System Updates
Before proceeding, ensure your system is up to date:
User & Group Relationship
File Owner
Web Server
Admin Access
🔧 PHP-FPM Pool Configuration
Creating a dedicated PHP-FPM pool for each subdomain provides resource isolation, improved security, and better performance monitoring. Each pool runs under its own user account with specific resource limits.
Step 1: Navigate to PHP Pool Directory
Step 2: Create Pool Configuration File
Copy the default pool configuration as a template for your new subdomain pool.
Step 3: Edit Pool Configuration
Configuration Changes Required:
Step 4: Create and Configure Log File
- 660 = Owner (read/write) + Group (read/write) + Others (none)
- Owner: store user can write logs
- Group: www-data can read logs for monitoring
Step 5: Verify Socket Configuration
This command confirms the socket file path. Example output:
listen = /run/php/php8.3-fpm-store.sockStep 6: Create Temporary Directory
| Directive | Value | Purpose |
|---|---|---|
| rlimit_files | 15000 | Maximum open file descriptors |
| rlimit_core | 100 | Core dump size limit |
| memory_limit | 256M | Maximum memory per PHP process |
⚙️ NGINX Server Block Configuration
The NGINX server block acts as the entry point for HTTP/HTTPS requests. Proper configuration ensures secure communication, correct PHP processing, and optimal performance.
Step 1: Navigate to Sites Directory
Step 2: Edit Server Block
Step 3: Update PHP-FPM Socket Reference
Locate the PHP processing location block and update the fastcgi_pass directive:
Step 4: Test and Reload NGINX
Expected output: "syntax is ok" and "test is successful"
Step 5: Reload PHP-FPM
Request Processing Flow
(Browser)
(Port 80/443)
(Unix Socket)
(PHP Processing)
🔐 Ownership & Permissions Management
Proper file permissions are crucial for security. We implement a two-tier approach: standard permissions for development and hardened permissions for production environments.
Navigate to Site Directory
Standard Permissions (Development)
Use these permissions during active development and content updates:
Set Ownership
Directory Permissions (770)
770 = Owner: rwx, Group: rwx, Others: ---
File Permissions (660)
660 = Owner: rw-, Group: rw-, Others: ---
Hardened Permissions (Production)
Apply these restrictive permissions once your site is fully configured and ready for production:
Base Directory Permissions (550/440)
WP-Content Directory (770/660)
WordPress needs write access to wp-content for uploads, plugins, and themes:
WP-Config Security
400 = Owner: r--, Group: ---, Others: --- (Read-only for owner)
| Location | Development | Production | Purpose |
|---|---|---|---|
| Directories (base) | 770 | 550 | Execute/traverse directories |
| Files (base) | 660 | 440 | Read-only for web server |
| wp-content (dirs) | 770 | 770 | Allow uploads/modifications |
| wp-content (files) | 660 | 660 | Allow file modifications |
| wp-config.php | 400 | 400 | Maximum security |
- Installing or updating plugins
- Installing or updating themes
- Performing WordPress core updates
🔒 Wildcard SSL Certificate Installation
A wildcard SSL certificate allows you to secure unlimited subdomains under a single certificate. This simplifies certificate management and reduces renewal overhead.
What is a Wildcard SSL Certificate?
A wildcard certificate uses an asterisk (*) as a wildcard character in the domain name field, allowing it to secure any subdomain. For example, a certificate for *.example.com will secure:
- store.example.com
- blog.example.com
- shop.example.com
- Any other subdomain
Step 1: Cloudflare API Configuration
Log in to your Cloudflare account and navigate to: My Profile → API Tokens → Global API Key
Step 2: Create Credentials Directory
Step 3: Create Credentials File
Add the following content (replace with your actual credentials):
Step 4: Secure Credentials
Step 5: Test Certificate Installation (Dry Run)
- certonly - Only obtain certificate, no automated configuration
- --dns-cloudflare - Use Cloudflare DNS validation method
- --dns-cloudflare-credentials - Path to API credentials file
- -d *.example.com - Domain to secure (wildcard)
- --preferred-challenges dns-01 - Use DNS validation method
- --dry-run - Test without actually obtaining certificate
Step 6: Install Certificate (Remove --dry-run)
Step 7: Verify Certificate Installation
Expected output will show certificate paths:
Certificate Path: /etc/letsencrypt/live/example.com-0001/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com-0001/privkey.pemStep 8: Create SSL Include File
SSL Certificate Architecture
Wildcard Cert
Subdomain 1
Subdomain 2
Subdomain 3
🛡️ Security Hardening Implementation
This section covers comprehensive security measures including HTTPS redirection, HTTP/2, HTTP/3, security headers, and rate limiting.
HTTPS Configuration
Step 1: Update Server Block for SSL
Step 2: Add HTTP to HTTPS Redirect
Step 3: Configure HTTPS Server Block
Security Headers Configuration
Security headers protect against common web vulnerabilities. Create or verify the http_headers.conf file:
Rate Limiting Configuration
Rate limiting prevents brute force attacks on wp-login.php and xmlrpc.php:
Step 1: Copy Rate Limiting Template
Step 2: Configure Rate Limiting
- zone=wp - Uses the "wp" rate limiting zone defined in nginx.conf
- burst=20 - Allows up to 20 requests in a burst
- nodelay - Processes burst requests immediately
- limit_req_status 444 - Returns error 444 (connection closed) when limit exceeded
Test and Apply Configuration
Verify HTTPS Redirect
Expected output: 301 Moved Permanently
Expected output: 200 OK
SSL/TLS Testing
Test your SSL configuration at: https://www.ssllabs.com/ssltest/
Expected rating: A+
HTTP/3 Testing
Test HTTP/3 support at: https://http3check.net/
Also verify in browser DevTools: Network tab → Protocol column → should show h3
- HTTPS redirect configured (301 permanent)
- HTTP/2 and HTTP/3 enabled
- Wildcard SSL certificate installed
- Security headers implemented
- Rate limiting active on wp-login.php and xmlrpc.php
- SSL/TLS rating: A+
⚡ WordPress Optimization
Optimize WordPress for performance through database configuration, caching, OPcache, and WP-Cron management.
Database Optimization
Revoke Unnecessary Privileges
For enhanced security, limit database user privileges to only what WordPress requires:
REVOKE ALL PRIVILEGES ON site_db.* FROM 'site_user'@'localhost';
GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;- SELECT - Read data from tables
- INSERT - Add new data
- UPDATE - Modify existing data
- DELETE - Remove data
These four privileges are sufficient for normal WordPress operation. Additional privileges (CREATE, DROP, ALTER) are only needed during plugin/theme installation or WordPress updates.
WordPress Constants (wp-config.php)
Disable Post Revisions
Increase Memory Limit
Also update PHP pool configuration:
Uncomment and modify:
Disable WP-Cron
Setup System Cron Job
Add the following line (runs every 15 minutes):
WordPress's built-in cron system runs on page loads, which can:
- Cause performance issues on high-traffic sites
- Execute unpredictably during low-traffic periods
- Consume unnecessary resources
System cron provides reliable, scheduled execution independent of site traffic.
OPcache Configuration
OPcache significantly improves PHP performance by storing precompiled script bytecode in memory.
Edit PHP Pool Configuration
Development Server Configuration
Production Server Configuration
| Directive | Value | Purpose |
|---|---|---|
| memory_consumption | 256 | Memory allocated for OPcache (MB) |
| interned_strings_buffer | 32 | Memory for storing strings (MB) |
| max_accelerated_files | 20000 | Maximum cached PHP files |
| validate_timestamps | 1 (dev) / 0 (prod) | Check file modifications (0=disabled for performance) |
| revalidate_freq | 2 | Seconds between timestamp checks (dev only) |
Caching Plugins
WP Super Cache Configuration
Create exclusion rules for dynamic content:
Redis Configuration (Advanced)
If using Redis object caching, add to wp-config.php:
Apply Changes
- Database privileges minimized
- Post revisions disabled
- Memory limit increased to 256M
- WP-Cron disabled and system cron configured
- OPcache enabled and optimized
- Caching plugin configured with proper exclusions
Performance Optimization Stack
(Static Assets)
(WP Super Cache)
(Redis)
(PHP Bytecode)
📚 Additional Resources
Testing Tools
- SSL Labs: https://www.ssllabs.com/ssltest/
- HTTP/3 Check: https://http3check.net/
- Security Headers: https://securityheaders.com/
Documentation Links
- NGINX Documentation: https://nginx.org/en/docs/
- PHP-FPM Documentation: https://www.php.net/manual/en/install.fpm.php
- Let's Encrypt: https://letsencrypt.org/docs/
- WordPress Optimization: WordPress Performance Guide
Best Practices Summary
- Isolation: Each subdomain should have its own user, PHP pool, and log files
- Permissions: Use 770/660 for development, 550/440 for production (except wp-content)
- SSL: Wildcard certificates simplify management for multiple subdomains
- Security: Implement rate limiting, security headers, and minimal database privileges
- Performance: Enable OPcache, configure caching plugins, and use system cron
- Monitoring: Regularly check logs and test SSL/security configurations
Troubleshooting Common Issues
| Issue | Possible Cause | Solution |
|---|---|---|
| 502 Bad Gateway | PHP-FPM pool not running | Check pool config and restart: sudo systemctl restart php8.3-fpm |
| Permission Denied | Incorrect file ownership | Reset ownership: sudo chown -R store:store public_html/ |
| SSL Certificate Error | DNS not propagated | Wait 5-10 minutes and retry certificate installation |
| File Upload Fails | Hardened permissions on wp-content | Ensure wp-content has 770/660 permissions |