Setting up per-user web directories

Apache HTTP Server allows each user to have a web site in their home directory.
This per-user web directories feature is provided by the module mod_userdir.somod_userdir.so is compiled by default on all Apache HTTP Server installations, unless we have disabled it using the configure option --disable-userdir or --enable-mods-shared=few.

 

I am working as a standard user named user1. Suppose I want to website user1.example.com which is hosted in my home directory.

WARNING: Make sure DNS or /etc/hosts entries are properly configured, so that the domain name user1.example.com resolves to IP address 192.168.0.100.

Setting up per-user web directories involves the following steps.
1) Configuring the server
2) Creating a DocumentRoot for the user’s website
3) Setting up Virtualhost for the user’s website

 

 

1) Configuring the server
Open Apache HTTP Server‘s main configuration file httpd.conf.
Look for the line containing mod_userdir.so. Uncomment the line. Now it must look as shown below.

LoadModule userdir_module modules/mod_userdir.so


Look for the line containing httpd-userdir.conf. Uncomment the line. Now it must look as shown below.

Include conf/extra/httpd-userdir.conf

Save and close the file.


Open Apache HTTP Server‘s per-user configuration file httpd-userdir.conf file.
Look for the line containing UserDir public_html directive. Above this line, add the following content.
NOTE: Replace user1 with your username.

UserDir disabled
UserDir enabled user1

The first line disables per-user directory feature for all users, while the second line explicitly allows this feature for the standard user user1. Save and close the file.


Finally, make sure you have configured a Virtualhost for the main website example.com, as explained in the first step Setting up Virtualhost for the main website.


Restart the apache service gracefully.

# service apache graceful

 

 

2) Creating a DocumentRoot for the user’s website
Let us create a public_html directory in the home directory of standard user. This directory will serve as the user’s DocumentRoot. In the terminal, execute the below command as standard user.

$ mkdir $HOME/public_html


Now there is a problem. Apache HTTP Server does not have access to the public_html directory. Let us add the group apache as a supplementary group to the standard user’s group and set proper permissions for the directories.

Add the group apache as a supplementary group to the standard user’s group.
NOTE: Replace GROUPNAME with the group of your standard user.

# usermod -G GROUPNAME apache
OPTIONS EXPLAINED

-G
A list of supplementary groups which the user is also a member of. Each group is separated from the next by a comma, with no intervening whitespace. The groups are subject to the same restrictions as the group given with the -g option.

NOTE: Adding a secondary group will only take effect after we logout and login again. So make sure you do that and come back right here.


You must have logged out and logged in again.
The default permission mode for a user’s home directory is 700. Let us change the permission mode of standard user’s home directory to 750. In the terminal, execute the below command as standard user.

$ chmod 750 $HOME


The default permission mode for a directory owned by a user is 775. Let us change the permission mode of user’s public_html directory and any contents inside this directory to 750. In the terminal, execute the below command as standard user.

$ chmod -R 750 $HOME/public_html
OPTIONS EXPLAINED

-R
operate on files and directories recursively



But there comes an issue. If we create a file/directory inside public_html directory as standard user, it will have permission mode 664/775 respectively. This is different from the permission mode 750, which we had intended. This occurs because POSIX permissions are not inherited by default.
A way to inherit POSIX permissions is to set up ACL. ACLs work on filesystem level. So ACL must be enabled at the time of mounting. Filesystems that are created/configured while the operating system was installed comes with the ACL option enabled within the filesystem superblock.

But if your home directory resides in a filesystem that was created after the operating system was installed, you may have to manually enable ACL.
To check if you have ACL enabled on the filesystem, execute the below command in terminal.
NOTE: Replace DEVICEPATH with the path to your filesystem device.

# tune2fs -l DEVICEPATH | grep acl
OPTIONS EXPLAINED

-l
List the contents of the filesystem superblock, including the current values of the parameters that can be set via this program.

If ACL is enabled, Default mount options in the output, will have acl listed.


To enable ACL on the filesystem, follow any of the below steps.
Enabling ACL in superblock
   OR
Enabling ACL in /etc/fstab

Enabling ACL in superblock
To enable ACL in the superblock of filesystem, execute the below command in terminal.
NOTE: Replace DEVICEPATH with the path to your filesystem device.

# tune2fs -o acl DEVICEPATH
OPTIONS EXPLAINED

-o
Set or clear the indicated default mount options in the filesystem. Default mount options can be overridden by mount options specified either in /etc/fstab(5) or on the command line arguments to mount(8). Older kernels may not support this feature; in particular, kernels which predate 2.4.20 will almost certainly ignore the default mount options field in the superblock. More than one mount option can be cleared or set by separating features with commas. Mount options prefixed with a caret character (’^’) will be cleared in the filesystem’s superblock; mount options without a prefix character or prefixed with a plus character (’+’) will be added to the filesystem.

The following mount options can be set or cleared using tune2fs:

debug - Enable debugging code for this filesystem.

