(Linux Apache MySQL PHP)

Introduction

My name is Paul Suarez and I am a software developer. I develop enterprise software professionally and enjoy working on smaller personal apps during my downtime. I’ve typically deployed some of my apps to services like Heroku and Firebase, and have appreciated a lot of the conveniences that come with cloud services offered by AWS and Google, etc. However, those options were getting expensive and I found myself constantly curious about the layers of abstraction those services simplified away. What’s behind the curtain? So I decided to do a bit of research and try to just deploy my code to my own server. This is a very novice, rudimentary look at that process.

Why, What

In this article, I’ll walk through the process of building an environment for us to deploy our apps. I’ll even deploy an app or two! For this example, I’ll use WordPress both because of its popularity (maybe that’s what you’re here for) and also because it’s a quick way to verify success. You could easily deploy your own apps or even just HTML documents on each virtual host to verify this as well (maybe one virtual host directs to site1.html and another could direct to site2.html).

I started creating this walkthrough mostly for my own reference later, but then it occurred to me that others may have struggled as well and might find this useful. I spent three and a half evenings trying to figure this stuff out because I’m terrible at googling. So if you’re better at googling, and you found this article, well then hopefully I can save you a couple of days. This is by no means the only way, or the easiest way, or even the right way to set up multiple virtual hosts with an Apache server, but it is the way that I figured it out, even if the server man-handled me throughout the process. All that to say, I’m looking for some constructive feedback so that I might improve this walkthrough. If you could leave a comment correcting or clarifying any of this process, I’d greatly appreciate it.

LAMP

We’ll be setting up a LAMP (Linux Apache MySQL PHP) stack on a fresh Ubuntu (18.04) server. The idea is to create multiple virtual hosts on one single dedicated server, or even Virtual Private Server (VPS), in this case. Of course, we will need root access and the ability to install serverside code so typical shared-hosting plans will not work.

Linux

The first acronym in LAMP is Linux. This should have been set up by the hosting company. I use, and highly recommend IONOS because they’re extremely reliable and CHEAP. They have Virtual Private Servers (which is what I use) for as low as $2/month! However, if you plan to host multiple webapps/projects, just get the $10/month option. Heres a referral link, not exactly sure what I get for the referral but here it is:

https://www.ionos.com/pro/vps?ac=OM.US.USf11K357093T7073a&kwk=621310372

Scroll down and select the VPS M package, unless you think you can work with less or need more. They have lots of options for operating systems. I’d argue Ubuntu 18.04 is the best for the job and that’s what I’ll be using.

Nice, our blank server is set up. If you’re on a mac or running Linux, you can access SSH via your terminal. However, if you’re on Windows I believe you’ll have to download a client like Putty.

In the SSH client/terminal, log in with the credentials that IONOS provides. In the IONOS pannel, the “Servers and Cloud” option will show the server details. The username will likely be “root@YOUR.SERVER.IP.ADDRESS” and the password will be hidden, but accessible in the server details. Ok, enough hand-holding; let’s get to the Apache installation.

Apache

First, check for updates:

apt-get update
apt-get upgrade

Ok, install Apache via the terminal:

apt-get install apache2

Fire it up:

systemctl start apache2

And enable it to go live with:

systemctl enable apache2

Let’s check the status:

systemctl status apache2

You should see something that looks like this:

Active

Now in the web browser, type YOUR.SERVER.IP.ADDRESS in the address bar and the default Apache page should look something like this:

Apache

Good job. LA of LAMP is done. Next up is MySQL, which I’ve grown quite fond of over this exercise. No, that’s not sarcasm. PHPMyAdmin was frustrating me so I found myself just doing it the old fashion way most of the time, but more on that later.

MySQL

Install MySQL with:

apt-get install mysql-server

Next, I’ll walk through a few ways of securing the MySQL installation. I’ll also cover other security topics later as well.

mysql_secure_installation

This will ask a series of question seen below. I answered yes to all of them. Take a look so you know what you’re getting yourself into:

Securing the MySQL server deployment.

Connecting to MySQL using a blank password.

VALIDATE PASSWORD PLUGIN can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD plugin?

Press y|Y for Yes, any other key for No: y

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters, and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
Please set the password for root here.

New password: 

Re-enter new password: 

Estimated strength of the password: 100 
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done!

Finally, add MySQL to auto start with the server as we did with Apache:

systemctl start mysql
systemctl enable mysql

Looking good so far. Now time for PHP.

PHP

I had a bit of trouble with this but I finally figured out a solution. I found the installation to be a bit finicky so hopefully, the route I go here will help with whatever version you’re installing. A quick google search lead me to think it would be as easy as:

