Table of Contents
- Prerequisites
- Creating Nginx Configuration
- Setting Up WordPress Directory
- Installing WordPress
- Database Setup
- Fixing Permission Issues
- Verifying Installation
- Troubleshooting
- Security Recommendations
Prerequisites
Before beginning, ensure you have:
- Root or sudo access to your server
- Nginx installed (
nginx -v) - PHP-FPM installed (PHP 7.4 or newer) (
php-fpm -v) - MySQL/MariaDB installed (
mysql --version) - Domain pointed to your server’s IP (example:
example.com)
Note: Throughout this guide, replace
example.comwith your actual domain name.
Verify Your Environment
bash
# Check Nginx
nginx -v
# Check PHP-FPM
php-fpm -v
# Check MySQL
mysql --version
# Check PHP extensions (WordPress requirements)
php -m | grep -E "mysqli|curl|json|mbstring|imagick|xml|zip"1. Creating Nginx Configuration
Step 1.1: Create Configuration File
bash
sudo nano /etc/nginx/sites-available/example.com
Step 1.2: Add Configuration Content
Paste the following configuration (replace example.com with your domain):
nginx
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.php index.html index.htm;
access_log /var/log/nginx/example.com_access.log;
error_log /var/log/nginx/example.com_error.log;
client_max_body_size 100M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires max;
log_not_found off;
access_log off;
add_header Cache-Control "public, immutable";
}
}Step 1.3: Adjust PHP-FPM Socket
Check your PHP version and adjust the fastcgi_pass line if necessary:
bash
# Check available PHP-FPM sockets ls /var/run/php/ # Common socket paths: # PHP 7.4: /var/run/php/php7.4-fpm.sock # PHP 8.0: /var/run/php/php8.0-fpm.sock # PHP 8.1: /var/run/php/php8.1-fpm.sock # PHP 8.2: /var/run/php/php8.2-fpm.sock # PHP 8.3: /var/run/php/php8.3-fpm.sock
Step 1.4: Optimize PHP-FPM Pool Settings
For better performance, edit the PHP-FPM pool configuration:
bash
sudo nano /etc/php/8.1/fpm/pool.d/www.conf
Adjust these settings based on your server resources:
ini
pm = dynamic pm.max_children = 50 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 35 pm.max_requests = 500
2. Setting Up WordPress Directory
Step 2.1: Create Directory Structure
bash
# Create document root sudo mkdir -p /var/www/example.com # Set initial permissions sudo chown -R $USER:www-data /var/www/example.com
Step 2.2: Configure Permissions
bash
# Set directory permissions
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
# Set file permissions
sudo find /var/www/example.com -type f -exec chmod 644 {} \;Step 2.3: Enable Site and Test Configuration
bash
# Enable site by creating symbolic link sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ # Test Nginx configuration sudo nginx -t # If test passes, reload Nginx sudo systemctl reload nginx # Verify site is enabled ls -la /etc/nginx/sites-enabled/
3. Installing WordPress
Step 3.1: Download WordPress
bash
# Navigate to web directory cd /var/www/example.com # Download latest WordPress wget https://wordpress.org/latest.tar.gz # Extract files tar -xzf latest.tar.gz # Move WordPress files to root mv wordpress/* . && mv wordpress/.* . 2>/dev/null || true # Clean up rm -rf wordpress latest.tar.gz
Step 3.2: Create wp-config.php from Sample
bash
# Copy sample configuration cp wp-config-sample.php wp-config.php # Set initial permissions sudo chmod 640 wp-config.php
Step 3.3: Set Final Permissions
bash
# Set ownership to web server user
sudo chown -R www-data:www-data /var/www/example.com
# Set secure permissions
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
# Create uploads directory with proper permissions
sudo mkdir -p /var/www/example.com/wp-content/uploads
sudo chmod 755 /var/www/example.com/wp-content/uploads
# Set special permissions for wp-content
sudo chmod 755 /var/www/example.com/wp-contentStep 3.4: Generate Security Keys
Generate unique security keys for wp-config.php:
bash
# Generate security keys curl -s https://api.wordpress.org/secret-key/1.1/salt/ # Copy the output to use in wp-config.php
4. Database Setup
Step 4.1: Create Database and User
bash
# Log into MySQL sudo mysql -u root -p
Run these SQL commands (replace your_strong_password with a secure password and adjust database names as needed):
sql
-- Create database with UTF8MB4 character set CREATE DATABASE wp_example CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- Create database user CREATE USER 'wp_example_user'@'localhost' IDENTIFIED BY 'your_strong_password'; -- Grant privileges GRANT ALL PRIVILEGES ON wp_example.* TO 'wp_example_user'@'localhost'; -- Apply changes FLUSH PRIVILEGES; -- Verify database creation SHOW DATABASES; -- Exit MySQL EXIT;
Step 4.2: Verify Database Connection
bash
# Test database connection mysql -u wp_example_user -p -e "SHOW DATABASES;" # Test specific database access mysql -u wp_example_user -p -e "USE wp_example; SHOW TABLES;"
Step 4.3: Update wp-config.php with Database Credentials
bash
sudo nano /var/www/example.com/wp-config.php
Update the database configuration section:
php
// ** Database settings - You can get this info from your web host ** // /** The name of the database for WordPress */ define( 'DB_NAME', 'wp_example' ); /** Database username */ define( 'DB_USER', 'wp_example_user' ); /** Database password */ define( 'DB_PASSWORD', 'your_strong_password' ); /** Database hostname */ define( 'DB_HOST', 'localhost' ); /** Database charset to use in creating database tables. */ define( 'DB_CHARSET', 'utf8mb4' ); /** The database collate type. Don't change this if in doubt. */ define( 'DB_COLLATE', '' );
5. Completing WordPress Installation
Step 5.1: Run Web Installer
- Open your browser and navigate to:
http://example.com - Select your language
- Fill in the installation information:
- Site Title: Your Website Name
- Username: Choose a secure admin username
- Password: Use a strong password
- Email: Your email address
- Search Engine Visibility: Uncheck if you want search engines to index
Step 5.2: Configure Site Settings
After installation, log in to wp-admin and configure:
- Permalinks: Settings → Permalinks → Select “Post name”
- Timezone: Settings → General → Timezone
- Site Icon: Appearance → Customize → Site Identity
- Reading Settings: Settings → Reading → Set homepage as static page if needed
Step 5.3: Add Advanced wp-config.php Settings
For enhanced security and performance, add these to wp-config.php:
php
// Add after database configuration
// ** Security Settings ** //
define( 'DISALLOW_FILE_EDIT', true ); // Disable plugin/theme editor
define( 'FORCE_SSL_ADMIN', true ); // Force SSL for admin (when SSL is enabled)
define( 'WP_DEBUG', false ); // Disable debug in production
define( 'WP_MEMORY_LIMIT', '256M' ); // Increase memory limit
define( 'WP_MAX_MEMORY_LIMIT', '512M' ); // Maximum memory limit
// ** Performance Settings ** //
define( 'WP_CACHE', true ); // Enable caching
// ** Filesystem Settings ** //
define( 'FS_METHOD', 'direct' ); // Direct filesystem method
// Add security keys from earlier (replace with your generated keys)
define('AUTH_KEY', 'your-unique-key-here');
define('SECURE_AUTH_KEY', 'your-unique-key-here');
define('LOGGED_IN_KEY', 'your-unique-key-here');
define('NONCE_KEY', 'your-unique-key-here');
define('AUTH_SALT', 'your-unique-key-here');
define('SECURE_AUTH_SALT', 'your-unique-key-here');
define('LOGGED_IN_SALT', 'your-unique-key-here');
define('NONCE_SALT', 'your-unique-key-here');
// ** Table prefix ** //
$table_prefix = 'wp_'; // Change this for security (e.g., 'ex_')6. Fixing Permission Issues
Problem: “Unable to create directory wp-content/uploads/2026/03”
If you encounter this error, follow these steps:
Step 6.1: Quick Permission Fix
bash
# Navigate to WordPress directory cd /var/www/example.com # Fix uploads directory permissions sudo chown -R www-data:www-data wp-content/uploads sudo chmod -R 755 wp-content/uploads # Create current month's directory sudo mkdir -p wp-content/uploads/$(date +%Y/%m) sudo chmod 755 wp-content/uploads/$(date +%Y/%m)
Step 6.2: Comprehensive Permission Fix
If the quick fix doesn’t work:
bash
cd /var/www/example.com
# Set ownership for entire WordPress installation
sudo chown -R www-data:www-data .
# Set directory permissions (755 for folders)
sudo find . -type d -exec chmod 755 {} \;
# Set file permissions (644 for files)
sudo find . -type f -exec chmod 644 {} \;
# Set special permissions for critical directories
sudo chmod 755 wp-content
sudo chmod 755 wp-includes
# Ensure uploads directory exists and is writable
sudo mkdir -p wp-content/uploads
sudo chmod 755 wp-content/uploads
# Create yearly/monthly directory structure
sudo mkdir -p wp-content/uploads/{$(date +%Y),$(date +%Y)/$(date +%m)}
sudo chmod 755 wp-content/uploads/*
# Set sticky bit for uploads directory (optional)
sudo chmod +t wp-content/uploadsStep 6.3: Fix SELinux Context (CentOS/RHEL)
If using SELinux:
bash
# Check SELinux status getenforce # If enabled, set correct contexts sudo chcon -R -t httpd_sys_rw_content_t /var/www/example.com/wp-content/uploads sudo chcon -R -t httpd_sys_rw_content_t /var/www/example.com/wp-content # Make context changes permanent sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/example.com/wp-content/uploads(/.*)?" sudo restorecon -Rv /var/www/example.com/wp-content/uploads # If WordPress needs to write to other directories sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/example.com/wp-content/(cache|upgrade)(/.*)?" sudo restorecon -Rv /var/www/example.com/wp-content
Step 6.4: Verify PHP-FPM User and Group
bash
# Check PHP-FPM running user ps aux | grep php-fpm | grep -v grep # View PHP-FPM pool configuration sudo nano /etc/php/8.1/fpm/pool.d/www.conf # Ensure these lines are set correctly: # user = www-data # group = www-data # listen.owner = www-data # listen.group = www-data # If changed, restart PHP-FPM sudo systemctl restart php8.1-fpm sudo systemctl reload nginx
Step 6.5: Test File Upload Permissions
bash
# Test as www-data user
sudo -u www-data touch /var/www/example.com/wp-content/uploads/test.txt
# Check if file was created successfully
if [ -f /var/www/example.com/wp-content/uploads/test.txt ]; then
echo "✅ Upload directory is writable"
sudo rm /var/www/example.com/wp-content/uploads/test.txt
else
echo "❌ Upload directory is NOT writable"
fi
# Test WordPress file creation
sudo -u www-data wp eval 'file_put_contents(ABSPATH . "wp-content/uploads/test.txt", "test");' --path=/var/www/example.com 2>/dev/null || echo "WP-CLI not installed, skip this test"7. Firewall Configuration
Step 7.1: Configure Firewall
For UFW (Ubuntu/Debian):
bash
# Allow HTTP and HTTPS sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Allow SSH (important!) sudo ufw allow 22/tcp # Enable firewall if not already enabled sudo ufw enable # Check status sudo ufw status verbose
For Firewalld (CentOS/RHEL):
bash
# Allow HTTP and HTTPS sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https # Reload firewall sudo firewall-cmd --reload # Check status sudo firewall-cmd --list-all
Step 7.2: Test Port Access
bash
# Check if ports are listening sudo netstat -tlnp | grep -E ":80|:443" # Test from remote (run from another machine) curl -I http://example.com
8. Performance Optimization
Step 8.1: Enable Nginx Caching
Create a cache directory and add to Nginx configuration:
bash
# Create cache directory sudo mkdir -p /var/cache/nginx/wordpress sudo chown -R www-data:www-data /var/cache/nginx/wordpress
Add to Nginx configuration (/etc/nginx/sites-available/example.com):
nginx
# Add this in the server block
set $cache_key "$scheme$request_method$host$request_uri";
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_cache wordpress;
fastcgi_cache_key $cache_key;
fastcgi_cache_valid 200 60m;
fastcgi_cache_use_stale error timeout updating;
fastcgi_cache_background_update on;
fastcgi_cache_lock on;
include fastcgi_params;
}Step 8.2: Install Caching Plugin
Recommended caching plugins:
- W3 Total Cache – Comprehensive caching solution
- WP Super Cache – Simple and effective
- LiteSpeed Cache – Best for LiteSpeed servers
- WP Rocket – Premium option (highly recommended)
Step 8.3: Configure PHP-FPM Status Page (Optional)
Add to Nginx configuration for monitoring:
nginx
location /status {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
allow 127.0.0.1;
deny all;
}9. Troubleshooting Guide
Common Issues and Solutions
| Issue | Symptoms | Solution |
|---|---|---|
| 502 Bad Gateway | Nginx can’t connect to PHP-FPM | sudo systemctl restart php8.1-fpmCheck PHP-FPM is running: sudo systemctl status php8.1-fpm |
| 404 Not Found | Pages not loading | Test Nginx config: sudo nginx -tCheck site is enabled: ls /etc/nginx/sites-enabled/ |
| Permission Denied | Can’t upload files | Run permission fixes from Section 6 Check ownership: ls -la /var/www/example.com/ |
| Database Connection Error | “Error establishing database connection” | Verify credentials in wp-config.php Check MySQL: sudo systemctl status mysqlTest connection: mysql -u wp_example_user -p |
| White Screen of Death | Blank page with no error | Enable WP_DEBUG in wp-config.php Check error logs: sudo tail -f /var/log/nginx/example.com_error.log |
| Memory Exhausted | Fatal error: memory size exhausted | Increase memory limit in wp-config.php Check PHP memory: php -i | grep memory_limit |
| Maximum Execution Time | Script timeout | Increase in wp-config.php: set_time_limit(300);Or in PHP-FPM config |
Error Log Locations
bash
# Nginx error log sudo tail -f /var/log/nginx/example.com_error.log # Nginx access log sudo tail -f /var/log/nginx/example.com_access.log # PHP-FPM error log sudo tail -f /var/log/php8.1-fpm.log # PHP error log sudo tail -f /var/log/php_errors.log # WordPress debug log (if enabled) tail -f /var/www/example.com/wp-content/debug.log # System log sudo journalctl -u nginx -f sudo journalctl -u php8.1-fpm -f
Diagnostic Commands
bash
# Check if site is accessible curl -I http://example.com # Check DNS resolution nslookup example.com dig example.com # Check PHP processing echo "<?php phpinfo(); ?>" | sudo tee /var/www/example.com/phpinfo.php # Visit http://example.com/phpinfo.php # DELETE after testing: sudo rm /var/www/example.com/phpinfo.php # Check Nginx configuration sudo nginx -T | grep -A 20 "example.com" # Check Nginx status sudo systemctl status nginx # Check PHP-FPM status sudo systemctl status php8.1-fpm # Check MySQL status sudo systemctl status mysql # Check disk space df -h # Check memory usage free -h # Check inodes (if uploads fail) df -i
WP-CLI Commands (Install if needed)
bash
# Install WP-CLI curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar php wp-cli.phar --info chmod +x wp-cli.phar sudo mv wp-cli.phar /usr/local/bin/wp # Useful WP-CLI commands wp core version --path=/var/www/example.com wp plugin list --path=/var/www/example.com wp user list --path=/var/www/example.com wp db size --path=/var/www/example.com wp rewrite structure '/%postname%/' --path=/var/www/example.com
10. Security Recommendations
Essential Security Measures
10.1 Secure wp-config.php
bash
# Move wp-config.php above web root (optional but secure) sudo mv /var/www/example.com/wp-config.php /var/www/ sudo ln -s /var/www/wp-config.php /var/www/example.com/wp-config.php # Restrict permissions sudo chmod 640 /var/www/example.com/wp-config.php sudo chown www-data:www-data /var/www/example.com/wp-config.php
10.2 Protect Sensitive Files in Nginx
Add to Nginx configuration:
nginx
# Deny access to sensitive files
location ~ /(wp-config.php|wp-config-sample.php|readme.html|license.txt) {
deny all;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
# Protect wp-includes
location ~ ^/wp-includes/ {
internal;
}
# Protect wp-admin
location ~ ^/wp-admin/ {
internal;
}10.3 Limit Login Attempts
Install a security plugin:
- Wordfence Security – Comprehensive security
- Sucuri Security – Website firewall
- iThemes Security – Easy security hardening
- Limit Login Attempts Reloaded – Simple brute force protection
10.4 Database Security
sql
-- Remove unnecessary privileges REVOKE ALL PRIVILEGES ON *.* FROM 'wp_example_user'@'localhost'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX ON wp_example.* TO 'wp_example_user'@'localhost'; FLUSH PRIVILEGES; -- Regularly backup database mysqldump -u wp_example_user -p wp_example > backup_$(date +%Y%m%d).sql
10.5 Add HTTP Security Headers
Add to Nginx configuration:
nginx
# Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Disable server version exposure server_tokens off;
11. Adding SSL Certificate (Let’s Encrypt)
Step 11.1: Install Certbot
Ubuntu/Debian:
bash
sudo apt update sudo apt install certbot python3-certbot-nginx
CentOS/RHEL:
bash
sudo yum install epel-release sudo yum install certbot python3-certbot-nginx
Step 11.2: Obtain SSL Certificate
bash
# Obtain certificate (automatically configures Nginx) sudo certbot --nginx -d example.com -d www.example.com # Follow the prompts: # - Enter email for renewal notices # - Agree to terms of service # - Choose whether to redirect HTTP to HTTPS (recommended: yes)
Step 11.3: Auto-renewal Setup
bash
# Test renewal sudo certbot renew --dry-run # Add to crontab for automatic renewal sudo crontab -e # Add line: 0 0 * * * /usr/bin/certbot renew --quiet
Step 11.4: Verify SSL Configuration
bash
# Check SSL certificate sudo certbot certificates # Test SSL configuration curl -I https://example.com # Check SSL rating at https://www.ssllabs.com/ssltest/
12. Backup Strategy
Automated Backup Script
Create backup script: /usr/local/bin/backup_wordpress.sh
bash
#!/bin/bash
# Configuration
SITE_NAME="example"
BACKUP_DIR="/backups/$SITE_NAME"
DB_NAME="wp_example"
DB_USER="wp_example_user"
DB_PASS="your_password"
KEEP_DAYS=30
# Create backup directory
mkdir -p $BACKUP_DIR/{database,files,logs}
# Timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Database backup
mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/database/db_$TIMESTAMP.sql.gz
# Files backup
tar -czf $BACKUP_DIR/files/files_$TIMESTAMP.tar.gz -C /var/www/$SITE_NAME .
# Remove old backups
find $BACKUP_DIR/database -name "*.sql.gz" -mtime +$KEEP_DAYS -delete
find $BACKUP_DIR/files -name "*.tar.gz" -mtime +$KEEP_DAYS -delete
# Log
echo "$TIMESTAMP: Backup completed" >> $BACKUP_DIR/logs/backup.log
# Optional: Upload to remote storage
# rsync -avz $BACKUP_DIR/ user@remote-server:/backups/Set up cron job:
bash
sudo chmod +x /usr/local/bin/backup_wordpress.sh sudo crontab -e # Add: 0 2 * * * /usr/local/bin/backup_wordpress.sh
13. Monitoring and Maintenance
Regular Maintenance Tasks
bash
# Update WordPress core wp core update --path=/var/www/example.com # Update plugins wp plugin update --all --path=/var/www/example.com # Update themes wp theme update --all --path=/var/www/example.com # Optimize database wp db optimize --path=/var/www/example.com # Check for vulnerabilities wp plugin verify-checksums --all --path=/var/www/example.com
Monitoring Commands
bash
# Check server load
uptime
top -bn1 | head -20
# Check Nginx connections
sudo nginx -T 2>/dev/null | grep -c "keepalive"
# Check PHP-FPM status
sudo systemctl status php8.1-fpm
# Check MySQL status
mysqladmin status
# Monitor logs in real-time
sudo tail -f /var/log/nginx/example.com_access.log | grep -v "wp-admin"
Summary
Your WordPress site should now be fully functional at http://example.com. The installation includes:
- ✅ Nginx configuration optimized for WordPress
- ✅ Proper file permissions and security settings
- ✅ MySQL database with secure user
- ✅ WordPress core installation
- ✅ Upload directory permissions fixed
- ✅ Firewall configured
- ✅ Security headers and hardening
- ✅ Backup strategy
- ✅ SSL ready (optional)
Next Steps
- Configure SSL using Let’s Encrypt (Section 11)
- Install essential plugins:
- Security: Wordfence or Sucuri
- Caching: WP Rocket or W3 Total Cache
- SEO: Yoast SEO or Rank Math
- Backup: UpdraftPlus or BackWPup
- Choose and customize theme
- Create essential pages (Home, About, Contact)
- Set up Google Analytics
- Configure email delivery (SMTP plugin)
- Set up monitoring (Uptime Robot, Pingdom)
Useful Resources
- WordPress Codex
- Nginx Documentation
- PHP-FPM Documentation
- Let’s Encrypt Documentation
- WordPress Security Hardening
This guide was created for WordPress installation on Nginx. Always test changes in a staging environment before applying to production.
