Closing the doors

Block an address, or the range behind it

An IP rule is the bluntest control you own. Written well it takes one noisy network out of your logs in a second. Written badly it blocks a mobile carrier, or it blocks you, and both mistakes are quiet until somebody complains.

Checked against a live WordPress install on .

What an IP rule stops, and what it does not

An IP rule blocks an address. It does not block a person, and it does not block software. That sounds obvious until you look at what the traffic hitting a WordPress site is made of.

Password guessing against wp-login.php is mostly distributed. One campaign arrives from hundreds of compromised machines, each one trying a handful of passwords, and blocking any single address moves the count by a fraction of a percent. Blocking the whole network they belong to often does move it, which is why the useful unit here is a range and not an address.

Where an IP rule earns its place:

  • One source, sustained. A scraper, a vulnerability scanner, or a single machine that has decided your xmlrpc.php is interesting. It shows up as thousands of lines from one address.
  • A hosting range with no business here. Real visitors do not browse from a datacentre. If a whole provider network only ever appears in your log probing paths, it costs you nothing to remove it.
  • An allow list on one door. Restricting /wp-admin/ and wp-login.php to addresses you control is a stronger control than any password policy, because it removes the door instead of guarding it.

Where it fails, and these are the ones that cost you customers:

  • Rotating addresses. Consumer broadband hands out a new address on a reboot. The attacker gets a new one too, and yours is now on the list you wrote yesterday.
  • Shared addresses. Mobile carriers put tens of thousands of subscribers behind one address with carrier-grade NAT. Corporate offices do the same at a smaller scale. Blocking one address there blocks a crowd, and none of them will email you about it.
  • Anything cheap to move. A blocked attacker rents another address and starts again the same day.

Treat an IP rule as noise reduction and as a door lock. A determined attacker walks around it, so pair it with controls that do not care where the request came from: rate limiting the login catches the distributed half that no address list will ever reach.

Reading CIDR, and the number you just typed

An IPv4 address is 32 bits, written as four numbers so humans can read it. The slash notation says how many of those bits are fixed. Everything after the fixed part is free to be anything, and every combination is in your rule.

That is the whole idea. 192.0.2.0/24 fixes the first 24 bits, which is the first three numbers, and leaves the last one free: 256 addresses, 192.0.2.0 through 192.0.2.255. Each bit you give back doubles the range.

Prefix Addresses What it usually is
/32 1 One machine. The default when you paste a bare address.
/30 4 A point-to-point link. Rare in a block list.
/28 16 A small block sold to one customer by a hosting provider.
/24 256 The workhorse. Usually the smallest block that gets routed on its own, and the right first guess for "the neighbourhood around this address".
/22 1,024 A common allocation to a hosting or CDN operator.
/20 4,096 A mid-sized provider network.
/16 65,536 A large operator, or a whole country's slice of a small one. Check before you write it.
/8 16,777,216 One in every 256 addresses in existence. You almost certainly do not mean this.

Two habits keep this safe. Write the network address rather than a host address, because every tool masks what you give it down to the prefix and shows you something you did not type. And say the size out loud before you save: if you cannot answer "how many addresses is that", the rule is not ready.

The masking is easy to trip over. Entered as 192.0.2.7/8 on the install this page was checked against, the saved rule read back as 192.0.0.0/8, and it blocked the whole 192.168 private range along with 192.255.255.255, while 193.0.0.1 passed. That is one address typed and sixteen million blocked. The same trap sits in 192.0.2.0/22, whose network address is 192.0.0.0, not the address in the text.

Find the range in your access log

Guessing at ranges is how people end up blocking their own visitors. The log already holds the answer. Find the file first, because every stack puts it somewhere different, and the fastest way is to ask the configuration rather than to hunt.

bash
grep -rnE "CustomLog|access_log" /etc/apache2/ /etc/nginx/ 2>/dev/null | head

On the Apache 2.4.67 install this page was checked against, that pointed at /var/log/apache2/access.log in the combined format. On Red Hat family systems it is usually /var/log/httpd/access_log. On shared hosting you will not be able to read /var/log at all, and the control panel offers the raw log for your own site instead. Every command below assumes the combined format, where the first field is the client address.

Count requests per address

bash
awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20

That is the list of who is talking to you, loudest first. Run on a development instance the top talker was the container's own network address with 14,799 hits out of 17,795 lines, which is the correct answer on that machine and shows you the shape of the output. On a public site the first two or three rows are usually search engine crawlers and the interesting rows start below them.