apt-get install php7.0 libapache2-mod-php7.0 php7.0-mysql php7.0-curl php7.0-mbstring php7.0-gd php7.0-xml php7.0-xmlrpc php7.0-intl php7.0-soap php7.0-zip

But nope, must be a version issue because I only got “Unable to locate package” and “Couldn’t find any package” so I found a resource to aid the PHP installation by deb.sury.org

add-apt-repository ppa:ondrej/php

This will spit out the following information:

Co-installable PHP versions: PHP 5.6, PHP 7.x and most requested extensions are included. Only Supported Versions of PHP (http://php.net/supported-versions.php) for Supported Ubuntu Releases (https://wiki.ubuntu.com/Releases) are provided. Don't ask for end-of-life PHP versions or Ubuntu release, they won't be provided.

Debian oldstable and stable packages are provided as well: https://deb.sury.org/#debian-dpa

You can get more information about the packages at https://deb.sury.org

BUGS&FEATURES: This PPA now has an issue tracker:
https://deb.sury.org/#bug-reporting

Caveats: 
1. If you are using php-gearman, you need to add ppa:ondrej/pkg-gearman
2. If you are using apache2, you are advised to add ppa:ondrej/apache2
3. If you are using nginx, you are advise to add ppa:ondrej/nginx-mainline
   or ppa:ondrej/nginx

PLEASE READ: If you like my work and want to give me a little motivation, please consider donating regularly: https://donate.sury.org/

WARNING: add-apt-repository is broken with non-UTF-8 locales, see 
https://github.com/oerdnj/deb.sury.org/issues/56 for workaround:

# LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
 More info: https://launchpad.net/~ondrej/+archive/ubuntu/php
Press [ENTER] to continue or Ctrl-c to cancel adding it.

Hit Enter and move along

Now I could finally install PHP with:

apt-get install php7.3 libapache2-mod-php7.3 php7.3-mysql php7.3-curl php7.3-mbstring php7.3-gd php7.3-xml php7.3-xmlrpc php7.3-intl php7.3-soap php7.3-zip

The installation can be verified with a quick PHP file:

nano /var/www/html/info.php

This will bring you into a terminal based editor where you can paste this:

<?php
phpinfo();
?>

Hit CONTROL X, then Y, and ENTER to save and finally restart apache with:

systemctl restart apache2

Now to confirm PHP installed correctly, go to YOUR.SERVER.IP.ADDRESS/info.php and it should look something like this:

php

Done! The LAMP server is ready to host some apps.

Extras

Before we move on, I’ll suggest a couple of optional steps. Install PHPMyAdmin and consider some security upgrades. PHPMyAdmin offers some handy tools. Mainly, a GUI application for your MySQL databases. Before we get started, lets hit the server with another:

apt-get update && apt-get upgrade

Now install PHPMyAdmin:

apt-get install phpmyadmin

This will take you through a vibrant, flashback-inducing wizard. It’s important to make your selections with SPACE and then hit ENTER. Do not just hit ENTER. Hit SPACE first to make the selection.

phpmyadmin

Select apache2 with SPACE, but I didn’t have to tell you that, you’re smart. Now hit ENTER to go to the next screen. It’ll look like this:

phpmyadmin

Don’t be too worried about this screen. I spent too much time researching the woes of either option. This is why I can’t enjoy Chose Your Own Adventures (copyright) or RPG’s because I obsess about the “what if.” Just hit ENTER for yes and move on with your life.

Set your password and that should conclude the setup for phpMyAdmin. Now go to YOUR.SERVER.IP.ADDRESS/phpmyadmin. Log in with the MySQL username and password you want for whatever database. Up until now, we haven’t made a database (well, potentially, there are some by default, but who cares) yet, but we will soon. Apparently, you can access all of your databases with the username root and the password that was created in the wizard but I can’t get my permissions right and I haven’t cared enough yet to figure it out. Leave a comment if you understand this better than I do.

Ok, before I get into the virtual hosts and WordPress and databases and all that cool stuff, let’s take a quick minute to consider the security of our server. There’s no better place to start than activating automatic, unattended updates. Yes, there are concerns like anything, but I believe you’re at a larger risk getting behind in your updates, so update frequently. If you’re the type to wait around for a confirmed, safe update than go for it, but I’m not.

apt-get install unattended-upgrades

I highly recommend protecting yourself from brute-force attacks with fail2ban. Yes, their website is terrible but their service is amazing.

apt-get install fail2ban

You can edit your fail2ban jail file with a quick nano, but I believe it’s unnecessary unless you want to increase or decrease the number of attempts, etc. Otherwise, just enable it as is:

systemctl enable fail2ban.service

Restart Apache:

systemctl restart apache2.service

There’s a lot of other things we could do like restrict file upload size, disable certain PHP functions, block remote access, etc, but some of that is overkill and can hinder the rest of our development. I recommend researching that further once you have all of your websites deployed and report back to me later so that I can learn too. Moving on.

Connect Databases

Now the fun part. Psych, we’ve been having fun all along. Time to create a few websites that will run independently of each other, but all within this LAMP server. Like I said at the beginning, we could deploy anything to our virtual hosts. I’m choosing to deploy WordPress in this demo, but you can push or install whatever you want. I plan to follow this up with an article on deploying a custom app to one of these virtual hosts soon.

Let’s jump into our webapps. I like to start by creating the databases for each website. It doesn’t have to be in this order, but I’ll start with this for now.

Type mysql and enter some SQL to create the databases and users. Of course, add in your own database name, username, and password. Here’s what that might look like:

CREATE DATABASE site1databasename;
CREATE USER site1username@localhost IDENTIFIED BY 'site1password'; 
GRANT ALL PRIVILEGES ON site1databasename.* TO site1username@localhost IDENTIFIED BY 'site1password';
FLUSH PRIVILEGES;
quit

We can do this as many times as we want, or until we run out of server resources. Let’s add another database for a second website. In your terminal, type msql again and build your additional database(s):

CREATE DATABASE site2databasename;
CREATE USER site2username@localhost IDENTIFIED BY 'site2password'; 
GRANT ALL PRIVILEGES ON site2databasename.* TO site2username@localhost IDENTIFIED BY 'site2password';
FLUSH PRIVILEGES;
quit

Now I’ll create the website directories. I’ll download and then copy WordPress into these in a bit. I found some resources say to make the website directories in /var/www/html and other resources saying to make them in /var/www, on the same sibling level as the HTML directory. Honestly not sure which is right or wrong so I decided to put my directories at /var/www. Each website will get its own directory as a sibling to the HTML directory. I would love to hear your inputs on this hierarchy decision. Navigate to the directory:

cd /var/www

And create a directory for each website:

mkdir site1
mkdir site2

Now I’ll download WordPress (again, and I know I’m really overemphasizing this, but we could choose Drupal or Joomla or our own custom apps along with some other languages besides PHP here). I recommend navigating back to the root directory.

cd /root

Some resources might recommend installing WordPress into a temp directory, but I’m not going to do that.

wget http://wordpress.org/latest.tar.gz
tar xzvf latest.tar.gz

This will get the current version of WordPress downloaded to my local server so now I can just copy it into each website directory easily when I’m ready. But first, I need to make a couple of adjustments to WordPress.

cd wordpress

Type ls to list everything in the WordPress directory.

If you only see the wp-config-sample.php and NO wp-config.php, then we need to create one with:

touch wp-config.php

Copy the contents from the sample PHP file into our newly created PHP file:

cp wp-config-sample.php wp-config.php

This will copy everything from the sample config file into the new wp-config we just created.

cd .. to go back up one directory to where we were in root.

ls again to confirm the WordPress directory is visible.

Then copy the WordPress to each website directory we created earlier:

cp -avr wordpress/* /var/www/site1/
cp -avr wordpress/* /var/www/site2/

Ok, now it’s time to connect each installation to its corresponding database we created above.

chown -R www-data:www-data /var/www/site1/
cd /var/www/site1
nano wp-config.php

Enter the below into the config file of each website. Of course, enter YOUR database information:

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'site1databasename');
/** MySQL database username */
define('DB_USER', 'site1username');
/** MySQL database password */
define('DB_PASSWORD', 'site1password');

/**
* WordPress Database Table prefix. 
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*/
$table_prefix  = 's1_';

It’s recommended to change the prefix from wp_. Choose whatever, but I recommend the same format, two letters followed by an underscore.

I need to do this for each website so that they’re each tied to their own databases.

chown -R www-data:www-data /var/www/site2/
cd /var/www/site2
nano wp-config.php
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'site2databasename');
/** MySQL database username */
define('DB_USER', 'site2username');
/** MySQL database password */
define('DB_PASSWORD', 'site2password');

