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/bar – is automatically created by the operating system when a user is added with commands like
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
LoadModuledirective that loads the module with any
Includedirective 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 (
apache2.conf) will anyway have a comment on how to add a
LoadModuleline 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
a2enmodyou can simply do
a2enmod modnameinstead of following the previous step. In our case that’s
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,
userdir module in effect now, let’s look at the usage details.
If you look at the default
mod_userdir configuration file that ships with Apache and included in the main server config file (
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
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
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 –
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
~userfrom the requested URL. In this case
/home/foo/public_html. That’s where
one/two.htmlwill 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
one/two.htmlwill be served from
- We can use a wildcard (asterisk or
*) match as well which will be replaced with the
~userfrom the requested URL. In our case that translates to
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.
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.
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
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
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
disabledused alone will turn off request username to filesystem directory mapping or translations for all users except those that are used with
UserDir enabledin some other line.
- The keyword
disabledcan 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 with
UserDir enabledwill also not work.
- The keyword
enabledtakes 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
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.
It is highly recommended to always set the following when using
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 (
# 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 (