Apache HTTPD: How to Allow Subnet Access While Denying Specific IP in .htaccess


1 views

When configuring access control in Apache HTTPD (version 2.2.3 in this case), a common requirement is to:

  1. Block all access by default
  2. Allow access from a specific subnet
  3. 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:

  1. First ALL Allow directives are evaluated
  2. Then ALL Deny directives are evaluated
  3. 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 to Order 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:

  1. Processes ALL Allow directives first (192.168.16.0/24 matches)
  2. Then processes Deny directives (192.168.16.100 also matches)
  3. 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 to Deny,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 and Order 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