Group the addresses into networks

One address at the top means one machine. Twenty addresses from the same neighbourhood mean a network, and that is the thing worth blocking. Collapse the last octet and count again.

bash
awk '{print $1}' /var/log/apache2/access.log \
  | grep -E "^[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+$" \
  | awk -F. '{print $1"."$2"."$3".0/24"}' \
  | sort | uniq -c | sort -rn | head

Then ask the more telling question: how many distinct addresses did each network use? A network that sent 5,000 requests from one address is one machine. A network that sent 5,000 requests from 200 addresses is a campaign, and only the second one justifies a range.

bash
awk '{print $1}' /var/log/apache2/access.log \
  | grep -E "^[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+$" | sort -u \
  | awk -F. '{print $1"."$2"."$3".0/24"}' \
  | sort | uniq -c | sort -rn | head

Narrow it to the traffic you care about

Total request counts flatter whoever crawls you hardest. Filter to the endpoints under attack and the picture changes.

bash
awk '$6 ~ /POST/ && $7 ~ /wp-login|xmlrpc/ {print $1}' /var/log/apache2/access.log \
  | sort | uniq -c | sort -rn | head

awk '$9 == 404 {print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head

The first command finds login and XML-RPC pressure. The second finds scanners, because a tool walking a list of known plugin paths generates almost nothing but 404s. Both ran against the checked install.

Field 9 is the status code only while the request line holds its usual three parts. A malformed request shifts every column after it, and 113 lines in the checked log returned - for that field. A handful of odd rows in the output is the log, not your command.

Confirm the real boundary before you write the prefix

A /24 is a guess. The registry knows the actual allocation, and the routing table knows the block the operator announces. Ask both.

bash
whois -h whois.cymru.com " -v 203.0.113.42"

whois 203.0.113.42 | grep -iE "^(cidr|netrange|inetnum|route)"

The first returns the network as it is announced to the internet, with the operator's name next to it. Checked against a public address, it answered with a /24 and the owning organisation on one line. The second returns the registry allocation, which is often wider. Block the announced prefix when you want the machine's neighbourhood, and the registry allocation when you have decided the whole operator can go.

Deny list or allow list

Every firewall on this page offers the same two shapes, and choosing between them is the only decision that really matters.

A deny list is open by default. Everyone gets in except the entries you named. It is safe to switch on, it never locks you out, and it is permanently behind: you can only block what you have already seen.

An allow list is closed by default. Nobody gets in except the entries you named. Against automated attacks it is close to absolute, because an address that is not on the list is refused before anything reads a password. On wp-login.php and /wp-admin/ it is the strongest single rule in this guide.

It is also the rule that ends with you outside. Home connections change address on a router reboot, on a lease renewal, and sometimes for no reason your provider will explain. An allow list built from the address you had this morning stops working the next time your router restarts.

Use an allow list when one of these is true, and a deny list otherwise:

  • A static address. An office line, a leased server, a business connection with a fixed assignment.
  • A VPN with a fixed exit. Allow the exit address, connect before you administer. This also survives you working from a cafe.
  • A jump host. Administer through one machine whose address never changes, and allow only that.

Whichever you pick, do not put your only route back in behind it. Have SSH, a control panel file manager, or database access ready before you save an allow list. The last section of this page is what to do when you did not.

An allow list on the whole site and an allow list on the login are very different risks. The first blocks your visitors when it is wrong. The second blocks only you, and your visitors never notice. If you are hesitating, scope it to the login and the admin.

Where the rule goes, and what each layer costs

The same rule does very different amounts of work depending on where you put it. Each layer down the list lets the request travel further before something says no, and each one is easier for you to reach.

1. The provider's edge

A cloud firewall, a CDN rule, or a security group in front of your server. The request never arrives. It costs the attacker a full connection attempt into somebody else's infrastructure and costs you nothing at all, not a socket, not a log line, not a byte of bandwidth.

This is the only layer that helps under real volume, and it is the layer most WordPress owners do not have. If your host offers it, use it first and treat everything below as a supplement.

2. The host firewall

Packet filtering on the machine itself. The connection is refused or silently dropped before the web server accepts it, so no PHP process is created and no worker is tied up. Both toolchains below accepted the rule and listed it back.

bash
# iptables 1.8.11
sudo iptables -I INPUT -s 192.0.2.0/24 -j DROP
sudo iptables -S INPUT

