Configuring LAMP with vhosts and ACLs

Notice!

The instructions in this article are meant to be used as an example of how to setup a working vhost for a user on a server. Special care must always be taken to ensure server security. Following this article does not guarantee server security.

In this article, we're going to add a new user, configure an Apache virtual host for that user and secure the web files so Apache only has write access to what it needs. We're making the assumption that you've got a fully working LAMP server running.

First we begin by adding a new user. For the sake of this example, I'm using a generic user for a made up person and a simple password. If doing this on a production server, please be sure to use secure passwords and for extra security, random usernames.

 

Let's add our user, John Doe with a username of jdoe32 and a password of p4ssw0rd

useradd jdoe32 -m -s /bin/bash
passwd jdoe32
usermod -aG sudo jdoe32

Now we'll create a document root so John Doe can host his own website.

mkdir /home/jdoe32/public_html
chown jdoe32: /home/jdoe32/public_html
chmod 750 /home/jdoe32/public_html

We need to give the www-data user the ability to read those files.
To do this, we'll use the ACL package, let's install it.

apt-get install acl

Ubuntu 14.04 LTS Info

Ubuntu 14.04 LTS has ACL enabled on ext4 file systems by default.
To confirm this and enable ACLs on your file system, please refer to this article.
https://help.ubuntu.com/community/FilePermissionsACLs

Now that you have ACLs enabled and your filesystem remounted with ACL support, we'll run the following commands to set the ACLs for www-data on joe's public_html folder.

setfacl -R -m u:www-data:rx /home/jdoe32/public_html
setfacl -d -R -m u:www-data:rx /home/jdoe32/public_html

Create a test file, we'll call it /home/jdoe32/public_html/jdoe32.php

nano /home/jdoe32/public_html/jdoe32.php

/home/jdoe32/public_html/jdoe32.php

<?php phpinfo(); ?>

Now let's create a virtual host for joe's domain.

cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/001-jdoe32.conf

We need to make some changes to the config, use your favorite editor and open the file. Make sure the ServerName, ServerAlias and DocumentRoot are set. You also need to add the Directory block. I like to change the logs to keep users separate from the system. You could grant the user access to read those logs too but that's a lesson for a different day.

nano /etc/apache2/sites-available/001-jdoe32.conf

/etc/apache2/sites-available/001-jdoe32.conf

<VirtualHost *:80>
	ServerName jdoe32.com
	ServerAlias www.jdoe32.com

	ServerAdmin me@jdoe32.com
	DocumentRoot /home/jdoe32/public_html

	ErrorLog ${APACHE_LOG_DIR}/jdoe32-error.log
	CustomLog ${APACHE_LOG_DIR}/jdoe32-access.log combined

	<Directory /home/jdoe32/public_html>
		Options Indexes FollowSymLinks
		AllowOverride All
		Require all granted
	</Directory>
</VirtualHost>

Now we need to enable the new site.

a2ensite 001-jdoe32

Test the new config to make sure there weren't any syntax errors

apachectl configtest

And reload the config if everything checks out.

service apache2 graceful

Now if you have your DNS setup, you should be able to view the new site in your browser.

If you see the phpinfo page, it means everything has worked up to this point.
Now it's time to install wordpress.

Let's sudo as the user.

sudo su jdoe32
cd ~

Next we'll download, extract and move the wordpress files and cleanup when we're done.

wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
mv wordpress/* ~/public_html/

rm -rf ~/latest.tar.gz ~/wordpress

We need to create the database that we'll use for wordpress

sudo mysqladmin -u root -p create jdoe32_wordpress

Login to mysql and create a user and grant privileges to the database

sudo mysql -u root -p

In the MySQL Console

MariaDB [(none)]> CREATE USER 'jdoe32'@'localhost' IDENTIFIED BY 'password';
MariaDB [(none)]> GRANT ALL PRIVILEGES ON `jdoe32_wordpress` . * TO  'jdoe32'@'localhost';
### YOU CAN WILDCARD THE GRANTS IF APPLICABLE###
MariaDB [(none)]> GRANT ALL PRIVILEGES ON `jdoe32\_%` . * TO  'jdoe32'@'localhost';
### END OPTIONAL SETUP ###
MariaDB [(none)]> FLUSH PRIVILEGES;

Now visit your site and finish the wordpress installation. You'll be prompted to save the wp-config.php contents, this is 100% intentional. We don't want the webserver to have the ability to modify files except where appropriate. That's the whole purpose of using ACLs instead of chmod and chown.

On that note, let's allow Apache to write to the uploads directory. We'll start by creating it if it doesn't exist.

mkdir ~/public_html/wp-content/uploads

setfacl -m u:www-data:rwX /home/jdoe32/public_html/wp-content/uploads
setfacl -d -m u:www-data:rwX /home/jdoe32/public_html/wp-content/uploads

That's it. You're all set. Going forward, you'll login via SCP / SFTP to upgrade Wordpress, install or upgrade themes and plugins.

If you'd like this to be possible via the admin gui, you can install the ssh2 bindings for php.

apt-get install libssh2-php

Enable the ssh2 extension for php

php5enmod ssh2

service php5-fpm restart

Multi-Tenant Notice

In the real world, it's best to not use the www-data user as your php worker user. You should have a set of users for each tenant or application on the server.

One user to own the web directory (jdoe32 in our example), and another user to run the worker as (could be jdoe32_worker). The worker user has read access to the web directory and ACLs to write to needed locations as we've done above.

Was this answer helpful? 6 Users Found This Useful (50 Votes)