When configuring access control in Apache HTTPD (version 2.2.3 in this case), a common requirement is to:
- Block all access by default
- Allow access from a specific subnet
- Then block specific IPs within that allowed subnet
The typical configuration attempt looks like this:
<Location /server-status>
SetHandler server-status
Order Allow,Deny
Deny from all
Deny from 192.168.16.100
Allow from 192.168.16.0/24
</Location>
This fails because of Apache's evaluation order with Order Allow,Deny
:
- First ALL Allow directives are evaluated
- Then ALL Deny directives are evaluated
- The last matching rule wins
Solution 1: Using Require in Apache 2.4+
For modern Apache installations (2.4+), this becomes much cleaner:
<Location /server-status>
SetHandler server-status
Require all denied
Require ip 192.168.16.0/24
Require not ip 192.168.16.100
</Location>
Solution 2: The Correct 2.2.x Approach
For Apache 2.2.x (like the OP's version), you need to use:
<Location /server-status>
SetHandler server-status
Order Deny,Allow
Deny from all
Deny from 192.168.16.100
Allow from 192.168.16.0/24
</Location>
Key differences from the failed attempt:
- Changed
Order Allow,Deny
toOrder Deny,Allow
- Evaluation now happens in reverse order
- The last matching rule (Allow from subnet) wins for most IPs
- But the explicit Deny for 192.168.16.100 takes precedence
Here's what happens with the working configuration:
1. First pass (Deny rules): - 192.168.16.100: matches explicit Deny - Other 192.168.16.x: don't match explicit Deny - All other IPs: match "Deny from all" 2. Second pass (Allow rules): - 192.168.16.x (except .100): match Allow - 192.168.16.100: already denied in first pass - All others: remain denied
To verify your access controls:
# Test from allowed IP (replace with your actual IP)
curl -I http://yourserver/server-status
# Test from denied IP (requires access to that machine)
ssh user@192.168.16.100 "curl -I http://yourserver/server-status"
# Check Apache error logs if unexpected results
tail -f /var/log/httpd/error_log
For complex scenarios, you can use mod_rewrite:
<Location /server-status>
SetHandler server-status
RewriteEngine On
RewriteCond %{REMOTE_ADDR} =192.168.16.100
RewriteRule ^ - [F]
Order Allow,Deny
Allow from 192.168.16.0/24
Deny from all
</Location>
When working with Apache's mod_authz_host
, a common security requirement is to allow an entire subnet while blocking specific IPs within that range. The default behavior of Order Allow,Deny
can sometimes produce counterintuitive results.
The critical misunderstanding lies in how Apache processes the directives:
Order Allow,Deny
Deny from all
Deny from 192.168.16.100 # Seems logical but doesn't work as expected
Allow from 192.168.16.0/24
Apache evaluates these rules in sequence:
- Processes ALL
Allow
directives first (192.168.16.0/24 matches) - Then processes
Deny
directives (192.168.16.100 also matches) - For IPs matching both, the last matching directive controls the outcome
For Apache 2.4+ (recommended approach):
<Location /server-status>
SetHandler server-status
Require all denied
Require ip 192.168.16.0/24
Require not ip 192.168.16.100
</Location>
For systems running older Apache versions, use this pattern:
<Location /server-status>
SetHandler server-status
Order Deny,Allow
Deny from all
Allow from 192.168.16.0/24
Deny from 192.168.16.100
</Location>
Key differences from your initial attempt:
- Changed
Order
toDeny,Allow
- Maintained the same directives but in different order
- The evaluation sequence now properly denies the specific IP
After making changes:
sudo apachectl configtest
sudo systemctl restart apache2
Verify with:
curl -I http://yourserver/server-status
For directory-level restrictions:
# .htaccess file contents
<Files "server-status">
Order Deny,Allow
Deny from all
Allow from 192.168.16.0/24
Deny from 192.168.16.100
</Files>
Note that IP-based restrictions:
- Add minimal overhead for small rulesets
- Should be combined with other auth methods for sensitive data
- Work best when placed in main config rather than .htaccess
- Mixing
Order Allow,Deny
andOrder Deny,Allow
syntax - Forgetting to restart Apache after config changes
- Using outdated directives in Apache 2.4+ environments
- Not testing from both allowed and denied IPs