WordPress remains the most popular CMS, but also one of the most attacked. In addition to security plugins, blocking access to sensitive areas directly at the server level and using Fail2Ban can make the site much more secure.

Below you find one Nginx and Apache configuration example, with admin IP restrictions and protections against common attacks (XSS, SQL injection, author enumeration, etc.).

Principles used

✔ access to admin only from trusted IPs
admin-ajax.php and admin-post.php remain public (required for frontend)
✔ disable XML-RPC
✔ author enumeration block
✔ blocking PHP in uploads
✔ block sensitive files
✔ XSS / SQLi protection at the webserver level
✔ Fail2Ban as an additional layer (automatic firewall

Nginx – WordPress Security Block (per site)

Replace IPs dummy with real admin IPs.

############################################
# WORDPRESS SECURITY BLOCK (PER-SITE)
############################################

# --- Trusted admin IPs ---
set $wp_admin_allowed 0;

# Admin IPs (dummy)
if ($remote_addr = 123.123.123.123) { set $wp_admin_allowed 1; }
if ($remote_addr = 111.111.111.111) { set $wp_admin_allowed 1; }
if ($remote_addr = 222.222.222.222) { set $wp_admin_allowed 1; }

# Server itself
if ($remote_addr = 127.0.0.1) { set $wp_admin_allowed 1; }

# PHP socket per site - CHNAGE SOCKET
set $php_sock unix:/run/php/161626534612882.sock; 

#or use set $php_sock 127.0.0.1@843; f

############################################
# BLOCK PHP EXECUTION IN wp-content
############################################
location ~* ^/wp-content/.*\.php$ {
    return 403;
}

############################################
# ADMIN AREA: /wp-admin
############################################

# Allow static assets for all
location ~ ^/wp-admin/.*\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot|ico)$ {
    access_log off;
    log_not_found off;
}

# Essential PHP scripts allowed for everyone
location ~ ^/wp-admin/(admin-ajax\.php|admin-post\.php)$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass $php_sock;
}

# WP Login - trusted IPs only
location = /wp-login.php {
    if ($wp_admin_allowed = 0) { return 403; }
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass $php_sock;
}

# All other PHP files in admin areas - trusted IPs only
location ~ ^/wp-admin/.*\.php$ {
    if ($wp_admin_allowed = 0) { return 403; }
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass $php_sock;
}

# Protect folders themselves (index.php)
location ~ ^/wp-admin/?$ {
    if ($wp_admin_allowed = 0) { return 403; }
    index index.php;
    try_files $uri $uri/ /index.php?$args;
}

############################################
# AUTHOR ENUMERATION
############################################
location ~* ^/author/ { return 403; }
if ($args ~* "author=\d+") { return 403; }
location ~* ^/wp-json/wp/v2/users { return 403; }

############################################
# DISABLE XML-RPC
############################################
location = /xmlrpc.php { return 403; }

############################################
# COMMON XSS / SQL INJECTION / MALICIOUS REQUESTS
############################################
location ~* "(eval\(|base64_|<script|union.*select|insert.*into|drop.*table|sleep\(|benchmark\(|information_schema|load_file|outfile|concat\()" {
    return 403;
}

############################################
# SENSITIVE FILES
############################################
location ~* ^/(wp-config\.php|readme\.html|license\.txt|\.env|composer\.json|package\.json) { return 403; }

############################################
# PHP IN UPLOADS
############################################
location ~* ^/wp-content/uploads/.*\.php$ { return 403; }

############################################
# DOTFILES & BACKUPS
############################################
location ~ /\. { return 403; }
location ~* \.(bak|old|orig|swp|sql|zip|tar|gz)$ { return 403; }

############################################
# SEO SAFE
############################################
location = /robots.txt { allow all; log_not_found off; access_log off; }
location = /sitemap.xml { allow all; log_not_found off; access_log off; }

############################################
# END WORDPRESS SECURITY BLOCK
############################################

Apache – WordPress Security Block

Works for Apache + PHP‑FPM or mod_php.

# -------------------------------------------
# WORDPRESS SECURITY BLOCK
# -------------------------------------------

<FilesMatch "wp-config.php|readme.html|license.txt|\.env|composer.json|package.json">
    Require all denied
</FilesMatch>

<FilesMatch "\.(bak|old|orig|swp|sql|zip|tar|gz)$">
    Require all denied
</FilesMatch>

<FilesMatch "\.php$">
    SetEnvIf Remote_Addr "^(123\.123\.123\.123|111\.111\.111\.111|222\.222\.222\.222|127\.0\.0\.1)$" WP_ADMIN_ALLOWED
</FilesMatch>

<Directory "/var/www/html/wp-admin">
    <Files "admin-ajax.php">
        Require all granted
    </Files>
    <Files "admin-post.php">
        Require all granted
    </Files>
    <FilesMatch "\.php$">
        Require env WP_ADMIN_ALLOWED
    </FilesMatch>
</Directory>

<Directory "/var/www/html/wp-content/uploads">
    <FilesMatch "\.php$">
        Require all denied
    </FilesMatch>
</Directory>

# Disable XML-RPC
<Files "xmlrpc.php">
    Require all denied
</Files>

# Disable author enumeration
RewriteEngine On
RewriteCond %{QUERY_STRING} author=\d [NC]
RewriteRule ^ /? [L,R=403]

Additional security layer: Fail2Ban

Nginx or Apache can protect the site, but brute force attacks can come quickly and massively. Fail2Ban can monitor web logs and automatically block IPs that attempt repeated logins or exploits.

WordPress jail example:

# /etc/fail2ban/jail.local
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 3600

Thus, even if one avoids Nginx or Apache, Fail2Ban blocks suspicious IPs at the firewall level.

Conclusion

🔐 Real security starts at the server level, not in plugins
🧱 Nginx / Apache = first wall
🔥 Fail2Ban = second wall
🧠 WordPress only sees already filtered traffic