What you'll have when you finish
A WordPress install that refuses traffic from countries you have chosen to block, refuses requests from named scraper and crawler bots regardless of how they identify themselves, and serves the right cross-origin headers when, and only when, another domain actually needs to call your site. All three layers run at the web-server level (or, on Nginx, at the earliest possible PHP hook) so blocked traffic never reaches the WordPress bootstrap and never costs you CPU.
- Prerequisites: A working AdminEase install. For country blocking, either a Cloudflare account in front of your site or the PHP GeoIP extension installed on your server.
- You'll need access to: WordPress admin, plus the ability to check your server stack (Apache, LiteSpeed, or Nginx) and confirm whether the PHP GeoIP extension is loaded.
- Time: 20 to 30 minutes if you have to set up Cloudflare from scratch, 10 to 15 minutes if you already use it.
CF-IPCountry header or the PHP GeoIP extension), and the toggle is disabled entirely when neither is available. Bot blocking writes .htaccess rules on Apache and LiteSpeed but falls back to a PHP filter on Nginx. Take five minutes to confirm your stack before you start.
- Confirm what country-detection backend your server has
- Set up Cloudflare for country blocking (recommended path)
- Alternative path: PHP GeoIP extension
- Enable country blocking and pick the country list
- Verify your country block with a VPN
- How bot blocking behaves on your stack: Apache versus Nginx
- Enable bot blocking and choose from the common bot list
- Add custom bot user-agents
- Decide whether you actually need CORS headers
- Enable CORS, and tighten the default wildcard
Three features sit in this tutorial: country blocking, bot blocking, and CORS headers. The first two are about keeping the wrong traffic out. The third is about letting the right traffic in. All three deserve the same care, because misconfiguring any of them will either let bad traffic through or block traffic you actually wanted.
Country and bot settings live at AdminEase › Security › Access Control. CORS sits at AdminEase › Security › Hardening because it is a response-header change rather than an access rule. Each step below names the exact path.
1. Confirm what country-detection backend your server has
Country blocking only works if AdminEase has a way to tell which country a visitor is coming from. The plugin supports two methods, in this priority order:
- Cloudflare: if your site sits behind Cloudflare, every request carries a
CF-IPCountryheader containing the visitor's two-letter country code. AdminEase detects the presence of this header and uses it. Cloudflare's free tier is sufficient. - PHP GeoIP extension: a server-side PHP extension that maps IP addresses to country codes using a local database. Requires installation by your host or system administrator.
If neither is available, the country-blocking toggle in AdminEase is disabled and the help text under the toggle reads "Unfortunately your server does not support this feature." There is no third fallback.
2. Set up Cloudflare for country blocking (recommended path)
Cloudflare is the recommended backend because it does the IP-to-country lookup on Cloudflare's edge, before the request reaches your server, and the country code arrives as a simple header your .htaccess rule can read. There is no database to keep updated, no PHP extension to maintain, and the free tier includes the CF-IPCountry header.
CF-IPCountry header automatically.
You do not need to enable any specific Cloudflare feature for the header to work. It ships on every request by default, on every plan, including Free. AdminEase detects the header presence on its next settings save.
wp-config.php at the real visitor IP using one of the standard HTTP_CF_CONNECTING_IP snippets. Many WordPress logging and rate-limit plugins still read REMOTE_ADDR and will otherwise see every visitor as a Cloudflare edge IP.
3. Alternative path: PHP GeoIP extension
If you cannot or do not want to put Cloudflare in front of the site (custom edge setup, server-only deployment, regulatory constraints), the PHP GeoIP extension is the alternative. It runs entirely on your server and reads from a local IP-to-country database.
Installation depends on your stack. On most managed hosts you have to open a support ticket because the extension is not in the default PHP build. On a self-managed Ubuntu/Debian server with Apache, the rough sequence is:
sudo apt install libapache2-mod-geoip geoip-database sudo a2enmod geoip sudo systemctl restart apache2
This installs the Apache mod_geoip module that AdminEase's .htaccess rule depends on, plus the country database. Once the module is loaded, AdminEase will detect it and unlock the country-blocking toggle. The detection runs through Utils::is_geoip_enabled() on every settings save.
geoipupdate tool on Linux is the standard). A stale database will block legitimate visitors and let blocked-country visitors through.
4. Enable country blocking and pick the country list
Once one of the backends is detected, the country-blocking toggle becomes active.
A small detail worth knowing: AdminEase silently removes your own country from the dropdown list. This is a guardrail. If you are running the WordPress admin from Germany and accidentally select Germany from the dropdown, you will lock yourself out within seconds. The plugin will not let you do that.
When you save, AdminEase writes one or two rule blocks to .htaccess depending on which backend is active. If Cloudflare is detected, you get something like this (between # ADMINEASE_BLOCK_SPECIFIC_COUNTRIES BEGIN and END markers):
<IfModule mod_rewrite.c>
RewriteEngine On
# Cloudflare compatibility
RewriteCond %{HTTP:CF-IPCountry} ^RU$ [OR]
RewriteCond %{HTTP:CF-IPCountry} ^CN$ [OR]
RewriteCond %{HTTP:CF-IPCountry} ^KP$
RewriteRule ^(.*)$ - [F,L]
</IfModule>
If PHP GeoIP is detected, AdminEase additionally writes a parallel mod_geoip block:
<IfModule mod_geoip.c>
GeoIPEnable On
SetEnvIf GEOIP_COUNTRY_CODE ^(RU|CN|KP)$ BlockCountry
Order Allow,Deny
Allow from all
Deny from env=BlockCountry
</IfModule>
Blocked visitors get an HTTP 403 response. No redirect, no PHP, no WordPress, no database query. The block happens at the Apache layer before WordPress has any idea a request came in.
5. Verify your country block with a VPN
Geo-blocking is the kind of feature that quietly fails. The toggle is on, the .htaccess looks right, and yet visitors from the supposedly blocked country still get through because the GeoIP database is stale, or Cloudflare's caching is serving an older response, or your test VPN endpoint resolves to a different country than its label suggests.
.htaccess markers, confirm the backend is what you think it is, and (on Cloudflare) purge the Cloudflare cache.
6. How bot blocking behaves on your stack: Apache versus Nginx
Bot blocking is the feature where your server stack matters most. AdminEase detects the server via SERVER_SOFTWARE and chooses a path:
- Apache, and LiteSpeed in Apache-compatibility mode (where
apache_get_modules()is available): AdminEase writesRewriteCond %{HTTP_USER_AGENT}rules to.htaccess. Blocking happens at the web server, before PHP runs. - Nginx, or any other stack that does not read
.htaccess: AdminEase falls back to a PHP-layer block, hooked toinitat priority 1. It checks theUser-Agentagainst your blocked list and returns HTTP 403 immediately, before WordPress finishes loading.
Both paths produce the same outcome for blocked bots (a 403 response), but the Apache path is faster because PHP never starts. On Nginx the PHP-layer block is still effective: it runs before WordPress's main bootstrap, so the cost is minimal compared to serving a full page.
.htaccess exactly like Apache, and the function apache_get_modules() is exposed. In its native mode it ignores .htaccess. AdminEase only takes the Apache path if both checks pass. If you are on LiteSpeed and the bot rules do not appear in .htaccess, the plugin has detected native mode and stored the bot list as a WP option instead. Check the adminease_blocked_bots option to confirm.
7. Enable bot blocking and choose from the common bot list
The common-bot list is grouped into categories (AI scrapers, SEO crawlers, content scrapers, and others). For most sites the right starting selection is everything in the AI scrapers and content scrapers groups, because those bots ignore robots.txt and exist to harvest content rather than index it. SEO crawlers (Ahrefs, Semrush, Majestic) are a judgment call: if you use one of them yourself, leave it unblocked, otherwise block the rest.
8. Add custom bot user-agents
The textarea takes one user-agent string per line. AdminEase sanitizes each entry: only alphanumeric characters, spaces, hyphens, underscores, and dots are allowed, each entry capped at 100 characters, and the total list capped at 200 entries to keep the .htaccess rules performant.
Where do you find the user-agents to add? The most reliable source is your own logs. AdminEase's Network Viewer Log (a separate feature in the Debug section) records requests with their user agents. Sort by request count, identify the ones causing problems, and copy the relevant substring of the user-agent into this textarea.
Mozilla/5.0 (compatible; ScrapyBot/2.1; +http://example.com), you only need to add ScrapyBot to the custom list. AdminEase's rule uses a case-insensitive match ([NC] on Apache, stripos in PHP), so any substring of the user agent is enough.
RewriteCond per chunk, OR-chained, ending in a single RewriteRule. This pattern is the recommended workaround for Apache's per-condition character limit and keeps the regex pattern length under control on long block lists. Special characters in bot names (dots, parentheses, plus signs) are escaped for the regex automatically.
9. Decide whether you actually need CORS headers
CORS is the easiest thing to enable badly. The default AdminEase rule allows requests from any origin (Access-Control-Allow-Origin: *) and allows credentials. For most sites that is more permissive than necessary, and for some sites it is actively wrong.
The question to ask before enabling CORS: is there a specific domain that needs to call my site's REST API or load my assets from JavaScript? If yes, you need CORS, and you should restrict the allowed origin to that specific domain. If no, leave CORS off. A site that does not need CORS does not benefit from having the headers enabled, and the wildcard default makes your data marginally easier to scrape.
10. Enable CORS, and tighten the default wildcard
The rule AdminEase writes to .htaccess (between # ADMINEASE_CORS_HEADERS BEGIN and END markers) sets five response headers:
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, X-WP-Nonce, Accept, Origin"
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Max-Age "3600"
</IfModule>
The accompanying mod_rewrite block handles preflight OPTIONS requests by returning 200 immediately, so browsers do not have to wait for WordPress to process a request that has no body.
Allow-Origin: * works but is not what you want in production. After enabling, replace the wildcard with your specific allowed origin by editing .htaccess directly between the BEGIN and END markers. For a single domain: Header always set Access-Control-Allow-Origin "https://app.example.com". AdminEase's marker block is the safe edit zone. Anything inside the markers is preserved across plugin saves.
Note that Access-Control-Allow-Origin only accepts one origin or *, not a list. If you genuinely need to allow several specific origins, the right pattern is a small mod_rewrite snippet that echoes the request's Origin header back into the response header when it matches an allowed list. That is beyond what AdminEase writes by default and is a manual addition outside the plugin's marker block.
Where to go from here
You have now layered three different access controls on top of the basic hardening from the previous tutorial. Country blocking deals with the geography of attack traffic, bot blocking deals with the identity of automated traffic, and CORS sets the rules for legitimate cross-origin requests. Together they cover most of what you can do at the request layer before the application layer takes over.
The next tutorial in this series, "Locking Down Your Site: Maintenance Mode and Full-Site Password Protection," moves up a level. Instead of filtering specific traffic types, it walks through gating the whole site behind either a maintenance message or a shared password, which is what you reach for during migrations, staging cycles, and pre-launch reviews.
Frequently asked questions
Can I block a city or region rather than a whole country?
Not with AdminEase's country blocker. Both backends (Cloudflare's CF-IPCountry header and the PHP GeoIP mod_geoip module) only expose the country code. City-level blocking requires a more detailed GeoIP database and rules outside the plugin. If you genuinely need it, do the city-level filter in Cloudflare's WAF or via a custom .htaccess block alongside AdminEase's marker block.
Will bot blocking hurt my SEO?
Only if you block the wrong bots. AdminEase's common-bot list separates SEO and search-engine crawlers from content scrapers and AI scrapers. As long as you do not select Googlebot or Bingbot from the list, your search visibility is unaffected. If you do want to verify, the Network Viewer Log will show you the actual user-agent strings hitting your site so you can confirm Google is still crawling.
What happens if Cloudflare goes down?
If Cloudflare is the only backend you have, country blocking stops working until Cloudflare returns. Requests bypass Cloudflare and hit your origin directly without the CF-IPCountry header, so the RewriteCond never matches and the block falls through. This is not a disaster (the site still works), but it means a country you intended to block is briefly unblocked. If that risk matters, install the PHP GeoIP extension as well: AdminEase writes both rule blocks, and either backend matching is enough to deny a request.
How do I see which bots are actually hitting my site?
Enable the Network Viewer feature under AdminEase › Debug › Network Viewer. It logs incoming requests with their user agents and source IPs. Run it for 24 to 48 hours, then sort by user agent. The worst offenders will be obvious: same user-agent, hundreds or thousands of hits, often from a small set of IPs. Those are the candidates for the custom-bots textarea.
Can I block IP addresses directly instead of countries or bots?
AdminEase does not include a per-IP blocklist UI in the current release. For ad-hoc IP blocking, edit .htaccess outside the AdminEase marker blocks and add a Require not ip 1.2.3.4 directive. AdminEase will preserve anything outside its own markers across saves.
Will any of this work on Nginx?
Bot blocking does, via the PHP-layer fallback. Country blocking does not currently, because both rule blocks AdminEase writes are .htaccess (Apache or LiteSpeed in Apache-compat mode). CORS headers do not, for the same reason. On Nginx you would need to translate the country and CORS rules into server block directives manually, or move Cloudflare in front of the site and rely on Cloudflare's own firewall rules for country blocking instead.