bsdgroups - Emulate BSD behaviour when creating new files: they will take the group-id of the directory in which they were created. The standard System V behaviour is the default, where newly created files take on the fsgid of the current process, unless the directory has the setgid bit set, in which case it takes the gid from the parent directory, and also gets the setgid bit set if it is a directory itself.

user_xattr - Enable user-specified extended attributes.

acl - Enable Posix Access Control Lists.

uid16 - Disables 32-bit UIDs and GIDs. This is for interoperability with older kernels which only store and expect 16-bit values.

journal_data - When the filesystem is mounted with journalling enabled, all data (not just metadata) is committed into the journal prior to being written into the main filesystem.

journal_data_ordered - When the filesystem is mounted with journalling enabled, all data is forced directly out to the main file system prior to its metadata being committed to the journal.

journal_data_writeback - When the filesystem is mounted with journalling enabled, data may be written into the main filesystem after its metadata has been committed to the journal. This may increase throughput, however, it may allow old data to appear in files after a crash and journal recovery.

nobarrier - The file system will be mounted with barrier operations in the journal disabled. (This option is currently only supported by the ext4 file system driver in 2.6.35+ kernels.)

block_validity - The file system will be mounted with the block_validity option enabled, which causes extra checks to be performed after reading or writing from the file system. This prevents corrupted metadata blocks from causing file system damage by overwriting parts of the inode table or block group descriptors. This comes at the cost of increased memory and CPU overhead, so it is enabled only for debugging purposes. (This option is currently only supported by the ext4 file system driver in 2.6.35+ kernels.)

discard - The file system will be mouinted with the discard mount option. This will cause the file system driver to attempt to use the trim/discard feature of some storage devices (such as SSD’s and thin-provisioned drives available in some enterprise storage arrays) to inform the storage device that blocks belonging to deleted files can be reused for other purposes. (This option is currently only supported by the ext4 file system driver in 2.6.35+ kernels.)

nodelalloc - The file system will be mounted with the nodelalloc mount option. This will disable the delayed allocation feature. (This option is currently only supported by the ext4 file system driver in 2.6.35+ kernels.


Enabling ACL in /etc/fstab
To enable ACL in /etc/fstab, follow the below steps.
Open the file /etc/fstab in a text editor. Look for the Options field. For me, the value in the Options field was defaults. Add acl separated by a comma.  Now it must look as shown below.

defaults,acl

Save and close the file.


If you have completed any of the above steps, remount the intended filesystem.
NOTE: Replace DEVICEPATH with the path to your filesystem device.

# mount -o remount DEVICEPATH
OPTIONS EXPLAINED

-o
Options are specified with a -o flag followed by a comma separated string of options. For example: mount LABEL=mydisk -o noatime,nouser. For more details, see FILESYSTEM INDEPENDENT MOUNT OPTIONS and FILESYSTEM SPECIFIC MOUNT OPTIONS sections.


Now we have a filesystem with ACL enabled. Let us set proper permissions for public_html directory.
Set the default ACL permissions for public_html directory. Execute the below command in terminal as standard user.

$ setfacl -R -m d:u::rwx,d:g::r-x,d:o:--- $HOME/public_html
OPTIONS EXPLAINED

-R
Apply operations to all files and directories recursively. This option cannot be mixed with ‘--restore’.

-m
Do recalculate the effective rights mask, even if an ACL mask entry was explicitly given. (See the -n option.)

[d[efault]:] [u[ser]:]uid [:perms]
Default permissions of a named user. Permissions of the file owner if uid is empty.

[d[efault]:] g[roup]:gid [:perms]
Default permissions of a named group. Permissions of the owning group if gid is empty.

[d[efault]:] o[ther][:] [:perms]
Default permissions of others.

This will result in the files/directories created inside public_html directory to inherit these default ACL permissions.


Set access ACL permissions for user1, on the public_html directory. Execute the below command in terminal as standard user.
NOTE: Replace USERNAME with the name of your standard user.

$ setfacl -R -m u:USERNAME:rwx,g:USERNAME:r-x,o:--- $HOME/public_html
OPTIONS EXPLAINED

-R
Apply operations to all files and directories recursively. This option cannot be mixed with ‘--restore’.

-m
Do recalculate the effective rights mask, even if an ACL mask entry was explicitly given. (See the -n option.)

[u[ser]:]uid [:perms]
Permissions of a named user. Permissions of the file owner if uid is empty.

g[roup]:gid [:perms]
Permissions of a named group. Permissions of the owning group if gid is empty.

o[ther][:] [:perms]
Permissions of others.


Let us again create a file/directory inside public_html directory as standard user. We will notice that the permission mode will be 640/750 respectively. Despite setting ACL with the intention to inherit executable permission, the files did not inherit them.
This is because, files are not created with an executable permission unless a program like gcc or cp(with appropriate option) wants them to. Files are always created with a permission mode 0666, even if the user has UMASK set to 0000. It is the POSIX semantics. If we want any files to be executable, we will have to manually set the executable permission.
NOTE:
• The default ACL permission we have set refers to the maximum permissions a file/directory can have. Means, a file/directory is allowed to have less permissions than specified in ACL.
• Here UMASK is not even considered because ACL is present.

 

Now, let us see how this setup works in a practical situation.

INITIAL CONDITIONS for public_html directory:
• Ownership   user1:user1    ; SGID set
• Permission 750                  ; ACL set
apache‘s primary group apache is added as a supplementary group to the standard user’s group user1.

TEST1: user1 creates a file/directory inside the public_html directory.
• Yes, able to create
• Resultant Ownership   user1:user1
• Resultant Permission 750/640
user1 created the new file/directory. So user1 can also modify them. Modified file/directory will have same ownerships. apache will also be able to modify the newly created file/directory. That will result in a new file/directory ownership apache:user1. So we will try that on TEST2.

TEST2: apache creates a new file/directory inside the public_html directory.
• No, not able to create.

So, if the standard user creates or modifies a file/directory, apache will not have write access for that file/directory. Write access to a file/directory is essential if we have PHP scripts that create, upload or modify files.
What if we give the apache user exclusively write access to public_html directory?
The result will be, the owner of created or modified file/directory will be set to apache, and standard user will not have write access to that file/directory. So this is not a preferable solution.

As another try, we can set SGID on public_html directory, and newly created files/directories will inherit standard user’s group. But this leads back to the condition in TEST2.

So it occurs we have to be more generous with permissions. But permission mode 750 is considered optimal for a public_html directory. Giving Apache HTTP Server write acccess to public_html directory was already insecure. We will not be giving away more permissions.
We can tackle this problem using suEXEC or suPHP or PHP-FPM. How to do this, I will explain sometime later. See the todo list.

 

 

3) Setting up Virtualhost for the user’s website
As the standard user, create a new file index.html inside the public_html directory, using the below content.

