Your "Deny from all" is doing nothing: LiteSpeed, AIOWPS and the 5G blacklist

A WordPress site kept serving requests despite a Deny from all rule. Tracing LiteSpeed's handling of AIOWPS's legacy blacklist revealed the conflict and a rewrite rule restored the intended restriction.

Share
Your "Deny from all" is doing nothing: LiteSpeed, AIOWPS and the 5G blacklist

I needed to lock a WordPress site down to a single IP for a few hours. The kind of thing you do without thinking:

Order deny,allow
Deny from all
Allow from 203.0.113.10

Saved it at the top of public_html/.htaccess, reloaded, and got the homepage. Fine, wrong IP maybe. Replaced the whole thing with a bare Deny from all. Still 200. Meanwhile a customer was happily editing pages in wp-admin. This is the story of why, and what the fix is, because the answer wasn't where I expected it.

Ruling out the boring stuff

The server is a cPanel/CloudLinux box running LiteSpeed Enterprise 6.3.7, behind Cloudflare. The usual suspects, in the order I eliminated them:

Wrong docroot. cPanel and httpd.conf agreed: /home/<user>/public_html. The file was there, 644, named correctly, no BOM.

Cache. Not it. The customer's requests were POST /wp-admin/admin-ajax.php with 200s, nothing LSCache would ever serve.

Not reaching the origin. The domlog showed real client IPs (Cloudflare's "real IP" forwarding was working) and the requests were landing on this vhost.

AllowOverride. Confirmed the vhosts had AllowOverride All and no account-specific userdata includes were overriding it.

.htaccess not parsed at all. This is where I made my first mistake. I assumed dropping a garbage line into the file would trigger a 500 error. I tried it and got 200 instead. On Apache that proves the file isn't being read. On LiteSpeed it proves nothing: unknown directives in .htaccess are logged and ignored, never fatal. I lost fifteen minutes to that.

The server itself. Switched the box to Apache. Immediate 403. Switched back to LiteSpeed. 200. Tried LiteSpeed 6.3.6. Same. Reproduced on a second account on the same server. At this point I was fairly sure I'd found a LiteSpeed bug.

What the debug log actually said

I turned LiteSpeed's log level to DEBUG, sent one request, and grepped for the file:

[HTAccess] Updating configuration file [/home/<user>/public_html/.htaccess]
[.htaccess:1]  processing directive: Deny from all.
...
[.htaccess:73] processing directive: SetEnvIfNoCase User-Agent (binlar|casper|...|zmeu) keep_out.
[.htaccess:74] processing directive: <limit GET POST PUT>.
[.htaccess:75] processing directive: Order Allow,Deny.
[.htaccess:76] processing directive: Allow from all.
[.htaccess:77] processing directive: Deny from env=keep_out.
[.htaccess:78] processing directive: </limit>.

So the file was parsed, and my Deny from all on line 1 was processed. Seventy lines later, All In One WP Security's "5G Blacklist Firewall" block does Order Allow,Deny / Allow from all / Deny from env=keep_out. That block is a verbatim copy of Jeff Starr's 2013 5G blacklist, and it lives in every site that has that AIOWPS feature enabled. Both of my "other vhost" tests had it too.

Why Apache and LiteSpeed disagree

Apache's mod_access_compat is explicit about how Order/Allow/Deny are evaluated: every Allow line in the context forms one group, every Deny line forms another, the order they appear in the file doesn't matter, and for Order Allow,Deny a match in the Deny group rejects the request regardless of what the Allow group matched. <Limit> only attaches a method mask to the directives inside it; it doesn't start a new context.

So on Apache my file collapsed to: Allow = {all}, Deny = {all, env=keep_out}. Deny matches, 403. Position irrelevant, by design.

LiteSpeed doesn't document how it evaluates these directives at all, but the behaviour is consistent and old. There's a LiteSpeed support forum thread from 2009 where someone reports that order allow,deny + deny from all + allow from all still grants access, and staff reply that you shouldn't use both. That's this bug in three lines, seventeen years ago. A 2015 thread about a different access-control bypass got a staff response admitting that matching Apache's exact behaviour in every scenario is hard. The model that fits every observation: LiteSpeed treats each Order/Allow/Deny cluster as the access policy for the context, and the last cluster in the file wins. AIOWPS's Allow from all was the last one.

Two things make this worse than an ordinary incompatibility. LiteSpeed's own security docs recommend the identical SetEnvIf + Order allow,deny + Allow from all + Deny from env= pattern for bot blocking, so it's not a fringe construct. And third-party compatibility tables list Order/Allow/Deny as "Native" without qualification. The syntax is understood. The semantics are not Apache's.

The fix

Don't fight for position in a file that a plugin rewrites. Use the rewrite engine, which runs in an earlier phase than access control and isn't affected by anything a later Allow from all says:

RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^203\.0\.113\.10$
RewriteRule ^ - [F,L]

Behind Cloudflare this relies on LiteSpeed's "Use Client IP in Header" being on for Cloudflare's ranges, otherwise REMOTE_ADDR is a Cloudflare edge and you lock yourself out too. The domlog showing real visitor IPs is your confirmation.

It worked first try on LiteSpeed, and it works identically on Apache. Next time I need to restrict access to a site with 5G rules in its .htaccess, I’ll go straight to rewrites.

For the plugin side: 5G is legacy. AIOWPS moved 6G into its PHP firewall years ago and 6G doesn't touch .htaccess. Turn 5G off. Note that AIOWPS 5.4.1 (May 2025) fixed a bug where the 5G toggle was inverted, enabling it removed the rules and disabling it added them, so a customer who believes 5G is off may still be carrying the block.

What I'd tell past me

  1. On LiteSpeed, the garbage-line test is worthless. Test with a simple redirect and check whether it takes effect before concluding that the file isn’t being read.
  2. Switching the server binary was the right instinct and gave the decisive signal in two minutes. Do it earlier.
  3. Deny from all at the top of a WordPress .htaccess is not a safe assumption on LiteSpeed. Read the whole file, or use a rewrite.
  4. LiteSpeed's DEBUG log lists every .htaccess directive it processes, with line numbers. It's the fastest way to see what the server actually loaded versus what you think you wrote.

The takeaway for anyone running LiteSpeed with WordPress security plugins: every legacy Order/Allow/Deny block in the file is competing for a single slot, and the plugin that wrote last gets it.