Hosting WordPress Yourself Part 3 – Setting Up Sites

In Part 2 of ‘Hosting WordPress Yourself’, I showed you how to install Nginx, PHP-FPM and MariaDB, which formed the foundations of a working web server. In this post I will guide you through the process of setting up individual sites within Nginx (known as server blocks or virtual hosts) and the process of installing WordPress via WP-CLI. At the end of this post I will also demonstrate how to connect to your server using Transmit (file management) and Sequel Pro (database management) for those who prefer to install WordPress manually. Let’s get started.

In order for your server to handle website traffic, you must perform two tasks:

  1. Create a DNS record for the domain name you wish to serve traffic, which points to your server.
  2. Setup a server block so that Nginx knows how to deal with the request. By default, Nginx will drop any connections it receives, as in the previous post you created a catch-all server block. This ensures that the server only handles traffic to domain names that you explicitly define.

DNS A Record

Login to your DNS control panel and add an A record that points to your server’s IP address. You can also add a CNAME record for the www subdomain, so that anyone who visits either domain.com or www.domain.com will be directed to your server.

DNS A Record

DNS changes can take up to 72 hours to propagate, therefore visiting the domain shortly after making any changes will more than likely yield an error or lead to unexpected results. Luckily, you can spoof your DNS to instantly use the new IP address, as detailed in this WPEngine article.

Nginx Server Block

As always, you are going to want to SSH to your server:

ssh ashley@pluto.ashleyrich.com

SSH

Once logged in you should be directed to your home directory, if not, go there now:

cd ~/

For simplicity’s sake, all of the sites that you host are going to be located in your home directory and have the following structure:

Directory Structure

The logs directory is where the Nginx access and error logs are stored, and the public directory is the site’s root directory which will be publicly accessible.

Begin by creating the required directories and setting the correct permissions:

mkdir -p ashleyrich.com/logs ashleyrich.com/public
chmod -R 755 ashleyrich.com

With the directory structure in place it’s time to create the server block in Nginx. Navigate to the sites-available directory:

cd /etc/nginx/sites-available

Create a new file to hold the configuration. Naming this the same as the site’s root directory will make server management much easier when hosting a large number of sites:

sudo nano ashleyrich.com

Copy and paste the following configuration, ensuring that you change the server_name, access_log, error_log and root directives to match your domain and file paths. Hit CTRL X followed by Y to save the changes.