# nftables 1.1.5
sudo nft add table inet filter
sudo nft add chain inet filter input '{ type filter hook input priority 0; }'
sudo nft add rule inet filter input ip saddr 192.0.2.0/24 drop
sudo nft list chain inet filter input

What it costs you: root access, which most shared hosting does not give you, and the rules vanish on reboot unless your distribution saves them. What it costs you if you are careless: your own SSH session, because INPUT covers every port and not just the web server. Add the rule from a console you did not reach over the network, or schedule a command that flushes it in ten minutes before you start.

3. The web server

The connection is accepted and the TLS handshake completes, then the server answers 403 on its own. No PHP process starts, no database connection opens, and no plugin code runs. On nginx 1.31.3 the config below returned 403 to an address inside the range and 200 to one outside it.

nginx
server {
    # Deny list: everyone except these.
    deny 192.0.2.0/24;
    deny 198.51.100.0/24;

    # Allow list, scoped to the login. Order matters: allow, then deny all.
    location = /wp-login.php {
        allow 203.0.113.42;
        deny all;
    }
}

Apache does the same thing with Require, and this is where most guides on the subject are wrong. A negative Require has to sit inside a container. Measured on Apache 2.4.67 with AllowOverride All: the block below returned 403, and the same Require lines written bare at the top of the file returned 500.

.htaccess apache
<RequireAll>
    Require all granted
    Require not ip 192.0.2.0/24
    Require not ip 198.51.100.0/24
</RequireAll>

The allow-list form needs no container, and wrapping it in <Files> scopes it to one file. Measured: the rule below returned 403 to an address outside the range, 200 to one inside it, and left every neighbouring file untouched at 200.

.htaccess apache
<Files "wp-login.php">
    Require ip 203.0.113.42
</Files>

Two warnings on the Apache side. A file it cannot parse returns 500 for every request in that directory, so a site that broke the instant you saved has a syntax error and not a second problem. And Apache reads .htaccess only where AllowOverride permits it, with the documented default being None. If your rule appears to do nothing at all, that is the first thing to check. The page on injected rewrite rules has a one-minute probe for it.

Where a control panel puts it

cPanel exposes this as IP Blocker under Security. Its own documentation, read on 18 August 2026, says it accepts a single address, a range, an implied range and CIDR, converts everything it is given into CIDR, and writes the result into .htaccess. So it is the Apache layer above with a form on top, and it blocks website access only: mail, FTP and SSH are not affected. Other panels differ, and the way to tell which layer yours uses is to block a test address and see whether the connection is refused outright or answered with a 403 page.

4. Inside WordPress

A rule enforced by a plugin runs last. PHP has started, WordPress has loaded, the database connection is open, and only then is the address compared against your list. The request stops before your login form and before your content, and the work already spent is spent.

There is a second consequence people miss. Static files never reach this layer at all. Measured on a live install with the client address in the deny list: the home page, wp-login.php and admin-ajax.php all returned 403, while wp-includes/js/wp-embed.min.js returned 200, because the web server answered that one without ever asking WordPress.

That is a real difference and it is worth knowing before you rely on it. It is also what you have when you do not control the server, which is most shared hosting. For a scraper hitting your search page or a bot pounding the login, stopping the request at WordPress still removes the thing you were actually worried about.

The same layer question decides whether country blocking is worth switching on, and the answer there has an extra wrinkle about accuracy. Blocking a country covers it.

Behind a proxy, every request looks the same

This is the part that quietly breaks everything above. Put a CDN or a load balancer in front of your site and your web server stops seeing visitors. It sees the proxy. Every request in your log carries one of a few dozen addresses, and the visitor's real address is only present in a header the proxy added.

Two failures follow from that, and they are opposites:

  • Your rule blocks nobody. You copy an attacker's address out of a report and deny it. That address never appears at your server, so the rule never matches, and you conclude the firewall is broken.
  • Your rule blocks everybody. You copy an address out of your own log and deny it. It was the proxy. Your entire audience is now behind a 403.

The fix is to tell the web server which addresses are proxies, so it replaces the connection address with the one in the forwarded header. On nginx that is the real IP module, and the measurement is worth reading closely. With a deny 192.0.2.0/24 in place on nginx 1.31.3, a request carrying X-Forwarded-For: 192.0.2.5 was answered 200 while the module was not configured. After adding the two lines below, the identical request was answered 403, and a request forwarding a different address still got 200.

