WordPress hardening is not one setting or one plugin. It is a set of layered controls across the operating system, web server, PHP runtime, filesystem permissions, database access, and application behavior.

This checklist focuses on practical controls that reduce common compromise paths without making day-to-day operations painful. Use it when you operate WordPress on Apache in production and need a baseline security posture you can verify and maintain.

Operational context

  • When to use this: self-managed WordPress on LAMP where you control Apache, SSH, and deployment access.
  • What it reduces: information disclosure, direct file tampering, brute-force exposure, and common plugin-driven attack paths.
  • Tradeoff: stricter controls can complicate emergency access and plugin maintenance. Document exceptions before you enforce them.

SSH hardening

Restrict administrative access before hardening the application layer. The exact SSH policy depends on how you administer the server, but these baseline settings are safe starting points for most production hosts:

Port 7766
PermitRootLogin no
AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no

If you deliberately use SSH tunneling for administrative browsing, keep that exception explicit instead of enabling broad forwarding. For example, a local SOCKS workflow may need AllowTcpForwarding yes plus a PermitOpen rule scoped to the destination you actually use:

AllowTcpForwarding yes
PermitOpen 127.0.0.1:8888

Then launch the browser through the tunnel:

google-chrome --proxy-server="socks5://127.0.0.1:8888"

Apache hardening

Useful references for Apache-specific hardening patterns:

Hide Apache version and OS identity

This reduces information disclosure in headers and error pages. It does not stop exploitation, but it removes easy version fingerprinting.

ServerSignature Off
ServerTokens Prod

Restart Apache after applying the change.

Verify the Apache runtime user

Apache should run as a non-login service account. On Debian and Ubuntu this is usually www-data; on RHEL-style systems it is often apache.

ps aux | grep -E 'apache2|httpd'

Only change the Apache user and group if you are intentionally designing the whole file-permission model around a custom account. Changing it casually can break package defaults, uploads, logs, and deployments.

If you do need a custom runtime account, define it in Apache configuration and test permissions carefully:

User apache
Group apache

.htaccess and wp-config.php controls

WordPress configuration references

1. Add secret keys to wp-config.php

2. Hide .htaccess and wp-config.php

For Apache 2.4:


    Require all denied

For older Apache 2.2 deployments:


    order allow,deny
    deny from all

3. Move wp-config.php outside the web root and include it

<?php
define('ABSPATH', dirname(__FILE__) . '/');
require_once(ABSPATH . '../path/to/wp-config.php');

4. Disable file editing in the admin UI

define('DISALLOW_FILE_EDIT', true);

5. Disable direct access to wp-includes/

# Block wp-includes folder and files

RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]

6. Prevent username enumeration

RewriteCond %{QUERY_STRING} (^|&)author=\d+ [NC]
RewriteRule ^ /? [L,R=301]

7. Block common script injection patterns

Options +FollowSymLinks
RewriteEngine On
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|[|%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|[|%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]

8. Prevent PHP execution in uploads

Place this .htaccess in wp-content/uploads/:

# Kill PHP Execution

deny from all

9. Disable xml-rpc.php if you do not use the mobile app


order allow,deny
deny from all

10. Restrict /wp-admin/ and login access by source IP


	RewriteEngine on
	RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$ [OR]
	RewriteCond %{REQUEST_URI} ^(.*)?wp-admin(\/)$ [OR]
	RewriteCond %{REQUEST_URI} ^(.*)?wp-admin/$
	RewriteCond %{REMOTE_ADDR} !^63\.224\.182\.124$
	RewriteCond %{REMOTE_ADDR} !^96\.81\.205\.229$
	RewriteRule ^(.*)$ - [R=403,L]

Replace the IP addresses with your own administrative egress ranges.

Nginx equivalents

If the site runs on Nginx instead of Apache, apply equivalent controls in the server block.

Hide Nginx version

Set this globally in nginx.conf:

server_tokens off;

Block direct access to sensitive files

location ~* /(wp-config\.php|\.htaccess|readme\.html|license\.txt)$ {
    deny all;
}

Prevent PHP execution in uploads

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

Disable xml-rpc.php if unused

location = /xmlrpc.php {
    deny all;
}

Restrict login and admin paths by source IP

This is a pattern, not a drop-in Nginx configuration. WordPress admin uses admin-ajax.php, REST endpoints, editor assets, and plugin routes, so test restrictions carefully before using them on a production site.

A safer first step is to restrict wp-login.php only:

location = /wp-login.php {
    allow 203.0.113.10;
    deny all;

    include fastcgi_params;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}

Replace the IP and PHP-FPM socket path with values from your environment. If you also restrict /wp-admin/, preserve the normal WordPress PHP handling and explicitly test admin-ajax.php, the block editor, media uploads, plugin pages, and update flows.

Plugins and additional controls

1. Limit login attempts with a focused plugin such as Saltech Functionality Plugin.

2. If you need broader threat blocking, evaluate Wordfence against your operational overhead and false-positive tolerance.

Operational tradeoffs

Hardening should reduce risk without making normal operations fragile. Before enforcing a control, decide:

  • who needs emergency admin access
  • how plugin and core updates are applied
  • whether deployments are manual, Git-based, or CI-driven
  • how media uploads and backups are tested
  • which controls are monitored for accidental breakage

Security controls that nobody verifies tend to fail quietly. Treat the checklist as part of the runbook, not a one-time configuration dump.

Verification

After applying these controls, verify:

  • Admin login still works from approved networks.
  • Media uploads and plugin updates still function as expected.
  • xml-rpc.php, upload PHP execution, and direct config access are blocked as intended.
  • Apache no longer exposes version details in error pages.
  • Nginx, if used, no longer exposes version details and denies sensitive paths.
  • Backups and restore procedures still work after permission changes.

Related work

This hardening checklist supports WordPress platform work in Large crowdlearning platform and Selected operational work.