/**
* WordPress Database Table prefix. 
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*/
$table_prefix  = 's2_';

Ok, great. I created my databases, installed/deployed my app (WordPress), and connected each website directory to its own database. Now I need to tell Apache how to access them independently.

Virtual Hosts

This is how to configure the Apache virtual host for each site.

Navigate to:

cd /etc/apache2/sites-available

Hit ls to see what’s there

Copy the default.conf file for this website and we’ll do it again in a bit for the other website(s).

cp 000-default.conf site1.conf

Now edit it with:

nano site1.conf

And enter the following:

<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/site1
  ServerName site1.com
  ServerAlias www.site1.com
    <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
  <Directory /var/www/site1>
    Options FollowSymLinks
    AllowOverride All
  </Directory>
 
  ErrorLog ${APACHE_LOG_DIR}/site1_error.log
  LogLevel warn
  CustomLog ${APACHE_LOG_DIR}/site1.com_access.log combined
</VirtualHost> 

Ok now configure the next website:

cp 000-default.conf site2.conf

Now edit it with:

nano site2.conf 
<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/site2
  ServerName site2.com
  ServerAlias www.site2.com
    <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
  <Directory /var/www/site2>
    Options FollowSymLinks
    AllowOverride All
  </Directory>
 
  ErrorLog ${APACHE_LOG_DIR}/site2_error.log
  LogLevel warn
  CustomLog ${APACHE_LOG_DIR}/site2.com_access.log combined