nginx
set_real_ip_from 103.21.244.0/22;   # one line per proxy range you actually use
real_ip_header    X-Forwarded-For;
real_ip_recursive on;

Apache does the same job with mod_remoteip: RemoteIPHeader X-Forwarded-For plus one RemoteIPTrustedProxy line per range.

Why the proxy list has to be exact

A forwarded header is text that arrived over the network. Anyone can send one. Curl will put any address you like in X-Forwarded-For, which is exactly how the 200 above was produced.

So the header is only worth anything when the request came from a machine you already trust. Trust the wrong range and you have handed every visitor the ability to claim any address: to walk through a deny list, to reset somebody else's rate limit, or to pin a lockout on an address that never sent a request. A proxy list that is too wide is worse than no proxy list, and a proxy list of 0.0.0.0/0 means the firewall now believes whatever the attacker types.

Two rules keep this straight. Trust only the ranges your own traffic actually passes through, published by the operator and kept current. And read the chain from the right: the rightmost entry in X-Forwarded-For was added by the hop nearest you, so you walk leftwards while each hop is one you trust and stop at the first that is not. That address is the client. Reading from the left instead takes whatever the client typed first, which is the whole bug.

The firewall described in the next section does exactly that walk, and it keeps its own list of proxy ranges so you do not have to maintain one. The list is downloaded once a day, its signature is checked before anything is stored, and any entry broader than a /8 for IPv4 or a /16 for IPv6 is discarded on import, so a bad list cannot quietly mark the whole internet as trusted. Cloudflare's connecting-IP header gets an extra condition: it is only believed when the machine that opened the connection is inside Cloudflare's own published ranges.

IPv6 needs a /64, not a /128

IPv6 addresses are 128 bits. The slash means exactly what it means in IPv4, and the sizes it produces are nothing like it. A /128 is one address, the equivalent of a /32 in IPv4, and it is almost always the wrong rule.

One network gets a /64. That is 18,446,744,073,709,551,616 addresses, and a single machine on that network can use any of them. Many operating systems generate temporary addresses by default, which means the host picks a fresh one inside its own /64 on a timer. The /128 you wrote this morning may not be the address that machine is using tonight, while the /64 stays true for as long as the connection does.

Above that, sizes are a policy question rather than a fact. RFC 6177 drops the old fixed /48 default, declines to name one correct size, names /56 as a workable example for home users, and says an end site should get more than a single /64. So for a persistent source behind a residential connection, a /64 is the minimum that means anything and a /56 is often the unit the provider actually handed out.

Do not translate IPv6 prefixes into address counts when you are deciding. The numbers are meaningless at that scale. Count networks instead: a /64 is one network, a /56 is 256 of them, a /48 is 65,536.

IPv4 and IPv6 rules live together. On the install this page was checked against, 192.0.2.0/24, 203.0.113.42/32 and 2001:db8:1234:5678::/64 were saved in one field and read back unchanged. Matching behaved as written: 2001:db8:1234:5678::dead was blocked and 2001:db8:1234:5679::1, one network along, was not.

Prove the rule is doing something

A firewall rule that silently does nothing looks exactly like one that works, because in both cases your site keeps loading for you. Three checks settle it.

Request the site from inside the range

The direct test, when you can arrange it. A phone on mobile data, a VPN exit, or any machine on the network you blocked.

bash
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/

curl -s --max-time 5 -o /dev/null -w "%{http_code}\n" https://example.com/ ; echo "exit $?"

403 is a rule at the web server or inside WordPress. A timeout, which the second command reports as exit code 28 with no status, is a packet filter dropping the traffic before anything answers. 200 means nothing is blocking you and the rule is not where you think it is.

Ask which layer answered

Request a static file that the web server hands over without touching PHP.

bash
curl -s -o /dev/null -w "%{http_code}\n" \
  https://example.com/wp-includes/js/wp-embed.min.js

403 on both the home page and this file puts the rule at the web server or below. 403 on the home page and 200 on this file puts it inside WordPress. That exact pair was measured on the checked install.

Read the log back

The log tells you what the rule is doing to real traffic, without you having to find a machine in the range.

bash
awk '$1 ~ /^192[.]0[.]2[.]/ {print $9}' /var/log/apache2/access.log \
  | sort | uniq -c | sort -rn

Requests from that network turning into 403 means a rule at the web server or in WordPress is firing, and each of those lines is a request your server still paid for. Requests from that network stopping entirely means a packet filter or an edge rule is catching them before the web server ever sees them, which is the outcome you want. No change at all means the rule is not matching, and the usual reason is the section above this one.

