Serve System User Home Directories Over the Web with Apache HTTPD mod_userdir
In a lot of cases (especially universities), there may be a setup where a single server machine has multiple users (teachers and students) who can upload some pages or scripts that they may also want to serve over the web. In such cases there has to be some way to access the content for each user separately. This could happen via:
- Multiple domains (foo.com, bar.com)
- Multiple subdomains (foo.example.com, bar.example.com)
- Multiple relative paths to a domain (example.com/foo, example.com/bar)
The Apache HTTP Web Server (httpd) ships with mod_userdir
module that allows us to do something similar to the last case above, i.e., have multiple relative paths for each user. Using mod_userdir
we can allow access to example.com/~username
(note the presence of ~
in the syntax) where the username will map to the home directory in the filesystem by default, but we can also configure this behaviour.
For those who may not know, a user home directory – /home/foo
, /home/bar
– is automatically created by the operating system when a user is added with commands like adduser
.
mod_userdir allows us to give a website to everyone on a system.
If this is something you’d want to use, then lets go through the steps to enable this feature and see how the configuration works in detail.
Enabling The Module
Enabling the module is easy in most Unix-like or Unix-based systems, be it macOS or FreeBSD or different Linux distributions like Ubuntu, Debian, CentOS, Fedora, etc. Enabling a module that httpd ships by default generally involves the following steps:
- Find the main server config path.
- Uncomment the
LoadModule
directive that loads the module with anyInclude
directive that loads the default configuration for that module. In some OS distributions, there may not be an existing line to uncomment. In such cases the main config file (httpd.conf
orapache2.conf
) will anyway have a comment on how to add aLoadModule
line to the config. Just search for the term “LoadModule” and “Include” in the file and you’ll find the instructions. - For systems that ship with
a2enmod
you can simply doa2enmod modname
instead of following the previous step. In our case that’sa2enmod userdir
Of course you’ll have to restart your web server after making any configuration changes with whichever command works in your case – service apache2 restart
, systemctl restart apache2
, apachectl restart
, httpd restart
.
With the userdir
module in effect now, let’s look at the usage details.
UserDir Directive
If you look at the default mod_userdir
configuration file that ships with Apache and included in the main server config file (httpd.conf
or apache2.conf
), it looks like this:
<IfModule mod_userdir.c>
UserDir public_html
UserDir disabled root
<Directory /home/*/public_html>
AllowOverride FileInfo AuthConfig Limit Indexes
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
Require method GET POST OPTIONS
</Directory>
</IfModule>
Note: If you really can’t find this file in the main server config folder, then just pick up a word from the code above and run a grep
.
Due to this configuration, once the module is enabled, if you actually put some sample HTML files in the public_html
folder of user home directories and try to visit http://IP_ADDR/~user
, then you’ll be able to see the HTML content being served. This is how the file/folder structure should look:
$ tree /home/
/home/
├── bar
│ └── public_html
│ └── index.htm
└── foo
└── public_html
└── index.htm
Apache does this automatic serving for any system user because of the UserDir
directive which is the main configuration directive that we have to care about with relation to the mod_userdir
module.
UserDir
can be placed inside:
- The main server config (like in the code above).
- A virtual host block.
And it can accept three kinds of values:
- Directory path
- Redirect URLs
- Certain keywords –
enabled
anddisabled
Directory Path
Syntax:
UserDir directory-path
For a given request URL – http://example.com/~foo/one/two.html
– let’s look at the different values we can pass to UserDir
and how it’ll map the request URI (/~foo/one/two.html
) to the filesystem to serve content:
# For http://example.com/~foo/one/two.html
UserDir public_html # Will map to /home/foo/public_html/one/two.html
UserDir /usr/web # Will map to /usr/web/foo/one/two.html
UserDir /home/*/www # Will map to /home/foo/www/one/two.html
The examples above covers various possible usages:
- A relative directory path will be relative to the (system) home directory of
~user
from the requested URL. In this casepublic_html
translates to/home/foo/public_html
. That’s whereone/two.html
will be served from. - An absolute directory path can be used to point to a location other than the default home directory. The absolute path is concatenated with the username to form the directory path to serve pages from. In this case it’s
/usr/web/foo
and not/home/foo
. Henceone/two.html
will be served from/usr/web/foo
. - We can use a wildcard (asterisk or
*
) match as well which will be replaced with the~user
from the requested URL. In our case that translates to/home/foo/www/one/two.html
.
Note: Whenever we use an asterisk (*
) in the directive’s value as a directory path or redirect URL (next section), it’ll always get replaced with the username.
Redirect URLs
Syntax:
UserDir redirect-url
We can also pass URLs to UserDir
that will be used to send HTTP 302 redirects to the client. For the same sample URL that we saw in the previous section – http://example.com/~foo/one/two.html
– this is the translation logic stated by the documentation:
Directive Value Translated path
--------------- ---------------
UserDir http://example.com/users http://example.com/users/bob/one/two.html
UserDir http://example.com/*/usr http://www.example.com/bob/usr/one/two.html
UserDir http://www.example.com/~*/ http://www.example.com/~bob/one/two.html
Although when I tested it on Ubuntu and CentOS, the expected behaviour did not happen. The way it actually works is that, you must have a wildcard in the Directive value and the value till that point will be considered. So the URL http://example.com/~foo/one/two.html
will be translated like this:
Directive Value Translated path
--------------- ---------------
UserDir http://example.com/users 403 Forbidden
UserDir http://example.com/*/usr http://example.com/foo/one/two.html (/usr is discarded)
UserDir http://www.example.com/~*/ http://example.com/~foo/one/two.html (as expected)
In the second case you’ll notice that /usr
is discarded in the translation. The URL value is picked till the *
point only. This is what I meant by “value till that point will be considered” above.
Keywords
Syntax:
UserDir keyword
Let’s look at what keywords we can use. The first one is disabled
. This will turn off all username to directory translations. In effect, the entire feature will be disabled.
# Disable all username to directory translation
UserDir disabled
Now if you want, you can enable this feature for specific users with enabled
keyword. We can pass a list of users (space delimited) for the username to directory translation:
# But enable for user foo and bar
UserDir enabled foo bar
Although we first disabled the feature for all users and then enabled for only foo
and bar
, we still need to specify whether the feature will work on translating the request URI into a directory path or a redirect URL. Hence the combined configuration would look something like this:
UserDir disabled
UserDir enabled foo bar
UserDir public_html
We’ve disabled the userdir feature for all users except foo
and bar
and configured a public_html
folder to be used from their home directories to serve content for incoming HTTP requests automatically.
Here’s a detailed version on how the rules affect the configuration:
- The keyword
disabled
used alone will turn off request username to filesystem directory mapping or translations for all users except those that are used withUserDir enabled
in some other line. - The keyword
disabled
can also specify a list of usernames delimited by spaces. In such cases a “hard” disable will be performed where trying to turn on the translation for that user withUserDir enabled
will also not work. - The keyword
enabled
takes one or more usernames to turn on username to directory translation for. They ignore any global disable in effect, i.e.,UserDir disabled
(first point above) but fail to work in case of a local disable in effect, i.e.,UserDir disabled foo
(second point above).
Multiple Directory Paths and URLs
Apache mod_userdir
also allows us to set multiple directory paths and URLs where the successive values act as fallback values.
UserDir public_html /usr/web http://example.com/*/
A request to http://example.com/~foo/one/two.html
will try to find the page at:
/home/foo/public_html/one/two.html
– If not found then next step./usr/web/foo/one/two.html
– If not found then next step.- If the redirect URL has an asterisk then that will be replaced with the username and client will be 302 redirected. In this case the client will be redirected to
http://example.com/foo/
. If the match here fails (no asterisk is found) then next step. - HTTP 404 Not Found will be thrown.
Security Tip
It is highly recommended to always set the following when using mod_userdir
:
UserDir disabled root
Let’s see why. You may want to have the following setting to automatically serve the contents of all the user home directories (/home/foo
, /home/bar
) at example.com/~user
:
# This will translate to /home/user/./
UserDir ./
But this will also open /root
(root user’s home) as a response to example.com/~root
which is not desirable. Hence username to directory translation for root
user must be disabled. Of course the /root
directory will only be served if Apache has the appropriate permissions to access it (+x
).