1. Overview & Resource Planning
💡 Important: This guide covers hosting an additional WordPress site as a subdomain
on your existing NGINX server. Proper resource allocation is critical for optimal performance.
Server Resource Requirements
| Site Type |
Minimum RAM |
Recommended RAM |
Notes |
| Static WordPress Site |
1 GB |
2 GB |
Minimal dynamic content |
| WooCommerce Site |
2 GB |
4 GB |
Heavy dynamic content |
| 3 Sites (2 Static + 1 WooCommerce) |
4 GB |
8 GB |
Optimal performance |
Multi-Site Server Architecture
NGINX
Reverse Proxy
→
PHP-FPM
Pool 1
→
WordPress
Site 1
NGINX
Reverse Proxy
→
PHP-FPM
Pool 2
→
WordPress
Site 2
NGINX
Reverse Proxy
→
PHP-FPM
Pool 3
→
WordPress
Subdomain
Initial Resource Check
Before proceeding, verify your server's current resource usage:
cd
mkdir wp_bash_scripts/
cd wp_bash_scripts/
2. Bash Scripts Setup
Creating the Subdomain Server Block Script
This bash script automates the creation of NGINX server block configuration for your subdomain:
nano 3.sub_domain_server_block
#!/bin/bash
# Function to create directories and files if they don't exist
create_files_and_directories() {
# Check if the includes directory exists, if not create it
if [ ! -d "/etc/nginx/includes" ]; then
sudo mkdir -p /etc/nginx/includes
fi
# Check if fastcgi_optimize.conf exists, if not create it
if [ ! -f "/etc/nginx/includes/fastcgi_optimize.conf" ]; then
sudo tee /etc/nginx/includes/fastcgi_optimize.conf > /dev/null <<EOF
fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 512k;
fastcgi_buffers 512 16k;
fastcgi_busy_buffers_size 1m;
fastcgi_temp_file_write_size 4m;
fastcgi_max_temp_file_size 4m;
fastcgi_intercept_errors on;
EOF
fi
# Check if browser_caching.conf exists, if not create it
if [ ! -f "/etc/nginx/includes/browser_caching.conf" ]; then
sudo tee /etc/nginx/includes/browser_caching.conf > /dev/null <<EOF
location ~*
\.(webp|3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$
{
add_header Cache-Control "public, no-transform";
access_log off;
expires 365d;
}
location ~* \.(js)$ {
add_header Cache-Control "public, no-transform";
access_log off;
expires 30d;
}
location ~* \.(css)$ {
add_header Cache-Control "public, no-transform";
access_log off;
expires 30d;
}
location ~* \.(eot|svg|ttf|woff|woff2)$ {
add_header Cache-Control "public, no-transform";
access_log off;
expires 30d;
}
EOF
fi
}
# Function to create subdomain.domain.com.conf file
create_subdomain_conf_file() {
subdomain=$1
filename="/etc/nginx/sites-available/$subdomain.conf"
# Define server_name for subdomain without www prefix
server_name="$subdomain"
# Check if the subdomain.domain.com.conf file exists, if not create it
if [ ! -f "$filename" ]; then
sudo tee "$filename" > /dev/null <<EOF
server {
listen 80;
server_name $server_name;
root /var/www/$subdomain/public_html;
index index.php;
location / {
try_files \$uri \$uri/ /index.php\$is_args\$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
include /etc/nginx/includes/browser_caching.conf;
access_log /var/log/nginx/access_$subdomain.log combined buffer=256k flush=60m;
error_log /var/log/nginx/error_$subdomain.log;
}
EOF
fi
}
# Main script starts here
# Ask for subdomain name
read -p "Enter subdomain name: " subdomain_name
# Call function to create directories and files
create_files_and_directories
# Call function to create subdomain.domain.com.conf file
create_subdomain_conf_file "$subdomain_name"
# Display success message
echo "Subdomain configuration files created successfully."
✅ Script Features:
- Automatically creates necessary NGINX include directories
- Generates FastCGI optimization configuration
- Sets up browser caching rules
- Creates subdomain-specific server block
- Configures logging for the subdomain
3. Subdomain Server Block Configuration
Understanding the Server Block Structure
The server block defines how NGINX handles requests for your subdomain. Key components include:
- Listen Directive: Port 80 for HTTP (initially)
- Server Name: Your subdomain (e.g., sub.example.com)
- Root Directory: Document root path
- PHP Processing: FastCGI configuration
- Logging: Access and error log paths
📝 Note: The initial configuration uses HTTP (port 80). We'll upgrade to HTTPS with
SSL certificates in later steps.
4. WordPress Installation
Database Preparation
Create a dedicated database and user for your subdomain WordPress installation using bash scripts (as
per your first and second site installations):
- Create Database
- Generate Admin User and Password
- Generate SALTS
- Add WP constants to text editor
Download and Configure WordPress
cd
wget https://wordpress.org/latest.tar.gz
ls
tar xf latest.tar.gz
ls
Rename and edit wp-config.php:
nano wp-config.php
⚠️ Configuration Changes Required:
- Database Name
- Database User
- Database Password
- Authentication Salts
- Table Prefix
- WordPress Constants
Deploy WordPress Files
Copy WordPress files to the document root and set proper ownership:
cd
sudo rsync -artv wordpress/ /var/www/sub.example.com/public_html/
cd /var/www/sub.example.com/
sudo chown -R www-data:www-data public_html/
ls -l
cd public_html/
ls -l
✅ Next Step: Complete the WordPress installation by accessing your subdomain in a
web browser and following the installation wizard.
5. WordPress Hardening
PHP-FPM Pool Isolation
Creating dedicated PHP-FPM pools for each site enhances security by isolating processes:
Create Dedicated System User
sudo useradd username
sudo usermod -a -G username www-data
sudo usermod -a -G www-data username
Create PHP-FPM Pool Configuration
cd /etc/php/8.3/fpm/pool.d/
ls
sudo cp www.conf sub.example.com.conf
ls
sudo nano sub.example.com.conf
; pool name ('www' here)
[sub.example]
; Default Values: The user is set to master process running user by default.
; If the group is not set, the user's group is used.
user = username
group = username
; Note: This value is mandatory.
listen = /run/php/php8.3-fpm-sub.example.com.sock
; Set open file descriptor rlimit.
rlimit_files = 15000
; Set max core size rlimit.
rlimit_core = 100
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.www.log
php_admin_flag[log_errors] = on
Update NGINX Socket Reference
sudo grep "listen = /" sub.example.conf
sudo nano /etc/nginx/sites-available/sub.example.com.conf
Update the fastcgi_pass directive:
fastcgi_pass unix:/run/php/php8.3-fpm-sub.example.com.sock;
File Ownership and Permissions
Standard Permissions (Development)
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 {} \;
Hardened Permissions (Production)
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
{} \;
Permission Structure
| Directory/File |
Development |
Production |
| Directories |
770 |
550 |
| Files |
660 |
440 |
| wp-content/ dirs |
770 |
770 |
| wp-content/ files |
660 |
660 |
PHP Function Restrictions
Disable potentially dangerous PHP functions in your pool configuration:
; ENABLED FUNCTIONS
; disk_free_space
; DISABLED FUNCTIONS
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
Open_basedir Configuration
Restrict PHP file access to specific directories:
cd /var/www/sub.example.com/
ls -l
sudo mkdir tmp/
sudo chown username:username tmp/
sudo chmod 770 tmp/
ls -l
Add these directives to your PHP pool configuration:
php_admin_value[upload_tmp_dir] = /var/www/sub.example.com/tmp/
php_admin_value[sys_temp_dir] = /var/www/sub.example.com/tmp/
php_admin_value[open_basedir] = /var/www/sub.example.com/public_html/:/var/www/sub.example.com/tmp/
6. Wildcard SSL Certificate Installation
Cloudflare API Configuration
Wildcard SSL certificates allow you to secure your main domain and all subdomains with a single
certificate.
📋 Prerequisites:
- Cloudflare account with your domain
- Global API Key from Cloudflare
- Certbot with DNS plugin installed
Create Secure Credentials Directory
sudo mkdir /root/.cf_credentials/
sudo nano /root/.cf_credentials/cf
Add your Cloudflare credentials:
dns_cloudflare_email = "
[email protected]"
dns_cloudflare_api_key = "your_global_api_key_here"
Secure the Credentials
sudo chmod 400 /root/.cf_credentials/cf
sudo chmod 700 /root/.cf_credentials/
Install Wildcard Certificate
⚠️ Important: Always test with --dry-run first to avoid hitting Let's Encrypt rate
limits!
Test Certificate Installation (Dry Run)
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials
/root/.cf_credentials/cf -d *.example.com --preferred-challenges dns-01 --dry-run
Install Certificate (Production)
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials
/root/.cf_credentials/cf -d *.example.com --preferred-challenges dns-01
🔧 Troubleshooting: If you encounter DNS errors (NXDOMAIN), wait a few minutes for
DNS propagation and try again.
Verify Certificate Installation
sudo certbot certificates
Configure SSL for Subdomain
Create an SSL include file for your subdomain:
sudo nano /etc/nginx/ssl/ssl_sub.example.com.conf
# This include file can be used for ALL sub domains, it's *.domain
# Include this file in your sites sub domain secure server block
ssl_certificate /etc/letsencrypt/live/example.com-0001/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com-0001/privkey.pem;
# SSL STAPLING
ssl_trusted_certificate /etc/letsencrypt/live/example.com-0001/chain.pem;
Update NGINX Server Block for HTTPS
server {
listen 80;
# NO www.sub.domain.com
server_name sub.example.com;
# ADD REDIRECT TO HTTPS: 301 PERMANENT 302 TEMPORARY
return 301 https://sub.example.com$request_uri;
}
# SECURE SERVER BLOCK
server {
# SECOND SERVER - NO REUSEPORT
listen 443 ssl;
http2 on;
listen 443 quic;
http3 on;
server_name sub.example.com;
root /var/www/sub.example.com/public_html;
index index.php;
# ADD SITE AND ALL SSL INCLUDE FILES
include /etc/nginx/ssl/ssl_sub.example.com.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-sub.example.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
include /etc/nginx/includes/browser_caching.conf;
access_log /var/log/nginx/access_sub.example.com.log combined buffer=256k flush=60m;
error_log /var/log/nginx/error_sub.example.com.log;
}
Test and Reload NGINX
sudo nginx -t
sudo systemctl reload nginx
Verify HTTPS Configuration
curl -I http://sub.example.com
curl -I https://sub.example.com
✅ SSL Testing:
- Test your certificate at SSL
Labs - aim for A+ rating
- Verify HTTP/3 support at HTTP/3 Check
- Use browser DevTools → Network tab to confirm h3 protocol
HTTP/3 Testing (Optional)
To test HTTP/3, temporarily add this directive to prevent browser caching:
# TESTING HTTP3 AND QUIC - add to location / context to test
# PREVENTS BROWSER CACHING SITE - USED TO TEST HTTP3
# COMMENT OR REMOVE DIRECTIVE AFTER CONFIRMING HTTP3 IS ENABLED
add_header Cache-Control 'no-cache,no-store';
⚠️ Important: Remove this directive after confirming HTTP/3 is working properly!
7. WordPress Optimization
Disable Post Revisions
Add to wp-config.php to prevent database bloat:
/** DISABLE POST REVISIONS */
define('WP_POST_REVISIONS', false);
Increase WordPress Memory Limit
Uncomment and modify the PHP pool memory limit:
;php_admin_value[memory_limit] = 32M
Change to:
php_admin_value[memory_limit] = 256M
Add to wp-config.php:
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
Disable WordPress Cron
Disable WP-Cron and use system cron for better performance:
/** DISABLE WP-CRON */
define('DISABLE_WP_CRON', true);
Setup system cron job:
crontab -e
Add this line to run every 15 minutes:
*/15 * * * * wget -q -O - https://sub.example.com/wp-cron.php?doing_wp_cron
>/dev/null 2>&1
OPcache Configuration
Development Server Settings
; 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] = 1
Production Server Settings
; 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
📝 OPcache Differences:
- Development: validate_timestamps = 1, revalidate_freq = 2 (checks for file
changes)
- Production: validate_timestamps = 0 (maximum performance, requires cache
clear on code changes)
Rate Limiting
Create rate limiting configuration for your 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.conf
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 in your server block:
# Rate Limiting Include
include /etc/nginx/includes/rate_limiting_sub.example.com.conf;
Database Privileges (Security Hardening)
Restrict database user privileges to minimum required:
REVOKE ALL PRIVILEGES ON site_db.* FROM 'site_user'@'hostname';
GRANT SELECT, INSERT, UPDATE, DELETE ON site_db.* TO 'site_user'@'hostname';
FLUSH PRIVILEGES;
exit
8. PHP-FPM Memory Tuning & Server Resource Management
🚨 Critical: After adding any site, WordPress updates, new themes, or plugins, you
MUST retune PHP-FPM. This is especially critical for low-resource servers (under 4GB RAM).
Understanding Memory Requirements
Memory Allocation Strategy
| Server RAM |
Free Memory (90%) |
Recommended Allocation |
| 1 GB |
~900 MB |
Insufficient for 3 sites |
| 2 GB |
~1800 MB |
2 static sites only |
| 4 GB |
~3600 MB |
2 static + 1 WooCommerce |
| 8 GB |
~7200 MB |
Optimal multi-site setup |
Memory Tuning Process
Step 1: Run Pool Memory Script
cd ~/server_bash_scripts/
sudo ./pool_memory
The script will display:
- Pool names for all sites
- Average memory usage per child process
- Current process manager configuration
💡 Example Output:
- expert_wp: 66 MB per process
- nginx_help: 66 MB per process
- store (WooCommerce): 93 MB per process
Step 2: Check Server Resources
htop
Press 'q' to quit htop. Note the available free memory.
Calculate pm.max_children Values
⚠️ Calculation Formula:
pm.max_children = (Available Memory * 0.9) / Average Process Memory
Example Calculation (4GB Server)
Free Memory: 3820 MB
After 90% allocation: 3820 * 0.9 = 3438 MB
Distribution:
- Static Sites (2): 900 MB each = 1800 MB total
- WooCommerce: 2000 MB
Static Site Calculation:
900 MB / 66 MB per process = 13.6 → 13 processes
WooCommerce Calculation:
2000 MB / 93 MB per process = 21.5 → 21 processes
Configure PHP-FPM Pools
For Static WordPress Sites (On-Demand Mode)
sudo nano /etc/php/8.3/fpm/pool.d/expert_wp.conf
Search for process manager mode (Ctrl+W):
; Comment out dynamic mode
; pm = dynamic
; Enable on-demand mode
pm = ondemand
; Set max children based on calculation
pm.max_children = 13
; Uncomment and verify these directives
pm.process_idle_timeout = 10s
pm.max_requests = 500
For WooCommerce Sites
sudo nano /etc/php/8.3/fpm/pool.d/store.conf
; Enable on-demand mode
pm = ondemand
; Set max children based on calculation
pm.max_children = 21
; Verify timeout and max requests
pm.process_idle_timeout = 10s
pm.max_requests = 500
📋 Process Manager Modes:
- dynamic: Maintains minimum children always running (uses more memory)
- ondemand: Spawns children only when needed (saves memory, slight latency)
- static: Fixed number of children always running (highest memory, lowest
latency)
Step 3: Reload PHP-FPM
sudo systemctl reload php8.3-fpm
Step 4: Verify Configuration
sudo ./pool_memory
With on-demand mode, you should see minimal or no active processes until requests are made.
Testing the Configuration
- Browse to each site's WordPress dashboard
- Navigate through a few pages (Settings → General → Dashboard)
- Run the pool memory script again to see active processes
- Monitor with htop to verify memory usage stays within limits
Memory Usage Flow
Request Arrives
→
PHP-FPM
Spawns Child
→
Process Request
(Uses ~66-93MB)
→
Idle Timeout
(10 seconds)
→
Kill Process
(Free Memory)
Server Upgrade Considerations
When to Upgrade
- Frequent 502/503 errors
- Slow page load times
- High memory usage (consistently above 90%)
- Unable to complete plugin installations
- Adding WooCommerce or resource-intensive plugins
Upgrade Process Example (1GB → 4GB)
💡 Most Cloud Providers: Allow memory/CPU upgrades without downtime. However,
downgrades are typically not supported.
After upgrading server resources:
- Verify new resources:
htop
- Recalculate PHP-FPM values:
sudo ./pool_memory
- Update MariaDB configuration:
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
# For 4GB server:
innodb_buffer_pool_size = 3200M # 80% of 4GB
innodb_log_file_size = 800M # 25% of buffer pool
- Update swap size if needed:
| Server RAM |
Recommended Swap |
| 1 GB |
2 GB |
| 2 GB |
4 GB |
| 4 GB |
4-8 GB |
| 8+ GB |
4-8 GB |
- Reload all services:
sudo systemctl reload php8.3-fpm
sudo systemctl restart mariadb
sudo systemctl reload nginx
Monitoring Best Practices
✅ Regular Checks:
- Run pool_memory script weekly
- Monitor htop for memory trends
- Check NGINX and PHP-FPM error logs
- Review slow query logs from MariaDB
- Test site performance after any changes
Troubleshooting Common Issues
Issue: 502 Bad Gateway
Possible Causes:
- PHP-FPM pool exhausted (all children busy)
- PHP-FPM not running
- Socket file permissions issue
Solution:
sudo systemctl status php8.3-fpm
sudo tail -f /var/log/php8.3-fpm.log
Increase pm.max_children if pool is consistently maxed out.
Issue: Slow Response Times
Possible Causes:
- Insufficient memory allocation
- Database not optimized
- No caching configured
Solution:
- Enable Redis or WP Super Cache
- Optimize MariaDB configuration
- Consider upgrading server resources
Issue: Out of Memory Errors
Possible Causes:
- Too many PHP children spawned
- Memory leaks in plugins
- Insufficient server RAM
Solution:
dmesg | grep -i "out of memory"
Reduce pm.max_children or upgrade server RAM.
9. Caching Configuration
WP Super Cache (Dynamic Subdomain)
Create or verify the WP Super Cache exclusions file:
sudo nano /etc/nginx/includes/wp_super_cache_exclusions.conf
Include in your subdomain server block:
include /etc/nginx/includes/wp_super_cache_exclusions.conf;
sudo nginx -t
sudo systemctl reload nginx
Redis Object Cache
Configure Redis for Subdomain
Add to wp-config.php:
define( 'WP_CACHE_KEY_SALT', 'sub.example.com' );
/** PREVENT REDIS CACHING WOO SESSION DATA */
define('WP_REDIS_IGNORED_GROUPS', 'wc_session');
📝 WooCommerce Cache Exclusions:
For detailed WooCommerce caching configuration, refer to the official documentation: WooCommerce Caching Guide
Caching Strategy Comparison
| Cache Type |
Best For |
Pros |
Cons |
| WP Super Cache |
Static sites |
Simple, fast, file-based |
Not ideal for dynamic content |
| Redis |
Dynamic/WooCommerce |
Memory-based, persistent, flexible |
Requires Redis server |
| OPcache |
All sites |
PHP bytecode caching, essential |
Code changes require cache clear |
| Browser Caching |
All sites |
Reduces server load, faster loads |
Users may see stale content |
10. Final Configuration Checklist
✅ Complete Configuration Checklist
-
Server Block Configuration
- HTTP to HTTPS redirect configured
- SSL certificates installed and verified
- HTTP/2 and HTTP/3 enabled
- Proper fastcgi_param directives in PHP location
-
PHP-FPM Pool
- Dedicated user created
- Pool configuration file created
- Socket file correctly referenced
- Memory limits appropriately set
- Disabled functions configured
- OPcache configured
-
Security Hardening
- File permissions set (550/440 or 770/660)
- wp-config.php secured (400)
- open_basedir configured
- Rate limiting enabled
- Database privileges restricted
- HTTP headers included
-
WordPress Optimization
- Post revisions disabled
- Memory limit increased
- WP-Cron disabled (system cron configured)
- Caching configured (Redis or WP Super Cache)
-
Testing & Verification
- Site loads properly over HTTPS
- SSL certificate A+ rating on SSL Labs
- HTTP/3 working (verified with tools)
- All redirects functioning correctly
- PHP-FPM memory usage monitored
- No errors in NGINX or PHP-FPM logs
⚠️ Post-Installation Maintenance
- Weekly: Run pool_memory script and adjust pm.max_children if needed
- After Updates: Clear OPcache (if using production settings)
- Monthly: Review error logs and optimize database
- Quarterly: Test backups and disaster recovery procedures
Summary
This comprehensive guide has covered the complete process of hosting an additional WordPress site as
a subdomain on your NGINX server. Key accomplishments include:
Server Configuration Overview
Infrastructure Components:
- ✅ NGINX web server with HTTP/2 and HTTP/3 support
- ✅ PHP 8.3-FPM with dedicated pools per site
- ✅ MariaDB database optimized for multi-site hosting
- ✅ Let's Encrypt wildcard SSL certificates
- ✅ Redis or WP Super Cache for performance
Security Measures:
- 🔒 Process isolation through separate PHP-FPM pools
- 🔒 Restricted file permissions and ownership
- 🔒 Open_basedir restrictions
- 🔒 Disabled dangerous PHP functions
- 🔒 Rate limiting on sensitive endpoints
- 🔒 Minimal database privileges
Performance Optimizations:
- ⚡ OPcache for PHP bytecode caching
- ⚡ Browser caching with appropriate headers
- ⚡ FastCGI optimizations
- ⚡ Disabled WordPress cron (using system cron)
- ⚡ Object caching (Redis)
- ⚡ Properly tuned PHP-FPM memory allocation
🎉 Congratulations!
You have successfully configured a production-ready WordPress subdomain with enterprise-level
security and performance optimizations. Your server is now capable of hosting multiple WordPress
sites efficiently while maintaining isolation and security between sites.