The same two lists on one screen

Everything above assumes you can reach a configuration file. On shared hosting you often cannot, and the rules then have to live where your code does. Segurium puts both list shapes on one tab, at wp-admin/admin.php?page=segurium&tab=firewall.

The Firewall tab of a WordPress plugin. A yellow banner across the top reads that settings were applied and revert in 60 seconds, with Confirm and Revert buttons. Below it an Enable Firewall checkbox is ticked, a Mode pair of radio buttons has Allow everything except selected over Deny everything except, and a Blocked IPs slash CIDRs box holds three lines: 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.42/32. Underneath, a status line with a green tick reads Known CDNs, proxies: 5696 CIDRs, updated today, next to a Refresh now link, above an empty custom trusted proxy box and a Save button.
The Firewall tab with a deny list of three entries, the auto-maintained proxy list reporting 5,696 ranges, and the confirm-or-revert banner counting down.

The two radio buttons are the choice from earlier on this page. "Allow everything except" is the deny list. "Deny everything except" turns the same box into an allow list, and every address not in it is refused.

One box takes both address families and both notations. The three entries in the screenshot are a /24, another /24 and a single address written as a /32; an IPv6 prefix such as 2001:db8:1234:5678::/64 goes in the same box on its own line. A bare address with no slash is stored as a /32, or a /128 for IPv6.

The status line under the second box is the proxy list from the previous section, refreshed daily and reporting 5,696 ranges on the install this was captured from. The box below it is for proxies of your own that no published list knows about, an in-house load balancer or a corporate egress gateway. Leave it empty if your traffic only passes through services the list already covers.

The banner across the top is the part worth pausing on. Saving an enabled firewall applies the change immediately and stages a revert 60 seconds later. Press Confirm and it sticks. Press Revert, close the tab, or lose access because the rule you just wrote excluded you, and the previous settings come back on their own. The check runs before the block does, so even a request from an address you have just locked out triggers the restore.

Two limits to know. The field does not reject a prefix that is out of range for its address family: 192.0.2.5/33 was accepted and stored as a rule that matched neither 192.0.2.5 nor 192.0.2.6, so read back what you typed. And an address in the allow list is also exempt from login rate limiting, which is convenient for an office address and worth knowing before you allow-list something broad.

The rules, the proxy feed and the countdown all ship in every install at no cost, alongside the rest of the hardening features.

When you block yourself

It happens two ways. You deny a range that turns out to contain your own address, or you build an allow list and your address is not in it. Both look identical from the browser: every page of the site, including the login and the admin, answers 403 with a short "Access Denied" page. Static files still load, which makes it look like a half-broken site rather than a rule.

Work through these in order. Each one needs more access than the last.

  1. Wait 60 seconds. If you locked yourself out by saving from the settings screen and never pressed Confirm, the staged change reverts on its own. Reload after a minute before you touch anything else. This is the reason the countdown exists, and it is the fix for most lockouts.
  2. Come back from an allowed address. Tether to a phone, connect the VPN whose exit you allowed, or use any other network. A deny list rarely covers two networks at once.
  3. Turn the feature off over SSH. WP-CLI is the fastest route, with one flag that matters.
  4. Change the row directly in the database. Last resort, and it works when PHP will not run your commands at all.

WP-CLI, with plugins skipped

A plain WP-CLI command is not exempt from the rule you just wrote, and the reason is worth understanding before you are in a hurry. WP-CLI 2.12.0 presents itself to WordPress with a client address of 127.0.0.1. An allow list that names your office address does not name that one, so the firewall refuses the command line as well. Measured on a live install in allow-list mode: wp option get blogname exited with Error: Access from your location is not allowed.

--skip-plugins loads WordPress without loading any plugin, so nothing is there to block you. The same command with the flag printed the site title, and the update below brought the site back to 200 on the next request.

bash
wp --skip-plugins option update segurium_firewall_enabled 0

# Confirm from the outside
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/

Read the rules back the same way before you switch it on again, so you can see which entry caught you.

bash
wp --skip-plugins option get segurium_firewall_mode
wp --skip-plugins db query \
  "SELECT INET6_NTOA(ip) AS network, cidr_bits, list_type \
   FROM wp_segurium_ip_list WHERE source = 'firewall_rule';"

The database, when nothing else is available