</VirtualHost> 

Now enable both of those virtual hosts:

root@server:~# a2ensite site1.conf
root@server:~# a2ensite site2.conf

And disable the default conf file:

a2dissite 000-default.conf

Honestly, not fully sure what this next part does but some article recommended adding your websites at the bottom of this file. Let me know if you decide to skip this step or find out what it does. The only reason I’m including it here is because I saw it recommended enough times… I know, if everyone jumped off a bridge, would I? Apparently.

root@server:~# sudo nano /etc/host
158.99.999.99    www.site1.com 
158.99.999.99    www.site2.com

Ok, reload apache again and we’re done.

systemctl reload apache2

Domains

So that’s pretty much it for the server. In fact, we may even be able to access the rest of the WordPress installations at YOUR.SERVER.IP.ADDRESS/wp-admin, but don’t do that until you have routed all of your domains. So if you haven’t done that, go to where you registered your domains and point them to YOUR.SERVER.IP.ADDRESS.  Apache will handle the routing of each domain pointed to it based on the virtual hosts we set up.

I recommend making sure all of your domains work with and without the www prefix. Mine were set up to work without, but not with, which made the next step a pain. So I fixed this by going to my DNS panel where my domain was registered. In this case, it was google domains. Under registered hosts, I threw in:

www.site1.com123.456.789.000

I also added a CNAME for my domain as well:

wwwCNAME1hsite1.com.

Ok, THE MOMENT OF TRUTH! Go to any of the domains pointed at YOUR.SERVER.IP.ADDRESS. Example:

site1.com

site2.com

Lastly, walk through the WordPress installation for each domain. It’s fairly simple so I won’t cover that here.

Security and Search Engine Optimization  

So we’re not done yet. I can’t in good conscience leave you with a couple of unsecured HTTP websites. I’ll grab some SLL certifications and turn these WordPress sites into some HTTPS, SEO safe havens. Some webhosts offer one-click LetsEncrypt installations. However, most do not have such conveniences, but not to worry because we have root access!

Certbot is insanely easy. You just pick your stack and OS and boom. If you’ve followed along, I’ve saved you a couple clicks:

https://certbot.eff.org/lets-encrypt/ubuntubionic-apache

apt-get update
apt-get install software-properties-common
add-apt-repository universe
add-apt-repository ppa:certbot/certbot
apt-get update
apt-get install certbot python-certbot-apache
certbot --apache

This will walk you through a couple of questions. It’s pretty straight forward. To finish this process you could manually edit your wp-config file, but honestly, Really Simple SSL makes it too easy. It’s a WordPress plugin. Install it, and activate that SSL certificate we got from Certbot. Install this plugin in each WordPress site. Now your sites are served over HTTPS.

Conclusion

Well, that about wraps up this walkthrough. I really hope this helps you all shed hours off of your development. I spent too long trying to figure this out, but I’m pretty happy with the result. I’m happy knowing I can repeat the above steps for each independent site I plan to launch in the future and now you can too!

Next, I’m hoping to dive into some of the best WordPress plugins including a deep dive through BuddyPress I also would like to deploy one of my custom webapps to this stack or do something similar with Java and Spring. Wouldn’t that be fun? A Linux, Spring, Postgres or Mongo Java stack? We’ll see.

Anyhow, thank you for reading and again, please comment with your best practices when deploying apps to your server.

My main blog is at PaulsBlogging.com and I’m currently restoring my old posts. Stay tuned.

Leave a comment

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