server {
    listen 80;
    listen [::]:80;

    server_name ashleyrich.com www.ashleyrich.com;

    access_log /home/ashley/ashleyrich.com/logs/access.log;
    error_log /home/ashley/ashleyrich.com/logs/error.log;

    root /home/ashley/ashleyrich.com/public/;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$args; 
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

This is a bare-bones server block that informs Nginx to serve the ashleyrich.com domain and www subdomain. It also sets the directory that Nginx should use for the site root and where to store the server log files. The two location blocks essentially tell Nginx to pass any PHP files to PHP-FPM for interpreting.

By default Nginx won’t load this configuration file. If you take a look at the nginx.conf file you created in the previous post, you will see the following lines:

##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

Only files within the sites-enabled directory are automatically loaded. This allows you to easily enable or disable sites by simply adding or removing a symlink.

To enable the newly created site, symlink the file that you just created into the enabled-sites directory:

sudo ln -s /etc/nginx/sites-available/ashleyrich.com /etc/nginx/sites-enabled/ashleyrich.com

In order for the changes to take effect, you must restart Nginx. However, before doing so you should check the configuration for any errors:

sudo nginx -t

If the test failed, recheck the syntax of the new configuration file. If the test passes, restart Nginx:

sudo service nginx restart

Restart Nginx

With Nginx configured to serve the new site, it’s time to create a new database for the WordPress installation.

Creating a Database

When hosting multiple sites on a single server, it’s good practice to create a separate user and database for each individual site. You should also lock down the user privileges so that the user only has access to the databases that they require. Here’s how to do just that.

Log into MariaDB with the root user. Although we’re using MariaDB the commands are exactly the same as if using MySQL, because it’s a drop in replacement. MariaDB and MySQL will be used interchangeably throughout the remainder of this post.

mysql -u root -p

You’ll be prompted to enter the password which you created when setting up MariaDB.

MySQL Command Line

Once logged in, create the new database:

CREATE DATABASE ashleyrich_com CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;

Next, create the new user using the following command, remembering to substitute the username and password for your own values:

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

You then need to add the required privileges. To keep things simple, you can grant all privileges but restrict them to the ashleyrich_com database only, like so:

GRANT ALL PRIVILEGES ON ashleyrich_com.* TO 'username'@'localhost';

Alternatively, you can have more granular control and explicitly define the privileges the user should have:

GRANT SELECT, INSERT, UPDATE, DELETE ON ashleyrich_com.* TO 'username'@'localhost';

Be very careful as not to overly restrict permissions. Some plugins and major WordPress updates require heightened MySQL privileges (CREATE, DROP, ALTER, etc), therefore revoking them could have adverse effects. The WordPress Codex has more information on the subject, which can be found here.

For the changes to take effect you must flush the MySQL privileges table:

FLUSH PRIVILEGES;

Finally, you can exit MySQL:

exit;

Now that you have Nginx configured and a new database table, it’s time to install WordPress, but before doing so you’ll need to install WP-CLI.

Installing WP-CLI

If you have never used WP-CLI before, it’s a command line tool for managing WordPress installations, and greatly simplifies the process of downloading and installing WordPress (plus many other tasks).

Navigate to your home directory:

cd ~/

Using cURL, download WP-CLI:

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

You can then check that it works be issuing:

php wp-cli.phar --info

The command should output information about your current PHP version and a few other details.

In order to access the command line tool by simply typing wp you need to move it into your PATH and ensure that it has execute permissions:

chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

You can now access the WP-CLI tool by typing wp.

WP-CLI Output

Installing WordPress

With everything in place it’s time to install WordPress. Start by navigating to the site’s public directory:

cd ~/ashleyrich.com/public

Then using WP-CLI, download the latest stable version of WordPress into the working directory:

wp core download

You now need to create a wp-config.php file. Luckily, WP-CLI has you covered:

wp core config --dbname=ashleyrich_com --dbuser=username --dbpass=password

Finally, with the wp-config.php file in place, you can install WordPress and setup the admin user in one fell swoop:

wp core install --url=http://ashleyrich.com --title='Ashley Rich' --admin_user=ashley --admin_email=hello@ashleyrich.com --admin_password=password

On success, you should be able to visit the domain name within the browser and be presented with a blank WordPress installation:

Blank WordPress Installation

Adding Additional Sites

Additional sites can be added to your server using the same procedure as above and you should be able to fire up new sites within a couple of minutes. Here’s a quick breakdown of how I personally add them:

  1. Add the relevant DNS records to the domain.
  2. Navigate to your home directory and create the required directory structure for the new site (logs and public).
  3. Navigate to the sites-available directory within Nginx and copy an existing config file for the new server block. Ensure you change the relevant directives.
  4. Symlink the config file to the sites-enabled directory to enable the site and restart Nginx.
  5. Create a new database and MariaDB user using either the command line, or Sequel Pro (see below).
  6. Navigate to the site’s public directory and download, configure and install WordPress using WP-CLI.

It’s as simple as that.

You are free to add as many additional sites to your server as you like, the only limiting factors are available system resources (CPU, RAM, etc) and bandwidth restrictions imposed by your VPS provider. Both of which can be overcome by upgrading your package. Caching will also greatly reduce system resource usage, which is something I will guide you through in the next post.

File and Database Management

File Management Via Transmit

If at any stage you need to upload files to your server, you can easily do so using SFTP. Using an FTP client is often the easiest option. I like to use Transmit.

Create a new connection and select SFTP as the protocol. Enter your server credentials and leave the password field blank as Transmit will automatically use the default key within your .ssh directory. You can click the key icon (next to the password field) to choose a custom key file, if you’re not using the default key.

Transmit Connection Details

Upon saving the settings and connecting to the server, you should be presented with your home directory. From there you can navigate to the site’s root directory.

Transmit File Browser

When uploading files to the server, you will not need to change ownership of them as you have configured Nginx and PHP-FPM to run under your user account. Super easy!

Database Management Via Sequel Pro

Managing MariaDB/MySQL from the command line can be a major pain, therefore it’s often easier to use a GUI. phpMyAdmin and Adminer are common choices, however they often involve setting up separate sites to host them. Instead, I like to use Sequel Pro, which is a native Mac app.

Setup is relatively straightforward, however, because you have configured MySQL to only allow connections from localhost (originating from your server) you need to tunnel the connection through SSH. Luckily, Sequel Pro makes this a breeze.

Create a new connection type of SSH and provide your server and database credentials. It’s advised to use the MariaDB root user so that you can manage all databases and create new users. As with Transmit, you do not need to provide a password as Sequel Pro will automatically use the default key from within your .ssh directory.

Sequel Pro Connection Details

Managing Users and User Privileges

As mentioned earlier, it’s best practice to lock down the privileges for each database user and only give them access to the databases they require. Sequel Pro greatly simplifies this process.

Open the Users panel.

Database View

From this panel you can create new users, change a user’s privileges and reset their password.

Users Panel

Restricting privileges is a trivial process. First remove the global privileges.

Global Privileges

You can then select each individual database that the user needs access to and add only the required schema privileges.

Schema Privileges

That’s all for part 3, if you have any questions please feel free to ask them below. In the next post I will guide you through the process of caching, performance optimization and server monitoring. Stay tuned!

About the Author

Ashley Rich

Ashley is a PHP and JavaScript developer with a fondness for solving complex problems with simple, elegant solutions. He also has a love affair with WordPress and learning new technologies.

  • WebDevDude

    Great article! If anyone is interested, here is a Gist of my WordPress Nginx conf file, that has a little bit more security and options added https://gist.github.com/chrisblackwell/323f7c70e0ced6987b49

  • Paul

    Nice series of articles, I know it must have been a big effort! I did it a bit differently, and would suggest to do the same: keep your site under version control and develop locally, then use a service such as dploy.io to sync the changes to the live site.

    • Hey Paul, you took the words right out of our… ‘post ideas list’!

    • Thanks for the comment Paul. There will be a post in this series on that topic, but I wanted to get the basics out of the way first.

  • Thanks for these posts! Just went through all 3 parts. Really hoping part 4 comes out in the next few days!

  • David Kinsella

    Hi there, loving the tutorials but when I get to adding the wp core config I am getting a error-1045-28000 for any user, but I can use the right details to log into mysql. I have even
    built the server and started again. Any ideas where have I might have gone wrong?

  • I’ve not yet come across a tutorial that has been so simple to follow so thanks for going to the effort to write this.

  • Hi, I’m having a problem with your tutorial, which is great btw, but when I try to add a second website, after I copy the config file and change all the directives, nginx gives an error about the fastcgi_cache zone (WORDPRESS) as a duplicate, if I change that, the next error is about fast_cgi_key, being a duplicate…..should I just remove those lines or I’m doing smth wrong?

    • If you are hosting multiple sites on the server, each key_zone will need to be unique and the fastcgi_cache_key will need moving to the global nginx.conf file.

  • Hi Ashley, I followed your tutorial for setting up the WordPress blogs on a Nginx sever. However, I found that the DigitalOcean tutorial https://www.digitalocean.com/community/tutorials/how-to-configure-single-and-multiple-wordpress-site-settings-with-nginx has a different way of going about the settings as they create a global file, wordpress.conf file, and a multisite.conf file – somethings that you do not include in your settings approach. With just having three WP sites on the server, do you think its okay to not go for those Nginx .conf files. I do like your settings procedure as its simple and understandable. Please let me know. Thanks.

    • Both approaches are acceptable, but it all comes down to how you want to organise your configuration. If you plan on only hosting WordPress sites, you can comfortably leave all of the global configuration in the main nginx.conf file. However, this can cause issues when you want to host other PHP frameworks or custom apps as they probably won’t need the same directives. In this situation it is best to separate the directives into logical files and include them in the individual virtual host files as required.

      In the last post of this series I will expand on the Nginx configuration and show how I organise my configuration files.

      • Thanks a lot Ashley, your answer really helps. I read that the Nginx & php-fpm combination with fastcgi cache configuration is really good. I’ll look forward to your future posts in this series. 🙂

  • Guilherme Souza

    I’m getting a 502 Bad Gateway error, how can I solve this?

  • Hello;

    Can i use this Nginx conf for multiple wordpress sites?

    • Each site should have it’s own entry in the `sites-available` directory and it should be symlinked to the `sites-enabled` directory. There should only be a single nginx.conf file.

      I always copy an existing virtual host and just change the required directives.

  • I keep getting access.log directory error even though the file is there nginx: [emerg] open() “/home/kingxxxxxx/healthablxxxxxxxx/logs/access.log” failed (2: No such file or directory)

    • Does the user that runs Nginx own the file and have write access?

  • chmod -R 755 domain.com returning no directory

  • Popsantiago

    Hi @A5hleyRich:disqus ,
    Why is pluto.ashleyrich.com on beginning and ashleyrich.com in this part ?
    I want the non-www point to the www domain, do i need to change something in the configuration ?
    Thanks for reply.

    • pluto.ashleyrich.com is the server name and ashleyrich.com is the site we’re setting up. You can use the primary domain for your server name too, but I prefer it to be unique.

      • Popsantiago

        Ok ! Thx for reply (And very great tutorial !)

  • NicolasJoly

    Hello,

    Very great tutorial !

    But i have problem when i add plugins.
    Download is ok, bu twhen i activated, i have this error :
    ‘The plugin does not have a valid header’
    The directory don’t have the right permission ?

    Thank you !

  • Arka_Bhowmik

    It a very wonderful tutorial. This is the only tutorial that worked for me on Linode. No other tutorials or pre-existing stackscripts on Linode or Droplets on DigitalOcean. I was successfully able to create my site. However, I have come across a few issues and therefore list the solutions for the same below.

    After setting up the site and logging in to the admin screen, many a times it might be possible that the upload screen / post and page screens are not working. This is mainly because the permissions are not properly set up on your server. The solution is to set proper permissions on the files(644) and directories(755) in the wordpress directory.

    Open SSH and type the following lines:

    sudo find ashleyrich.com -type d -exec chmod 755 {} ;
    sudo find ashleyrich.com -type f -exec chmod 644 {} ;

    This might create a warning that the access.log, error.log and .htaccess files cannot be changed permissions. This is also what we require because we do not want to allow changes to this files by mistake.

    The above lines will probably allow you to update new images by the media uploader. However, it might be possible that the uploaded images even if uploaded, is not visible after upload and the “error” is shown. This can be solved by resolving ownership of the wordpress directory and the wp-content/uploads directory.

    Type the following lines to change ownership to your username(in this case ashley) and the group to “www-data”.

    sudo chown ashley:www-data ashleyrich.com/public
    sudo chown :www-data ashleyrich.com/public/wp-content/public

    If you still have issues, make sure you clear your browser history and cache and try again.

  • Dennis

    Hi there, nice guides, really enjoy them!

    However I got some issues:

    1. whenever i’m trying to run “wp core download” – I’m getting some errors about permissions, however in the end there is a success message.

    2. ‘wp core config –dbname=ashleyrich_com –dbuser=username –dbpass=password’ – with my own details, it states that user doesn’t have permission, however there is no issue to connect to mysql with this user/pass

    3. I’ve managed to install WP through browser, however I was also pushed to create wp-config file manually with ‘sudo nano wp-config.php’

    I guess there is something strange going on with my permissions, however I have no idea why X_x.

    P.S. The only difference from your configuration I have is that I’m setting up everything for IP right now, and I haven’t changed www-data to my user, that’s it…
    P.P.S. most of the commands require sudo to go through ;(

  • Angel Cardenas

    I need to word without a domain until the website is finished… is possible to finish the web and at the end add the domain?

  • Filip Kuzmanovski

    I want to set up an additional site, but my client needs me to work without a domain to keep the domain I’ll be using later live. How I can go ahead and do this when setting up an additional site

    • If you need the site be accessible by yourself and the client, the only real way is to use a subdomain of an existing domain.

      If you only need access yourself you can just spoof your DNS entry by adding the server IP and domain to your `/etc/hosts` file. You would then setup the virtual host as normal in Nginx.

      • Filip Kuzmanovski

        Thank you

      • Filip Kuzmanovski

        I followed steps 1-5 to set up additional sites, however my new site which sits on the same droplet keeps redirecting to the parent site that I initially set up when I first ran through the tutorial. Any idea what is causing this, and what steps should I take to reconfigure it.

  • JP

    First of all, I would like to thank you Ashley for a great gudie.

    I got an issue I can’t fix and wonder if you or anybody else here could help me with that.

    I’ve installed all the things that are needed down to mysql. For php management I’ve installed PhPmyAdmin via the url you provided in the comment section on “part 2” of the guide.

    I started the server and could connect to the welcome screen (nginx) via my domain name.
    I configured the “Catch All Server Block” so it would allow my domain jpbernhardt.com

    When I try to access http://jpbernhardt.com/phpmyadmin i get the “404 Not Found” error and the 403 Forbidden when I go to jpbernhardt.com

    Not sure where the problem is located.

    JP

  • Reine

    Thank you so much for these series! I especially appreciated (up to now): easy securing of server, WP CLI!

  • For anyone going through this for PHP7, the Nginx configuration need to be slightly modified to something like :
    location ~ .php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+.php)(/.+)$;
    fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    fastcgi_index index.php;
    include fastcgi.conf;
    }

    Also others add access log off such as in this tutorial http://atulhost.com/install-wordpress-nginx-php7-phpmyadmin. What you think @A5hleyRich:disqus ? 🙂

  • For PHP 7 I had to do

    `sudo apt-get install php-mysql`

  • hkpinguin

    great posts, but I have a question..I added a second site to the server but I am not able to configure a second ssl cert, can letsencrypt manage multiple certs for one IP?

    • Yes, you can have multiple certificates per IP address.

  • At the database creation time, I think it would be worth updating the code to specify utf8mb4. So instead of this line:

    CREATE DATABASE ashleyrich_com;

    It would be:

    CREATE DATABASE ashleyrich_com CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;

    References
    http://dba.stackexchange.com/questions/76788/create-a-database-with-charset-utf-8
    And http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci/766996#766996

    • Good point! Post updated, cheers!

      • Amazing! Also, I forgot to say how much I appreciate this series of articles – thank you!

        (WP Migrate DB Pro is amazing as well)

  • Mike

    I was able to get my first site completely up and running and secure. I then tried to go back and add a second site, but the domain keeps giving me information from my first site… Do I need to use different port numbers for the second site (which doesn’t make much sense being port 80 and 443 are standard ports). Not sure how to alleviate the problem.

  • nilblank

    Great series of articles. Very thorough in practice. There are a few areas where it might be helpful to understand why you have made certain setup choices rather than others. For example, why is the WordPress site installed under the sudo user’s folder (~/ashleyrich.com/public) rather than the typical default location (/var/www/ashleyrich.com)?

    • It’s doesn’t really matter where they’re stored, but I use my home directory for convenience. When I login I can just `ls` to view all sites or `cd` into one.

      • nilblank

        Thanks for the clarification. I see your reasoning. Would it make sense to create symbolic links between /var/www/ and the home folder projects?

        • If you want to store them in `/var/www` then it’s a good idea.

  • Worth adding a rule to disable XMLRPC as standard?

    # http://wordpress.stackexchange.com/questions/219643/best-way-to-eliminate-xmlrpc-php#answer-219666
    # nginx block xmlrpc.php requests
    location /xmlrpc.php {
    deny all;
    }

  • George Brown

    I need to finish my WordPress site before pointing DNS to it… is possible to add the domain at the end?

  • bubienok

    in the post ,,Adding Additional Sites , what to do with letsencrypt certificates (how to add) ?