🔒 WordPress Hardening
PHP Pool Configuration
Create a dedicated PHP-FPM pool for enhanced security and isolation:
Step 1: Create System User
sudo useradd nginx_help_user
sudo usermod -a -G nginx_help_user www-data
sudo usermod -a -G www-data nginx_help_user
sudo usermod -a -G $USER nginx_help_user
Step 2: Create Pool Configuration
cd /etc/php/8.3/fpm/pool.d/
ls
sudo cp www.conf nginx.help.conf
ls
sudo nano nginx.help.conf
Pool Configuration Contents:
; pool name
[nginx_help]
; Username and Group
user = nginx_help_user
group = nginx_help_user
; Path and Unix Socket Filename - unique to this pool
listen = /run/php/php8.3-fpm-nginx.help.sock
; Set open file descriptor rlimit.
rlimit_files = 15000
; Set max core size rlimit.
rlimit_core = 100
; PHP POOL CONFIGURATION
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.nginx_help.log
php_admin_flag[log_errors] = on
Step 3: Create Log File
sudo touch /var/log/fpm-php.nginx_help.log
sudo chown nginx_help_user:www-data /var/log/fpm-php.nginx_help.log
sudo chmod 660 /var/log/fpm-php.nginx_help.log
Step 4: Update NGINX Configuration
sudo nano /etc/nginx/sites-available/nginx.help.conf
Modify the PHP processing location block:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm-nginx.help.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
Step 5: Test and Reload Services
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm
Ownership and Permissions
Standard Permissions (For Updates):
cd /var/www/nginx.help/
sudo chown -R nginx_help_user:nginx_help_user public_html/ tmp/
sudo chmod 770 public_html/
sudo chmod 770 tmp/
sudo find /var/www/nginx.help/public_html/ -type d -exec chmod 770 {} \;
sudo find /var/www/nginx.help/public_html/ -type f -exec chmod 660 {} \;
ls -l
Hardened Permissions (Production):
sudo find /var/www/nginx.help/public_html/ -type d -exec chmod 550 {} \;
sudo find /var/www/nginx.help/public_html/ -type f -exec chmod 440 {} \;
sudo find /var/www/nginx.help/public_html/wp-content/ -type d -exec chmod 770
{} \;
sudo find /var/www/nginx.help/public_html/wp-content/ -type f -exec chmod 660
{} \;
🔐 Permission Explanation:
| Permission |
Directories |
Files |
Purpose |
| Standard |
770 |
660 |
Allows WordPress to update themes/plugins |
| Hardened (Core) |
550 |
440 |
Prevents modifications to core files |
| Hardened (wp-content) |
770 |
660 |
Allows uploads and plugin functionality |
PHP Function Restrictions
Add to the pool configuration file to disable dangerous PHP functions:
sudo nano /etc/php/8.3/fpm/pool.d/nginx.help.conf
; 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
sudo systemctl reload php8.3-fpm
Open Base Directory Restrictions
Restrict PHP to access only necessary directories:
sudo nano /etc/php/8.3/fpm/pool.d/nginx.help.conf
php_admin_value[upload_tmp_dir] = /var/www/nginx.help/tmp/
php_admin_value[sys_temp_dir] = /var/www/nginx.help/tmp/
php_admin_value[open_basedir] = /var/www/nginx.help/public_html/:/var/www/nginx.help/tmp/
Database Privilege Restrictions
Step 1: Check Current Credentials
sudo grep DB_NAME /var/www/nginx.help/public_html/wp-config.php
sudo grep DB_USER /var/www/nginx.help/public_html/wp-config.php
Step 2: Restrict Database Privileges
REVOKE ALL PRIVILEGES ON database_name.* FROM 'username'@'localhost';
GRANT SELECT, INSERT, UPDATE, DELETE ON database_name.* TO 'username'@'localhost';
FLUSH PRIVILEGES;
EXIT;
⚠️ Database Security:
Revoking CREATE, DROP, and ALTER privileges prevents attackers from modifying database structure,
even if they compromise the WordPress installation.
🔐 SSL Certificate Configuration
Step 1: Obtain SSL Certificate
sudo certbot certonly --webroot -w /var/www/nginx.help/public_html/ -d
nginx.help -d www.nginx.help
Step 2: Create SSL Configuration File
sudo nano /etc/nginx/ssl/ssl_certs_nginx.help.conf
SSL Configuration Contents:
ssl_certificate /etc/letsencrypt/live/nginx.help/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/nginx.help/privkey.pem;
# SSL STAPLING
ssl_trusted_certificate /etc/letsencrypt/live/nginx.help/chain.pem;
Step 3: Update NGINX Server Block for HTTPS
sudo nano /etc/nginx/sites-available/nginx.help.conf
Updated Server Block Configuration:
server {
listen 80;
server_name nginx.help www.nginx.help;
# Redirect HTTP to HTTPS
return 301 https://nginx.help$request_uri;
}
server {
listen 443 ssl;
http2 on;
listen 443 quic;
http3 on;
server_name nginx.help www.nginx.help;
root /var/www/nginx.help/public_html;
index index.php;
# Include SSL certificates
include /etc/nginx/ssl/ssl_certs_nginx.help.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# Include HTTP security headers
include /etc/nginx/includes/http_headers.conf;
# Include WordPress security directives
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-nginx.help.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
include /etc/nginx/includes/browser_caching.conf;
access_log /var/log/nginx/access_nginx.help.log combined buffer=256k flush=60m;
error_log /var/log/nginx/error_nginx.help.log;
}
Step 4: Test and Reload
sudo nginx -t
sudo systemctl reload nginx
Step 5: Verify Redirects
curl -I http://nginx.help
curl -I http://www.nginx.help
curl -I https://www.nginx.help
curl -I https://nginx.help
Rate Limiting Configuration
cd /etc/nginx/includes/
sudo cp rate_limiting_example.com.conf rate_limiting_nginx.help.conf
sudo nano rate_limiting_nginx.help.conf
Rate Limiting Contents:
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-nginx.help.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-nginx.help.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
Include in server block:
# Rate Limiting Include
include /etc/nginx/includes/rate_limiting_nginx.help.conf;
⚡ Performance Optimization
WordPress Core Optimizations
1. Disable Post Revisions
Add to wp-config.php:
define('WP_POST_REVISIONS', 'false');
2. Increase Memory Limit
Edit PHP pool configuration:
sudo nano /etc/php/8.3/fpm/pool.d/nginx.help.conf
php_admin_value[memory_limit] = 256M
Add to wp-config.php:
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
3. Disable WP-Cron
Add to wp-config.php:
/** DISABLE WP-CRON */
define('DISABLE_WP_CRON', true);
Set up system cron:
*/15 * * * * wget -q -O - https://nginx.help/wp-cron.php?doing_wp_cron >/dev/null 2>&1
OPcache Configuration
sudo nano /etc/php/8.3/fpm/pool.d/nginx.help.conf
Development Server Configuration:
; OPCACHE CONFIGURATION - DEVELOPMENT SERVER
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 Configuration:
; OPCACHE CONFIGURATION - PRODUCTION SERVER
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] = 0
php_admin_flag[opcache.validate_permission] = 1
📊 OPcache Setting Explanation:
| Setting |
Purpose |
Dev vs Prod |
| memory_consumption |
Amount of memory for OPcache |
Same (256MB) |
| interned_strings_buffer |
Memory for string interning |
Same (32MB) |
| max_accelerated_files |
Maximum cached files |
Same (20000) |
| validate_timestamps |
Check file modifications |
Dev: 1 (check), Prod: 0 (don't check) |
| revalidate_freq |
Seconds between timestamp checks |
Dev: 2 seconds, Prod: N/A |
FastCGI Caching
Step 1: Configure nginx.conf
sudo nano /etc/nginx/nginx.conf
### FASTCGI CACHING
# fastcgi_cache_path directive - PATH & NAME must be unique for each site
fastcgi_cache_path /var/run/nginx_help levels=1:2 keys_zone=NGINXHELP:100m inactive=60m;
fastcgi_cache_path /var/run/site2 levels=1:2 keys_zone=SITE2:100m inactive=60m;
# Applied to all sites
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
Step 2: Update Server Block
sudo nano /etc/nginx/sites-available/nginx.help.conf
Add to PHP location block:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-nginx.help.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
# FastCGI caching directives
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache NGINXHELP;
fastcgi_cache_valid 60m;
}
# Include cache exclusions
include /etc/nginx/includes/fastcgi_cache_excludes.conf;
add_header X-FastCGI-Cache $upstream_cache_status;
# Cache purge location
location ~ /purge(/.*) {
fastcgi_cache_purge NGINXHELP "$scheme$request_method$host$1";
}
Permission Management Scripts
Loosen Permissions Script (For Updates)
cd ~/wp_bash_scripts/
nano 5.loosen_permissions.sh
#!/bin/bash
# List directory contents of /var/www for reference
sudo ls -l /var/www
# Prompt for the domain name
read -p "Enter the domain name: " domain_name
# Check if the domain directory exists
if [ ! -d "/var/www/$domain_name/public_html/" ]; then
echo "Error: Domain directory /var/www/$domain_name/public_html/ does not exist."
exit 1
fi
# Set permissions with confirmation
echo "Setting permissions for /var/www/$domain_name/public_html/..."
# Grant read, write, and execute permissions to owner and group - directories
sudo find "/var/www/$domain_name/public_html/" -type d -exec chmod 770 {} \;
# Grant read and write permissions to owner and group - files
sudo find "/var/www/$domain_name/public_html/" -type f -exec chmod 660 {} \;
echo "Permissions set to allow WordPress updates using the dashboard!"
Tighten Permissions Script (For Production)
nano 6.tighten_permissions.sh
#!/bin/bash
# List directory contents of /var/www for reference
sudo ls -l /var/www
# Prompt for the domain name
read -p "Enter the domain name: " domain_name
# Check if the domain directory exists
if [ ! -d "/var/www/$domain_name/public_html/" ]; then
echo "Error: Domain directory /var/www/$domain_name/public_html/ does not exist."
exit 1
fi
# Set permissions with confirmation
echo "Setting permissions for /var/www/$domain_name/public_html/..."
# Grant read and execute permissions to the owner and group only
sudo find "/var/www/$domain_name/public_html/" -type d -exec chmod 550 {} \;
# Grant read permissions to the owner and group only
sudo find "/var/www/$domain_name/public_html/" -type f -exec chmod 440 {} \;
# Grant read, write and execute permissions to owner and group - wp-content/
sudo find "/var/www/$domain_name/public_html/wp-content" -type d -exec chmod 770 {} \;
# Grant read, write to owner and group - wp-content/
sudo find "/var/www/$domain_name/public_html/wp-content/" -type f -exec chmod 660 {} \;
echo "Permissions hardened, no write permissions on core WordPress files and directories!"
Make Scripts Executable:
chmod +x 5.loosen_permissions.sh
chmod +x 6.tighten_permissions.sh
Monitoring Scripts
PHP-FPM Pool Memory Usage
#!/bin/bash
# Extract pool names
pools=$(grep -E '^\s*user\s*=' /etc/php/8.3/fpm/pool.d/*.conf | awk -F= '{print $2}' | xargs | tr ' '
'\n' | sort -u)
# Display memory used by each pool
for pool in $pools; do
memory_used=$(ps -C php-fpm --user "$pool" -o rss= | awk '{ sum += $1; count++ } END { if (count > 0)
printf ("%d%s\n", sum/NR/1024,"M") }')
echo -e "Pool: $pool\nMemory Used: $memory_used\n"
done
OPcache File Count Checker
#!/bin/bash
echo ""
echo "Checking for and displaying the opcache.max_accelerated_files directive value in each sites PHP
pool file"
echo ""
POOL_DIR="/etc/php/8.3/fpm/pool.d/"
for file in "$POOL_DIR"*.conf; do
filename=$(basename "$file")
if grep -q "^[^#;]*opcache.max_accelerated_files" "$file"; then
value=$(grep "^[^#;]*opcache.max_accelerated_files" "$file" | awk -F'=' '{print $2}' | tr -d ' ')
echo "File: $filename - opcache.max_accelerated_files: $value"
else
echo "File: $filename - opcache.max_accelerated_files directive not found"
fi
done
echo ""
echo "Listing sites and number of PHP files in each site, compare to the value set in the pool file"
echo ""
WWW_DIR="/var/www/"
for domain_dir in "$WWW_DIR"*/ ; do
if [ "$domain_dir" == "/var/www/html/" ]; then
continue
fi
domain_name=$(basename "$domain_dir")
public_html_dir="$domain_dir/public_html/"
if [ -d "$public_html_dir" ]; then
php_file_count=$(find "$public_html_dir" -type f -name "*.php" | wc -l)
echo "Domain: $domain_name - PHP files: $php_file_count"
else
echo "Domain: $domain_name - public_html directory not found"
fi
done
echo ""
Make Monitoring Scripts Executable:
chmod +x pool_list_usage.sh
chmod +x opcache_files.sh