<html>
<h1> Hello </h1>
<body> This is my personal webpage </body>
</html>

Open the web browser and type in the URL http://example.com/~user1/. This URL is called the Temporary URL. We must view our personal webpage now.


Still, we can access the website only via the temporary URL. If we want to access the website via our intended URL user1.example.com, proper VirtualHost configurations should be made.

Open Apache HTTP Server‘s Virtualhost configuration file httpd-vhosts.conf.
Let us create a Virtualhost for our personal website user1.example.com. Add the following content to the end of file.
NOTE:
• Replace 192.168.0.100 with IP address of your machine.
• Replace user1.example.com with required domain name.

<VirtualHost 192.168.0.100:80>
ServerAdmin root@localhost
DocumentRoot "/home/user1/public_html"
ServerName user1.example.com
ServerAlias www.user1.example.com
ErrorLog "logs/user1.example.com_error_log"
CustomLog "logs/user1.example.com_access_log" common
<Directory "/home/user1/public_html">
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>

Save and close the file.

 

If we want to enable SSL support for user1.example.com, we must follow the procedure explained at Enabling SSL support. After that we must tweak the SSL Virtualhost configuration for main website user1.example.com, as explained in the first step Setting up Virtualhost for the main website.
If all this is done, we can proceed with the below steps.

Open Apache HTTP Server‘s SSL configuration file httpd-ssl.conf.
Add the following content to the end of file.
NOTE:
• Replace 192.168.0.100 with IP address of your machine.
• Replace user1.example.com with hostname of your machine.
• The directive ServerAlias does not work with SSL configuration in Apache HTTP Server. Because of this, we have added a separate VirtualHost www.user1.example.com.

<VirtualHost 192.168.0.100:443>

DocumentRoot "/home/user1/public_html"
ServerName user1.example.com
ServerAdmin root@localhost
ErrorLog "/usr/local/apache2/logs/user1.example.com_error_log"
TransferLog "/usr/local/apache2/logs/user1.example.com_access_log"

<Directory "/home/user1/public_html">
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>

SSLEngine on

SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

<FilesMatch ".(shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>

BrowserMatch "MSIE [2-5]" 
nokeepalive ssl-unclean-shutdown 
downgrade-1.0 force-response-1.0

CustomLog "/usr/local/apache2/logs/ssl_user1.example.com_request_log" 
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x "%r" %b"

</VirtualHost>

<VirtualHost 192.168.0.100:443>

DocumentRoot "/home/user1/public_html"
ServerName www.user1.example.com
ServerAdmin root@localhost
ErrorLog "/usr/local/apache2/logs/user1.example.com_error_log"
TransferLog "/usr/local/apache2/logs/user1.example.com_access_log"

<Directory "/home/user1/public_html">
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>

SSLEngine on

SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

<FilesMatch ".(shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>

BrowserMatch "MSIE [2-5]" 
nokeepalive ssl-unclean-shutdown 
downgrade-1.0 force-response-1.0

CustomLog "/usr/local/apache2/logs/ssl_user1.example.com_request_log" 
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x "%r" %b"

</VirtualHost>

Save and close the file.


Restart the apache service gracefully.

# service apache graceful


Now, we can goto the below URLs and check if everything is working fine.

http://example.com/~user1

http://user1.example.com

http://www.user1.example.com

https://example.com/~user1

https://user1.example.com

https://www.user1.example.com

 

 

 

You may go back to the following section.
Customizing Apache HTTP Server