phpMyAdmin, Adminer, or the mysql client. One statement, and it works because the enabled flag is checked before any address list is read, so nothing else has to be consistent for the site to come back.

sql
UPDATE wp_options
   SET option_value = '0'
 WHERE option_name = 'segurium_firewall_enabled';

Change wp_ to your own table prefix. Setting the value back to '1' re-enables the firewall with your rules intact, which was verified in both directions on the checked install.

Do not try to fix this by deleting rows from the rules table instead. Matching is served from a cached copy on disk that is rebuilt from a version counter, and a hand-written DELETE does not touch the counter. Measured: after deleting every rule row by hand, a fresh PHP process still matched the deleted rule and kept blocking. The flag in wp_options is the switch that always works.

Then make the lockout impossible next time

Before you re-enable an allow list, check your own address the way the server sees it, not the way an address-lookup site reports it, because the two differ the moment a proxy is involved. Load any page of your site and read the last line of the access log.

bash
tail -1 /var/log/apache2/access.log | awk '{print $1}'

Put that address in the list, add whatever second network you can reach the site from, and keep a terminal open until you have pressed Confirm from a different browser. If your home connection has no fixed address, allow the network rather than the address: your provider hands out from a pool, so a /24 or the announced prefix from the whois command earlier survives a router reboot that a /32 does not.

Questions

How many addresses does a /24 block?
256. The number after the slash counts the bits that are fixed, and the 32 minus that many bits left over are free, so a /24 fixes three of the four octets and covers x.y.z.0 through x.y.z.255. The three sizes worth memorising: /32 is one address, /24 is 256, /16 is 65,536. A /8 is 16,777,216 addresses, one in every 256 on the IPv4 internet, and almost nobody who types one means it.
I typed 192.0.2.7/24 instead of 192.0.2.0/24. Does that matter?
Not to the result. Every tool masks the address down to the prefix you gave, so 192.0.2.7/24 becomes 192.0.2.0/24 and blocks the same 256 addresses. It matters when the prefix is wrong: 192.0.2.7/8 quietly becomes 192.0.0.0/8. Measured on a live install, that rule blocked the whole 192.168 private range and 192.255.255.255 while 193.0.0.1 walked through. Write the network address so the number you see is the number you get.
Why does my block do nothing when the site is behind Cloudflare?
Because the address in the rule never reaches your server. Every request arrives from the proxy, so your web server sees a Cloudflare address and the visitor's address is only in a header. A rule against the client address matches nothing, and a rule against the proxy address matches everyone. Fix the proxy configuration first (set_real_ip_from on nginx, mod_remoteip on Apache), then write the rule, then check the log shows real client addresses again.
Can I whitelist my IP for wp-admin and block everyone else?
Yes, and it is the strongest rule on this page. It is also the one that locks people out, because most home connections get a new address on a router reboot or a lease renewal. Use it if you have a static office address, a VPN with a fixed exit, or a jump host. Before you save it, make sure you have a way back in that does not depend on the rule: SSH, WP-CLI with plugins skipped, or database access.
Is blocking an IP inside WordPress worth anything, if PHP already ran?
It stops the request reaching your login form, your content and your database queries, which is what most people want from it. It does not give back the CPU the request already spent, because PHP started and WordPress loaded before the rule was read. Measured on a live install, a blocked address got a 403 on the home page and on admin-ajax.php while a static JavaScript file still returned 200, because the web server answered that one without asking WordPress. If you control the server, put the rule in front of PHP. If you are on shared hosting, in WordPress is what you have.
Does a /128 work for IPv6?
It works, and it usually misses. A single machine normally holds a whole /64 and rotates its address inside that range on a timer, so the /128 you wrote this morning may not be the address it uses tonight. Write the /64 for one network. RFC 6177 leaves the assignment size to operators, drops the old fixed /48 default, and says an end site should get more than a single /64, so a persistent source behind a home connection is often better matched by a /56.

Next

Rules you write once, and a list you never maintain

The rules on this page are cheap to write and expensive to maintain. The list of ranges worth blocking grows every month, the addresses in it go stale, and the proxy list underneath it changes whenever a CDN adds capacity. That last one is the part nobody keeps up with by hand, and it is the part that decides whether any of the other rules match the right request.

The Firewall tab keeps that list current for you and applies your own entries on top of it, with a countdown that undoes the save if the rule you just wrote turned out to include you. Both list shapes, both address families, and the confirm-or-revert window come with every install.