2026年11月12日
2026年11月12日
Restrict WordPress Admin Access by IP: Apache 2.4 and Nginx Setup
This article is a localized edition of the Japanese original. Read the Japanese original
By default, anyone can open the WordPress admin area (/wp-admin/) and login screen (/wp-login.php). If the people who manage the site always connect from known addresses, you can use the web server configuration to allow only those IP addresses. Password brute-forcing and requests aimed at the admin area are then stopped before they ever reach WordPress.
This article covers, in order:
- Restricting access with
.htaccesson Apache 2.4 - Restricting access on Nginx
- Files to exclude from the restriction (such as
admin-ajax.php) and why - Caveats about IPv6, CDNs and reverse proxies, and connections without a static IP
- Testing the setup, and getting back in if you lock yourself out
Before you start
Your IP address: find the IP address of the connection you normally use to open the admin area. You may be connecting over IPv6, so check both.
curl -4 https://ifconfig.me # IPv4 address
curl -6 https://ifconfig.me # IPv6 address (errors on connections without IPv6)
- Whether it is a static IP: home connections often change IP address, and you would have to update the configuration every time it changes. If you do not have a static IP, read "Without a static IP" below first.
- A way back in: a mistake in the configuration can lock you out of the admin area too. Make sure you can edit
.htaccessover FTP, SFTP or SSH, or with your hosting provider's file manager.
Method 1: Restrict with .htaccess on Apache 2.4
Apache 2.4 uses Require for access control. The Order deny,allow / Deny from all / Allow from lines common in older articles are Apache 2.2 syntax and do not work in 2.4 without the compatibility module (mod_access_compat).
Restrict /wp-admin/
Create an .htaccess file in the wp-admin directory (WordPress core normally has no .htaccess in wp-admin). Replace the IP addresses with your own.
# /wp-admin/.htaccess
# Allow access only from these IP addresses (multiple Require ip lines: matching any one is enough)
Require ip 203.0.113.10
Require ip 198.51.100.0/24
Require ip 2001:db8:1234::/48
# Files called by public-facing features stay open to everyone
<FilesMatch "^(admin-ajax|admin-post)\.php$">
Require all granted
</FilesMatch>
admin-ajax.phpmay be called from public pages by themes and plugins (infinite scroll, form submissions, like buttons and so on). Restricting it breaks those features, so exclude it.admin-post.phpis sometimes used by plugins as the target of forms on public pages. If you do not use it, you do not need to exclude it, but even when excluded, WordPress still checks login status and permissions.- To use
Requirein.htaccess, the server configuration must allowAuthConfig(orAll) inAllowOverride. Otherwise,Requirein.htaccessis either ignored or causes a 500 error.
Restrict wp-login.php as well
Restricting only /wp-admin/ leaves the login screen, wp-login.php, open to anyone because it lives in the site root, so brute-force attempts still get through. If the goal is to protect the admin area, limit wp-login.php to the same IP addresses.
Add this to the .htaccess in the WordPress root. WordPress rewrites everything between # BEGIN WordPress and # END WordPress when permalinks are saved, so put it outside that block (for example, above # BEGIN WordPress).
# .htaccess in the WordPress root (above # BEGIN WordPress)
<Files "wp-login.php">
Require ip 203.0.113.10
Require ip 198.51.100.0/24
Require ip 2001:db8:1234::/48
</Files>
Once wp-login.php is restricted, the following also stop working from any IP address you have not allowed. Decide based on how your site is used.
- Entering the password for password-protected posts (the form submits to
wp-login.php?action=postpass) - User registration and password resets (on sites that accept member sign-ups)
xmlrpc.php is also used for login attempts from outside. If you do not use it, see "How to disable XML-RPC in WordPress" to turn it off.
Method 2: Restrict on Nginx
On Nginx, you put allow and deny in a location. The catch is that giving a prefix match priority, as in location ^~ /wp-admin/, stops the location ~ \.php$ regex from being used. As is, PHP files in wp-admin would not be passed to PHP-FPM, so nest the PHP handling inside the location.
# Put this inside the server block. Adjust fastcgi_pass to your environment
# File called by public-facing features (exact matches take top priority)
location = /wp-admin/admin-ajax.php {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
# Admin area
location ^~ /wp-admin/ {
allow 203.0.113.10;
allow 198.51.100.0/24;
allow 2001:db8:1234::/48;
deny all;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
# Login screen
location = /wp-login.php {
allow 203.0.113.10;
allow 198.51.100.0/24;
allow 2001:db8:1234::/48;
deny all;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
allowanddenyare evaluated from the top, and the first match wins. Putdeny all;last.- The nested
location ~ \.php$inherits the outerallow/deny. - To exclude
admin-post.phpas well, add alocation = /wp-admin/admin-post.phpblock in the same form asadmin-ajax.php. - Match the
fastcgi_passsocket path to your existinglocation ~ \.php$configuration. If you usesnippets/fastcgi-php.confon Debian or Ubuntu, you canincludethat instead.
After editing, check the syntax and then reload.
sudo nginx -t && sudo systemctl reload nginx
Basic Nginx configuration is also covered in "Combining Basic authentication and IP restrictions on Nginx".
Things to watch out for
If you connect over IPv6
If your connection supports IPv6, the browser may connect over IPv6. If you allow only your IPv4 address, you will get a 403 yourself. If curl -6 shows an IPv6 address, add that IPv6 address (or the range your provider assigned you) to the allowed list.
If you use a CDN or reverse proxy
With a CDN such as Cloudflare or a reverse proxy in front, the web server sees the CDN's or proxy's IP address as the client. IP restrictions then do not work as intended. First configure the server to restore the real IP address from X-Forwarded-For or similar headers sent by trusted proxies (mod_remoteip on Apache, real_ip_header and set_real_ip_from on Nginx), and then add the restriction.
Requests from the server itself
WordPress sends HTTP requests to its own server (loopback requests), for example for Site Health checks. If, after adding the restriction, Tools > Site Health reports a loopback request error, add the server's own IP addresses (127.0.0.1 and the server's public IP address) to the allowed list.
wp-cron.php and the REST API
wp-cron.php is in the site root and is not affected by the /wp-admin/ restriction. The REST API used by the block editor (/wp-json/) is also outside /wp-admin/, so this restriction does not block it.
Without a static IP
If your IP address changes, consider one of these:
- Use a VPN with a static IP, and connect to the VPN before opening the admin area
- Protect
/wp-admin/andwp-login.phpwith Basic authentication instead of IP addresses ("How to add Basic authentication to the WordPress admin") - Limit login attempts ("How to limit login attempts")
Some hosting providers offer a control panel setting that restricts access to the admin area from outside the country. Setting that up together with .htaccess makes it harder to tell which one is causing a problem, so keep a note of where each restriction is configured.
Test the setup
Test from both an allowed connection and one that is not allowed (such as a phone on mobile data).
# From an allowed connection: 302 (redirect to wp-login.php) if not logged in
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-admin/
# From a connection that is not allowed: 403 means the restriction works
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-admin/
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-login.php
# admin-ajax.php: 400 means the request reached WordPress (no action was given). 403 means you forgot to exclude it
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://example.com/wp-admin/admin-ajax.php
Also try features that use admin-ajax.php, such as contact forms on public pages or a theme's "load more" button, once from a connection that is not allowed.
In the access log you can count where POSTs to the login screen come from (with the default Apache or Nginx log format).
# Sources sending the most POSTs to wp-login.php (top 20)
grep 'POST /wp-login.php' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# Number of requests blocked with 403
awk '$9 == 403 && $7 ~ /^\/(wp-admin|wp-login\.php)/' /var/log/nginx/access.log | wc -l
For Apache, read the log path as /var/log/apache2/access.log (Debian/Ubuntu) or /var/log/httpd/access_log (RHEL-based systems).
If you lock yourself out
If the admin area returns 403 because your IP address changed or you forgot to allow IPv6, revert as follows over FTP, SFTP, SSH or your hosting provider's file manager.
- Apache: rename
/wp-admin/.htaccess(for example, to.htaccess.off) or add your new IP address to theRequire iplines. Fix the<Files "wp-login.php">section in the root.htaccessthe same way - Nginx: add your new IP address to
allowin the configuration file, runsudo nginx -t, thensudo systemctl reload nginx
To narrow down the cause of a 403 error in the admin area, also see "How to fix a 403 error in wp-admin".
Settings to combine with IP restrictions
IP restrictions narrow down who can open the admin area; they do not reduce the damage after someone logs in. Setting the following constant in wp-config.php disables editing theme and plugin files from the admin area.
<?php
// Put this in wp-config.php above "That's all, stop editing!"
define( 'DISALLOW_FILE_EDIT', true ); // Disable the theme and plugin file editors
Setting DISALLOW_FILE_MODS to true also prevents adding or updating plugins and themes from the admin area, as well as updating WordPress core. Unless you handle updates with WP-CLI or some other method, leaving it unset helps you avoid missed updates.
Summary
- On Apache 2.4, put
Require ipin/wp-admin/.htaccessand excludeadmin-ajax.php(andadmin-post.phpif needed) withRequire all granted.Order/Deny/Allowis Apache 2.2 syntax. - To protect the admin area, also limit
wp-login.phpin the root to the same IP addresses. - On Nginx, PHP is not passed to PHP-FPM unless you nest the PHP handling inside
location ^~ /wp-admin/. - Decide which IP addresses to allow with IPv6, CDNs and reverse proxies, and requests from the server itself in mind, and confirm that a connection you have not allowed gets a 403.
References: Apache HTTP Server 2.4 – Access Control, Apache – mod_authz_core (Require), nginx – ngx_http_access_module, WordPress – Brute Force Attacks, WordPress – Hardening WordPress
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)