Back to blog
Security Tips5 min read

WordPress Server Hardening: Beyond the Plugin Layer

June 11, 2025·WO Security Shield Team
server hardeningphpwordpressfile permissions
WordPress Server Hardening: Beyond the Plugin Layer

Most WordPress security guides focus on plugins and admin settings. But true hardening happens at the server level — in your PHP configuration, file permissions, server headers, and database setup. These settings protect you even if a plugin is compromised.

File and directory permissions

Incorrect file permissions are one of the most common misconfigurations on WordPress servers.

Recommended permissions

Path Permission
wp-config.php 400 or 440
/wp-content/ 755
/wp-content/uploads/ 755
PHP files in /uploads/ Should not exist
.htaccess 444
WordPress core files 644
Directories 755

Prevent PHP execution in the uploads folder

The uploads folder must accept file uploads but should never execute PHP. A single misconfigured permission here lets attackers upload a webshell and execute it directly.

Apache — add this to /wp-content/uploads/.htaccess:

<FilesMatch "\.php$">
  Deny from all
</FilesMatch>

Nginx:

location ~* /wp-content/uploads/.*\.php$ {
    deny all;
    return 403;
}

WO Security Shield checks this configuration on every scan and flags it as a critical misconfiguration if PHP execution is possible in the uploads directory.

PHP configuration hardening

Edit your php.ini (or use a .user.ini in shared hosting):

; Disable dangerous functions
disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_multi_exec, parse_ini_file, show_source

; Hide PHP version from headers
expose_php = Off

; Limit upload sizes
upload_max_filesize = 10M
post_max_size = 12M

; Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1

HTTP security headers

Security headers are returned with every HTTP response and instruct the browser how to handle your content. They're free, fast to implement, and stop entire categories of attacks.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' data: https:

WO Security Shield audits your HTTP headers on every scan and provides a one-click fix for missing or misconfigured headers.

Database user isolation

Your WordPress database user should only have the permissions it actually needs:

-- Create a restricted WordPress DB user
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'strong-password';

-- Grant only what WordPress needs
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX
  ON wordpress_db.*
  TO 'wp_user'@'localhost';

-- Never grant SUPER, FILE, or PROCESS privileges
FLUSH PRIVILEGES;

If an attacker compromises your WordPress installation, a restricted database user limits the blast radius — they can't read other databases, write files to disk via SELECT INTO OUTFILE, or execute system commands via sys_exec().

wp-config.php hardening

For a comprehensive deep-dive on this topic, see our dedicated guide to hardening wp-config.php. Move wp-config.php one directory above the WordPress root. Requests to it return a 404 rather than exposing the file path.

Also add:

// Prevent direct file editing via WordPress admin
define( 'DISALLOW_FILE_EDIT', true );

// Limit post revisions
define( 'WP_POST_REVISIONS', 5 );

// Force SSL for admin
define( 'FORCE_SSL_ADMIN', true );

// Restrict wp-cron to server-side only
define( 'DISABLE_WP_CRON', true ); // Use a real cron job instead

Server hardening is a one-time investment that pays off continuously. Combined with a properly configured WordPress application firewall and WO Security Shield's ongoing monitoring and file integrity checks, it creates a layered defence that stops most attacks before they can do any damage. For the full picture, work through our WordPress security checklist.

Essential Server-Level Security Configurations

File Permissions Matrix

Incorrect file permissions are one of the most common security misconfigurations we see:

Path Recommended permission Why
WordPress root (/) 755 Readable and executable, not writable
wp-config.php 440 (or 400) Read-only, no write access
.htaccess 644 Apache needs to read it
/wp-content/ 755 WordPress needs to write to subdirectories
/wp-content/uploads/ 755 Media uploads need write access
/wp-content/plugins/ 755 Plugin updates need write access
PHP files 644 Readable, not writable by web server

Set these in bulk:

# Fix directory permissions
find /var/www/html -type d -exec chmod 755 {} \;

# Fix file permissions
find /var/www/html -type f -exec chmod 644 {} \;

# Lock down wp-config.php
chmod 440 /var/www/html/wp-config.php

# Ensure uploads directory is writable
chown -R www-data:www-data /var/www/html/wp-content/uploads

PHP Configuration Hardening

Edit your php.ini or add these to your virtual host configuration:

; Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

; Prevent file inclusion attacks
allow_url_fopen = Off
allow_url_include = Off

; Limit file upload size
upload_max_filesize = 10M
post_max_size = 10M

; Hide PHP version
expose_php = Off

; Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1

Important: Some plugins require allow_url_fopen = On (especially those that fetch remote data). Test your site after making this change.

Block PHP Execution in Upload Directories

Attackers frequently upload PHP backdoors disguised as images. Block PHP execution in directories that should only contain media files:

Apache (.htaccess in /wp-content/uploads/):

<Files "*.php">
    Deny from all
</Files>

Nginx:

location ~* /wp-content/uploads/.*\.php$ {
    deny all;
    return 403;
}

MySQL/MariaDB Hardening

Your database is the most valuable target on your server:

-- Create a dedicated WordPress database user with minimal privileges
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'strong_random_password';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP
  ON wordpress_db.* TO 'wp_user'@'localhost';
FLUSH PRIVILEGES;

-- Don't grant FILE, PROCESS, SUPER, or GRANT OPTION
-- WordPress never needs these

Also in your MySQL config (my.cnf):

[mysqld]
# Bind to localhost only — no remote database access
bind-address = 127.0.0.1

# Disable LOCAL INFILE to prevent data exfiltration
local-infile = 0

Automatic Security Updates

Enable unattended security updates for your server OS. On Ubuntu/Debian:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

This ensures critical OS-level security patches are applied automatically — even if you forget to check for weeks.

WO Security Shield

Is your WordPress site protected?

Run a free malware scan in under 2 minutes. No credit card required.