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
site.com/blog
With these exceptions (allow access to)
site.com/blog/wp-admin
site.com/blog/wp-json
site.com/blog/wp-login.php
Can anyone help me?
There are a few issues here...
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.
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.
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.
The solution would be to either:
Require
) directive(s) in the root .htaccess
file. Together with <If>
expressions (requires Apache 2.4)./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.