phpwordpressapache.htaccessmod-rewrite

htaccess block root folder bit allow access to list of specified sub folders


I am trying to create htaccess rules at the root of a client site to block traffic from a specific subfolder where wordpress is installed.

Here is my structure

site.com
|--blog
   |--.htaccess
   |--wp-files
.htaccess
site-root-files

My root htaccess file look like this:

RewriteEngine On

# Define the list of exception folders
RewriteCond %{REQUEST_URI} ^/blog/([^/]+)/
RewriteCond %{REQUEST_URI} !^/blog/(wp-admin|wp-json)/

# List of exception files
RewriteCond %{REQUEST_URI} ^/blog/wp-login\.php$ [NC]
RewriteCond %{REQUEST_URI} !^/blog/wp-login\.php$

# Redirect all other requests under /blog/
RewriteCond %{REQUEST_URI} ^/blog/
RewriteRule ^/blog/ / [L]

Redirect 301 /tmp /
ErrorDocument 404 /error/404.html

Nothing seems to work except the last two lines the tmp redirect and the 404 page.

Wordpress is installed in the blog folder.

Then it dawned on my that Wordpress might have its own htaccess file. It does and it looks like this:


# BEGIN WordPress
# Direktiverne (linjer) mellem 'BEGIN WordPress' og 'END WordPress' er
# dynamisk genereret og bør kun ændres via WordPress-filtre.
# Eventuelle ændringer i direktiverne mellem disse markører vil blive overskrevet.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /blog/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
</IfModule>

# END WordPress

What have I tried?

Commenting out all of the rules in the blog/.htaccess file Inverting the functions in the site root .htaccess file.

Nothing seems to work.

My regex knowledge is pretty basic but I can wrap my head around this. !^/blog/(wp-admin|wp-json)/ looks like not/don't write rewrite rules for blog/wp-admin, or blog/wp-json etc

TL;DR

Block access to

With these exceptions (allow access to)

Can anyone help me?


Solution

  • There are a few issues here...

    1. RewriteRule ^/blog/ / [L]
      The URL-path that is matched by the RewriteRule pattern in a per-directory context (ie. .htaccess) does not start with a slash, so this rule would never match (so does nothing). However, see point #3 below.

    2. RewriteCond %{REQUEST_URI} ^/blog/wp-login\.php$ [NC]  
      RewriteCond %{REQUEST_URI} !^/blog/wp-login\.php$
      

      These two conditions are mutually-exclusive - they cannot both be successful (assuming the request is all lowercase, although what is the thinking behind using the NC flag on the first condition here?), so the rule will fail. However, this condition/regex (^/blog/wp-login\.php$) also conflicts with the first condition (^/blog/([^/]+)/), again they cannot both be successful. And, see point #3 below.

    3. Due to the way mod_rewrite inheritance works (by default), the mod_rewrite directives in the /blog/.htaccess (child) file will completely override the mod_rewrite directives in the parent/root directory. The mod_rewrite directives in the root are not even processed. You can change how the directives are "inherited", but this is going to affect other directives in the root .htaccess file and is generally best avoided in this scenario.

    Note that on Apache, each module behaves independently and has it's own rules with regards to inheritance. mod_rewrite is a notable exception to the norm.

    Aside: However, I doubt that allowing access to only /wp-admin, /wp-json and /wp-login.php in that directory will result in a working system, or is this entirely headless? Don't these pages require additional assets (CSS, JS, images) within the /blog/ directory? You are also blocking access to the main WordPress front-controller (/blog/index.php) - so the WordPress code block in the /blog/.htaccess file is not actually doing anything.

    Solution

    The solution would be to either:

    1. Use mod_authz_core (Require) directive(s) in the root .htaccess file. Together with <If> expressions (requires Apache 2.4).
    2. Or (preferable), move the directives to the /blog/.htaccess file, and adjust the directives accordingly (they can be simplified). Unless there is a requirement for "separation", this does keep the relevant directives together.

    For example, at the top of the /blog/.htaccess file:

    # /blog/.htaccess
    
    # Block access to everything except /wp-admin, /wp-json and /wp-login.php
    RewriteRule !^(wp-admin/|wp-json/|wp-login\.php$) - [F]
    

    UPDATE: Added the ^ prefix to the regex. Without this it would still "work", but potentially match too much.

    Alternatively, this could be written like this if you need to extend it:

    # /blog/.htaccess
    
    # Block access to everything except /wp-admin, /wp-json and /wp-login.php
    RewriteCond $1 !^wp-(admin|json)/
    RewriteCond $1 !^wp-login\.php$
    RewriteRule (.*) - [F]
    

    This sends a 403 Forbidden for any request that is not of the form /wp-admin/<anything>, /wp-json/<anything> or /wp-login.php. Note that the URL-path matched by the RewriteRule pattern is relative to the directory that contains the .htaccess file. So we do not match the /blog subdirectory here.

    No need to repeat the RewriteEngine directive, since that already appears later in the file inside the WordPress code block (which should not be modified manually).

    Alternatively, if you only have access to the root .htaccess file then you could do something like the following instead:

    # /.htaccess
    
    # Block access to the `/blog` subdirectory with some exceptions
    <If "%{REQUEST_URI} =~ 'm#^/blog/#' && %{REQUEST_URI} !~ 'm#!^/[^/]+/(wp-admin/|wp-json/|wp-login\.php$)#">
        Require all denied
    </If>
    

    The above checks that the full requested URL-path matches /blog/<anything> but not the paths/files to be allowed.


    Aside: In addition to the points raised above...

    # Define the list of exception folders
    RewriteCond %{REQUEST_URI} ^/blog/([^/]+)/
    :
    

    The presence of this first condition would potentially allow access to the /blog/ base URL (ie. the WordPress homepage) and any other files directly in the /blog/ subdirectory - is that the intention? Although this conflicts with the natural language description of requirements at the end of your question (and static assets would still be blocked).

    :
    # Redirect all other requests under /blog/
    RewriteCond %{REQUEST_URI} ^/blog/
    

    Given the first condition, this last condition is superfluous, since you have already established that the URL-path must match ^/blog/([^/]+)/. These two conditions are implicitly AND'd. If this condition should apply then the first condition is redundant.

    Note that you should apply the base requirement in the RewriteRule pattern since this is the first directive that is processed to assess whether the rule should be applied (and preceding conditions checked) at all.