Debugging “403 Forbidden” Error Accessing Files on Apache 2

Recently I was trying to play around with some WordPress plugins, that brought my attention to the console and discovered there have been JavaScript errors for my site:

GET https://aresou.net/wp-includes/js/dist/vendor/lodash.min.js?ver=4.17.19 net::ERR_ABORTED 403 (Forbidden)

Looking at the site errors logged by Apache, it shows:

[Thu Jan 13 01:50:48.844763 2022] [access_compat:error] [pid 18887] [client 50.47.132.16:60932] AH01797: client denied by server configuration: /var/www/html/aresou.net/public_html/wp-includes/js/dist/vendor/lodash.min.js, referer: https://aresou.net/wp-admin/index.php

Weird enough, this error only happens on admin pages, not on the front pages or posts. The first thing I did is to check the folder and file permissions within the WordPress installation. Apache might be unable to serve the static file because www-data account cannot read that file or folder.

Nothing suspicious after checking that. I even set folders to 755 and files to 644 with chmod. Seems like the problem isn’t in the file system. Having done a bit more research, I realized that the next possible cause is the Apache site configurations.

I have three sites on my host, serving two blogs (one of them is aresou.net, aka this site you’re reading) and one personal wiki. The wiki shares the same root domain with this blog. First I reviewed the <Directory> sections of the configs for the two sites with their own domains. Everything looks good, there was no permission restricted from either root or sub folders. Apache should not be blocking access to these folders.

As the wiki site is sharing the same root domain with this blog, I did not think of checking the config. Well, this is the worst assumption I had in the debugging. With more research, I found a line somewhere saying “Apache will combine directives from all enabled site configurations when loading”. To be precise, let me quote from Apache official documentation:

Directives in the configuration files may apply to the entire server, or they may be restricted to apply only to particular directories, files, hosts, or URLs. This document describes how to use configuration section containers or .htaccess files to change the scope of other configuration directives.

https://httpd.apache.org/docs/2.4/sections.html

Which means, any directives from any site config file will be applied to the whole server, if it is not restricted to a particular scope. I immediately went back and checked the config for my wiki site. There were a few lines blocking access for some folders, but not everywhere:

...
Require all denied
...
Order allow,deny
Deny from all
Satisfy All

They are for protecting data folders in DokuWiki, specifically for Apache older than version 2.4. The config is copied directly from their official documentation. Then I noticed another few lines:

<LocationMatch "(data|conf|bin|inc|vendor)/">
    Order allow,deny
    Deny from all
    Satisfy All
</LocationMatch>

The “vendor” regex keyword immediately caught my eyes. Recall that the forbidden JavaScript file is placed under somewhat “/vendor/” folder. Also, this <LocationMatch> section lays at the top level without scope restriction, meaning it will be applied by Apache to the whole server, even though it is only configured within one site.

It’s easy to fix the problem if knowing the root cause. By adding a prefix in the match regex, the forbidden error in WordPress went away and the admin page is again working like a charm.

<LocationMatch "/wiki/(data|conf|bin|inc|vendor)/">
    Order allow,deny
    Deny from all
    Satisfy All
</LocationMatch>

It is also possible to fix this by adding new directives introduced from Apache 2.4, without using <LocationMatch>:

<Directory /usr/share/dokuwiki/data>
        Require all denied
        ## FOR VER APACHE2.4

        ## Add other directories one by one
</Directory>

Hope this article can save someone a bit of time. It took me almost 2 hours to figure out what was going on. Have a good day!

Leave a Reply

Your email address will not be published. Required fields are marked *