angelscript
1. Overview
This guide covers the complete process of installing WordPress on an NGINX server with PHP-FPM, including
security hardening, optimization, and best practices for hosting additional domains.
Installation Process Flow
1Download WordPress
2Extract & Configure
3Copy to Document Root
4Set Permissions
5Complete Browser Installation
6Harden Security
7Optimize Performance
2. Download WordPress
2.1 Download Latest Version
Navigate to your home directory and download the latest WordPress package:
cd
wget https://wordpress.org/latest.tar.gz
ls
Info: The URL https://wordpress.org/latest.tar.gz always points to the
most recent WordPress version.
2.2 Extract the Archive
Use the tar command to extract the downloaded file:
tar xf latest.tar.gz
ls
This creates a directory called wordpress containing all WordPress files and directories.
2.3 Clean Up and Navigate
rm latest.tar.gz
cd wordpress/
ls
ls -l
Note: All files are initially owned by your user account, so no sudo is needed for
editing at this stage.
3. Configure wp-config.php
3.1 Rename Configuration File
Rename the sample configuration file:
mv wp-config-sample.php wp-config.php
ls
3.2 Edit Configuration
Open the configuration file in nano editor:
nano wp-config.php
3.3 Database Configuration
Update the following database settings (obtained from running the database creation script):
| Setting |
Description |
Example |
DB_NAME |
Database name |
example_com |
DB_USER |
Database username |
jvJRsUEEhWwq |
DB_PASSWORD |
Database password |
RandomGeneratedPass123 |
Important: When copying and pasting credentials, ensure you paste between the two
apostrophes, preserving the exact format.
3.4 Security Keys (Salts)
Replace the placeholder salts with unique keys generated from the WordPress API:
curl -s https://api.wordpress.org/secret-key/1.1/salt/
Copy and paste the generated salts into wp-config.php, replacing the existing placeholder values.
3.5 Table Prefix
Change the default table prefix for security:
$table_prefix = 'wp_';
Change to a random prefix (generated from the database creation script):
$table_prefix = 'xyz4_';
3.6 WordPress Constants
Add the following constants before the "That's all, stop editing!" comment:
/** Allow Direct Updating Without FTP */
define('FS_METHOD', 'direct');
/** Disable Editing of Themes and Plugins Using the Built In Editor */
define('DISALLOW_FILE_EDIT', 'true');
/** TURN OFF AUTOMATIC UPDATES */
define('WP_AUTO_UPDATE_CORE', 'false');
angelscript
Configuration Complete: Save and close nano (Ctrl+X, then Y, then Enter).
4. Complete Installation
4.1 Copy Files to Document Root
Return to home directory and use rsync to copy files:
cd
sudo rsync -artv wordpress/ /var/www/example.com/public_html/
Explanation of rsync flags:
-a : Archive mode (preserves permissions, timestamps, etc.)
-r : Recursive (copies directories and their contents)
-t : Preserves modification times
-v : Verbose output
4.2 Set Initial Permissions
Navigate to the site directory and set web server ownership:
cd /var/www/example.com/
ls -l
sudo chown -R www-data:www-data public_html/
ls -l
cd public_html/
ls -l
4.3 Browser Installation
Complete the installation using your web browser:
- Navigate to:
http://www.example.com or http://example.com
- Select language (English US)
- Click "Continue"
- Enter site title
- Enter admin username (use generated credentials)
- Enter admin password (use generated credentials)
- Enter your email address
- Click "Install WordPress"
- Save credentials to your browser if prompted
- Click "Log In"
Installation Complete! You will receive a confirmation message that your WordPress site
has been successfully set up.
4.4 Initial Configuration
Settings → General
Verify the WordPress Address URL and Site Address URL. Note whether www is included or not.
Settings → Permalinks
Set to: Post name
Posts, Pages & Comments
Delete default content:
- Delete sample post
- Delete sample page
- Delete sample comment
Plugins
Remove default plugins:
- Delete Akismet Anti-Spam
- Delete Hello Dolly
Themes
Keep only the active theme, delete unused themes.
Users → Profile
- Edit your nickname
- Set "Display name publicly as" to your nickname
Settings → Discussion
Disable comments site-wide:
- Uncheck "Allow people to submit comments on new posts"
- Click "Save Changes"
Note: For existing posts, edit each post individually and close comments in the
Discussion section.
5. Security Hardening
5.1 PHP-FPM Pool Configuration
Create Dedicated User
sudo useradd username
sudo usermod -a -G username www-data
sudo usermod -a -G www-data username
sudo usermod -a -G $USER username
Create Pool Configuration
cd /etc/php/8.3/fpm/pool.d/
ls
sudo cp www.conf example.com.conf
sudo nano example.com.conf
Pool Configuration Settings
; pool name
[example]
; Username and Group
user = username
group = username
; Path and Unix Socket Filename - unique to this pool
listen = /run/php/php8.3-fpm-example.com.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.example.log
php_admin_flag[log_errors] = on
angelscript
Create Log File
sudo touch /var/log/fpm-php.example.log
sudo chown username:www-data /var/log/fpm-php.example.log
sudo chmod 660 /var/log/fpm-php.example.log
5.2 NGINX Configuration
Update Server Block
sudo nano /etc/nginx/sites-available/example.com.conf
Modify the fastcgi_pass directive to use the new socket:
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
Test and Reload
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm
5.3 Ownership and Permissions
Standard Permissions (Development)
cd /var/www/example.com/
sudo chown -R username:username public_html/ tmp/
sudo chmod 770 public_html/
sudo chmod 770 tmp/
sudo find /var/www/example.com/public_html/ -type d -exec chmod 770 {}
\;
sudo find /var/www/example.com/public_html/ -type f -exec chmod 660 {}
\;
ls -l
Hardened Permissions (Production)
sudo find /var/www/example.com/public_html/ -type d -exec chmod 550 {}
\;
sudo find /var/www/example.com/public_html/ -type f -exec chmod 440 {}
\;
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 {} \;
Permission Explanation:
- 550 - Read and execute for owner/group (directories)
- 440 - Read-only for owner/group (files)
- 770 - Read, write, execute for owner/group (wp-content directories)
- 660 - Read and write for owner/group (wp-content files)
5.4 Disable PHP Functions
Add to pool configuration to disable dangerous PHP functions:
; 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
angelscript
sudo systemctl reload php8.3-fpm
5.5 Open Basedir Restriction
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/
php_admin_value[open_basedir] = /var/www/example.com/public_html/:/var/www/example.com/tmp/
angelscript
5.6 SSL/TLS Configuration
Obtain SSL Certificate
sudo certbot certonly --webroot -w /var/www/example.com/public_html/ -d
example.com -d www.example.com
Create SSL Configuration File
sudo nano /etc/nginx/ssl/ssl_certs_example.com.conf
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
SSL STAPLING
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
Update Server Block for HTTPS
server {
listen 80;
server_name example.com www.example.com;
# 301 Permanent Redirect to HTTPS
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
http2 on;
gradle
listen 443 quic;
http3 on;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.php;
# Include SSL configurations
include /etc/nginx/ssl/ssl_certs_example.com.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;
# ... rest of configuration
}
angelscript
Test and Reload
sudo nginx -t
sudo systemctl reload nginx
Verify Redirects
curl -I http://example.com
curl -I http://www.example.com
curl -I https://www.example.com
curl -I https://example.com
Test Your SSL Configuration:
5.7 Security Headers
Add HTTP security headers by including the configuration file:
# Place ABOVE the php processing location block
include /etc/nginx/includes/http_headers.conf;
5.8 WordPress Security Directives
# Place ABOVE the php processing location block
include /etc/nginx/includes/wp_security.conf;
angelscript
5.9 Rate Limiting
Create Rate Limiting Configuration
cd /etc/nginx/includes/
sudo cp rate_limiting_example.com.conf
rate_limiting_example.net.conf
sudo nano rate_limiting_example.net.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-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-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
Include in server block:
# Rate Limiting Include
include /etc/nginx/includes/rate_limiting_example.net.conf;
angelscript
5.10 Database Privileges Hardening
Check Database Credentials
sudo grep DB_NAME /var/www/example.com/public_html/wp-config.php
sudo grep DB_USER /var/www/example.com/public_html/wp-config.php
Revoke and Grant Limited Privileges
REVOKE ALL PRIVILEGES ON database_name.* FROM 'username'@'localhost';
GRANT SELECT, INSERT, UPDATE, DELETE ON database_name.* TO 'username'@'localhost';
FLUSH PRIVILEGES;
angelscript
Important: This removes dangerous privileges like DROP, CREATE, and ALTER. The database
user can only read and modify data, not structure.
6. Optimization
6.1 WordPress Post Revisions
Add to wp-config.php to disable post revisions:
define('WP_POST_REVISIONS', 'false');
6.2 WordPress Memory Limit
Update PHP Pool Configuration
cd /etc/php/8.3/fpm/pool.d/
sudo nano example.com.conf
Change memory_limit from 32M to 256M:
php_admin_value[memory_limit] = 256M
Update wp-config.php
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
6.3 Disable WP-Cron
Add to wp-config.php
/** DISABLE WP-CRON */
define('DISABLE_WP_CRON', true);
angelscript
Setup System Cron
crontab -e
Add the following line to run every 15 minutes:
*/15 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
6.4 OPcache Configuration
Development Server Settings
; 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 Settings
; 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
angelscript
Key Difference: Production sets validate_timestamps to 0, which disables
checking for file changes on every request, improving performance significantly.
6.5 FastCGI Caching
Add to nginx.conf
cd /etc/nginx
sudo nano nginx.conf
### FASTCGI CACHING
fastcgi_cache_path directive - PATH & NAME must be unique for each site
fastcgi_cache_path /var/run/SITE levels=1:2 keys_zone=NAME:100m inactive=60m;
fastcgi_cache_path /var/run/SITE2 levels=1:2 keys_zone=NAME2: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;
angelscript
Update Server Block
cd /etc/nginx/sites-available/
sudo nano example.com.conf
location ~ \.php$ {
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;
# FastCGI caching directives
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache NAME;
fastcgi_cache_valid 60m;
}
include /etc/nginx/includes/fastcgi_cache_excludes.conf;
add_header X-FastCGI-Cache $upstream_cache_status;
Cache purge location
location ~ /purge(/.*) {
fastcgi_cache_purge NAME "$scheme$request_method$host$1";
}
angelscript
Verification: Check response headers for
X-FastCGI-Cache values:
- HIT - Content served from cache
- MISS - Content not in cache, will be cached
- BYPASS - Cache bypassed for this request
7. Maintenance Mode
7.1 Install Maintenance Mode Plugin
- Navigate to: Plugins → Add New Plugin
- Search for "Maintenance Mode"
- Click "Install Now"
- Click "Activate"
7.2 Configure Plugin
- Go to Settings → Maintenance Mode
- Remove background image (optional)
- Disable front-end login form
- Save changes
Site Setup Complete! Your WordPress installation is now secured, optimized, and ready
for use. Remember to regularly update WordPress core, themes, and plugins, and maintain backups.
8. Permission Management Scripts
8.1 Loosen Permissions Script
Use before updating WordPress, themes, or plugins through the dashboard:
cd ~/wp_bash_scripts/
bash 5.loosen_permissions.sh
8.2 Tighten Permissions Script
Use after completing updates to restore security:
cd ~/wp_bash_scripts/
bash 6.tighten_permissions.sh
Important: Always tighten permissions after making changes through the WordPress
dashboard to maintain security.
9. Monitoring Scripts
9.1 Pool Memory Usage
Monitor memory usage by PHP-FPM pool:
bash pool_list_usage.sh
9.2 OPcache File Count
Check OPcache configuration and PHP file counts:
bash opcache_files.sh
Optimization Tip: Ensure opcache.max_accelerated_files is higher than the
number of PHP files in your WordPress installation for optimal performance.
10. Best Practices Summary
| Category |
Best Practice |
Benefit |
| Security |
Use dedicated PHP-FPM pools |
Site isolation and security |
| Security |
Harden file permissions |
Prevent unauthorized modifications |
| Security |
Limit database privileges |
Reduce impact of SQL injection |
| Security |
Implement rate limiting |
Prevent brute force attacks |
| Performance |
Enable OPcache |
Faster PHP execution |
| Performance |
Configure FastCGI caching |
Reduced server load |
| Performance |
Use HTTP/2 and HTTP/3 |
Faster page loads |
| Maintenance |
Disable automatic updates |
Control update timing |
| Maintenance |
Use system cron |
Reliable scheduled tasks |
Conclusion
This comprehensive guide covers the complete process of installing and securing WordPress on an NGINX
server with PHP-FPM. By following these steps, you've created a high-performance, secure WordPress
installation with proper isolation and optimization.
Key Achievements:
- ✓ WordPress installed with secure configuration
- ✓ Dedicated PHP-FPM pool for site isolation
- ✓ SSL/TLS encryption with HTTP/2 and HTTP/3
- ✓ Hardened file permissions
- ✓ Database privilege restrictions
- ✓ Performance optimization with caching
- ✓ Rate limiting for security
- ✓ Automated maintenance scripts
Remember to regularly backup your site, monitor performance, and keep all software updated for optimal
security and performance.