In the following lines, I’ll try to provide a concise guide on how to manually set up a server for hosting a website based on WordPress.
This guide covers setting up the OS (Ubuntu), web server (nginx), PHP, database (MariaDB), HTTPS (with Let’s Encrypt), WordPress and some useful security and general stuff.

1. Hosting

A popular option these days is to use a VPS. And it’s a good one – you get full control of a server, dedicated hardware resources and mostly easy way to scale if your website grows.
Hetzner Cloud and DigitalOcean are reliable hosting providers and offer good value for money. DigitalOcean has servers in few parts of the world and Hetzner only in the EU, but Hetzner offers more resources/performance for less money. Choose wisely 🙂
If you are running a small blog, 2GB of RAM will be enough for a start.

2. OS and basic setup

Ubuntu 18.04 is a good choice for an OS. When creating a server instance you will most probably be able to select a ready-made OS image through your provider’s UI.

When you are there, choose to use an SSH key for authentication. You can generate a key (if you don’t already have it) with the following command:
ssh-keygen -t rsa -b 2048
Upload file ~/.ssh/id_rsa.pub or copy it’s contents when required.

Connect to your server:
ssh root@server_ip

Setup firewall

On Ubuntu, UFW firewall can be used to allow connections only to certain services.

  1. Allow only SSH connections:
    ufw allow OpenSSH
  2. Enable the firewall:
    ufw enable
  3. Check the status:
    ufw status

Create a new user account

It’s a good practice not to use a root user to login to the server, so we’ll create a new user to use from now on.

  1. Create a new user:
    adduser emily
  2. Use a strong password!
  3. Add the user to sudo group:
    usermod -aG sudo emily
  4. Copy root account’s SSH key to a new user account:
    rsync --archive --chown=emily:emily ~/.ssh /home/emily
  5. Try to login as emily (in a new terminal session):
    ssh emily@server_ip
  6. If this is successful, you can log out as root.
  7. Now, as Emily, if you want to run any command with superuser privileges, you can do:
    sudo any_command.

Secure the SSH server

To protect from bots, it’s a good practice to disable password authentication and also logins as a root user.

  1. Edit file /etc/ssh/sshd_config and change PermitRootLogin to no and PasswordAuthentication to no.
  2. Restart SSH server:
    sudo service ssh restart

To prevent intrusion, fail2ban is a great tool that monitors SSH log files, detects failed login patterns and blocks access to IP addresses.

  1. Install:
    sudo apt install fail2ban
  2. Add following lines to file /etc/fail2ban/jail.local and then restart fail2ban service:
    sudo service fail2ban restart.
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3

Update Ubuntu packages

From time to time it’s good to check for and install updated packages in order to get security updates and new versions.

  1. sudo apt update
  2. sudo apt upgrade -y

Set UTC timezone and time synchronization

  1. Set UTC timezone:
    sudo timedatectl set-timezone UTC
  2. Use date command to verify the date.
  3. Turn off timesyncd:
    sudo timedatectl set-ntp no
  4. Use timedatectl command to verify that timesyncd is not active.
  5. Install NTP:
    sudo apt install ntp
  6. Check if ntpd is started:
    ntpq -p

3. Web server

NGINX is a free and open source web server known for great performance and stability.
Since the default Ubuntu package repository most probably does not contain the latest version of nginx, we’re going to use the official nginx repository with stable versions.

  1. Set up a repository:
    echo "deb http://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
  2. Import an official nginx signing key:
    curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
  3. Install nginx:
    sudo apt update
    sudo apt install nginx
  4. You can check nginx version with:
    nginx -v
  5. We need to allow HTTP traffic (on port 80) on the firewall:
    sudo ufw allow 'Nginx HTTP'
  6. You can now test if the server is running by accessing your server’s IP address or domain name in your web browser.

By default, nginx is serving files for default website from /var/www/html directory and using the website configuration file /etc/nginx/sites-available/default.

Website and nginx configuration

Create a directory for your’s website files:
mkdir -p /var/www/myblog

Set ownership and permissions:
sudo chown -R www-data:www-data /var/www/myblog
sudo chmod -R 755 /var/www/myblog

Create file /var/www/myblog/index.html with the following content:

<html>
<body>
    <h1>Hi!</h1>
</body>
</html>

Now let’s create a configuration file for your website /etc/nginx/conf.d/myblog.conf:

server {
    root /var/www/myblog;
    
    index index.html;

    server_name myblog.com www.myblog.com;

    location / {
        try_files $uri $uri/ =404;
    }

    include custom/general.conf;
    include custom/security.conf;
}

You no longer need the default website, so you can delete it:
rm -rf /var/www/html /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

I created a GitHub Gist with usable examples of main nginx configuration file nginx.conf and additional configuration files for setting up caching, compression, security and other options.
You can download and use those files as a good starting point, but I encourage you to learn more about configuring nginx and customize those files to your needs.

cd /tmp
curl -Lo nginx-config.zip https://gist.github.com/joshefin/03cb788e2a564fd0b8eab1db4bcfda4a/download
unzip -j nginx-config.zip -d nginx-config

sudo mkdir /etc/nginx/custom

sudo cp /tmp/nginx-config/cache.conf /etc/nginx/custom/
sudo cp /tmp/nginx-config/compression.conf /etc/nginx/custom/
sudo cp /tmp/nginx-config/general.conf /etc/nginx/custom/
sudo cp /tmp/nginx-config/security.conf /etc/nginx/custom/
sudo cp /tmp/nginx-config/ssl.conf /etc/nginx/custom/
sudo cp /tmp/nginx-config/mime.types /etc/nginx/
sudo cp /tmp/nginx-config/nginx.conf /etc/nginx/

rm -rf nginx-config nginx-config.zip

Always test configuration files using the following command:
sudo nginx -t
Then restart nginx to load modified configuration files:
sudo service nginx restart.

You can again test if the server is running properly by entering your domain name in the web browser.

Good places to learn about nginx configuration are the H5BP server-configs-nginx repository, nginx config generator and the official documentation.

4. PHP

WordPress is written in PHP. PHP-FPM is a component commonly used with web servers to serve applications that use PHP.
In order to get the newest PHP version, which is recommended by WordPress team, we need to use a custom package repository.

  1. Add repository with latest stable PHP versions:
    sudo add-apt-repository ppa:ondrej/php
  2. Install PHP-FPM:
    sudo apt update

    sudo apt install php7.3-fpm
  3. To configure nginx to use PHP-FPM for PHP processing add/modify the following lines in file /etc/nginx/conf.d/myblog.conf:
index index.php;

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

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
}

Now create file /var/www/myblog/index.php with the following content:

<html>
<body>
    <?php echo '<h1>Hi!</h1>'; ?> 
</body>
</html>

Test nginx configuration and restart nginx:
sudo nginx -t
sudo service nginx restart.

Access your server’s IP address or domain name in your web browser to check if server is running and PHP working.

If you stumble upon a Permission Denied error from nginx, add nginx user to the www-data group with sudo usermod -aG www-data nginx.

5. Database

MariaDB is a popular database server, made as a free and open source fork of MySQL.

Install MariaDB

To get the latest version of MariaDB we’ll use a MariaDB repository. Go to https://downloads.mariadb.org/mariadb/repositories/ and select your OS and latest stable version of MariaDB to get a repository configuration. Execute given commands to install MariaDB. They will look like this:

sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://mirror.mva-n.net/mariadb/repo/10.3/ubuntu bionic main'

sudo apt update
sudo apt install mariadb-server

Once MariaDB is installed, run mysql_secure_installation command to secure MariaDB for production use and answer everything with Y.

If you would like to tune MariaDB to get better performance, check out MySQLTuner tool and a good example of MariaDB/MySQL configuration file.

PHP module for MySQL

To allow PHP to communicate with the MariaDB/MySQL database, install the php-mysql module:
sudo apt install php7.3-mysql

6. HTTPS

Let’s Encrypt provides free certificates to enable HTTPS (SSL/TLS) for websites. They provide a tool called Certbot to obtain a certificate, securely configure it for use, and automatically take care of renewal.

  1. Set up a repository for Certbot:
    sudo add-apt-repository ppa:certbot/certbot
  2. Install Certbot for nginx:
    sudo apt install python-certbot-nginx
  3. Allow both HTTP and HTTPS traffic on the firewall:
    sudo ufw allow 'Nginx Full'
  4. If it is enabled, delete the firewall rule for only HTTP:
    sudo ufw delete allow 'Nginx HTTP'
  5. Run Certbot to obtain a certificate and automatically configure nginx:
    sudo certbot --nginx -d myblog.com -d www.myblog.com
  6. This will modify nginx configuration file for your website with proper setup for SSL.

7. WordPress

Prepare a database

First, create a database to be used by WordPress:
create database wordpress default character set utf8mb4 collate utf8mb4_general_ci;
Then create a user (with username wp and password password) and grant all privileges for the wordpress database. Use a strong password and remember it.
grant all on wordpress.* to 'wp'@'localhost' identified by 'yourstrongpassword';
flush privileges;

Download WordPress

  1. Download the latest WordPress version:
    cd /tmp

    curl -LO https://wordpress.org/latest.tar.gz
  2. Unpack the archive:
    tar xfvz latest.tar.gz
  3. Use the sample config file as the base:
    cp wordpress/wp-config-sample.php wordpress/wp-config.php
  4. Copy all of the files and directories to the website root directory:
    sudo cp -a /tmp/wordpress/. /var/www/myblog/
  5. Setup proper ownership so nginx can serve and modify the files:
    sudo chown -R www-data:www-data /var/www/myblog
  6. Delete the leftovers:
    rm -rf /tmp/wordpress/ /tmp/latest.tar.gz /var/www/myblog/index.html

Configure WordPress

  1. Generate secret keys:
    curl -s https://api.wordpress.org/secret-key/1.1/salt/
  2. Use generated keys to replace default keys in file /var/www/myblog/wp-config.php
  3. Also in the wp-config.php file change the following settings:
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wp' );
define( 'DB_PASSWORD', 'yourstrongpassword' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', 'utf8_unicode_ci' );
define( 'FS_METHOD', 'direct' );

In your browser, go to your server’s address or domain and complete the installation.

Recommended PHP modules

WordPress will work without any additional modules/extensions, but some will improve WordPress performance.
Install the Health Check plugin and then go to WP Admin > Tools > Site Health and check for issues and recommendations and install the required modules/extensions.
For example:
sudo apt install php7.3-bcmath php7.3-curl php-imagick php7.3-gd
After this you’ll need to restart PHP-FPM:
sudo service php7.3-fpm restart

Block attacks on a login page

The internet is a crazy place 🤪 You may think that no one knows about your small new website, but there is always someone using tools like masscan, scanning the internet for open ports and trying to break into WordPress websites using popular username/password combinations. You don’t have to believe me, check your /var/log/nginx/access.log file.
One way to protect from this is to rate limit WordPress logins.

Create a file /etc/nginx/custom/rate-limit.conf with the following lines:

limit_req_zone $binary_remote_addr zone=wplogin:10m rate=1r/s;
limit_req_status 444;

This defines a memory zone wplogin to keep states using IP address as a key and limits rate to up to 1 request per second. Status code 444 will be returned in response to rejected requests.

Modify the /etc/nginx/nginx.conf file and add the following line before the last include statement:

include custom/rate-limit.conf;

Then modify the nginx config file for your website /etc/nginx/conf.d/myblog.conf to include the following lines, to limit access to wp-login.php page:

location ~ \.php$ {
    location ~ \wp-login.php$ {
        limit_req zone=wplogin burst=1 nodelay;
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
    }

    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
}

Instead of tailing and grepping the access.log file – GoAccess is a free open source terminal-based web log analyzer and viewer. It can analyze nginx access log files and display useful and pretty statistics.

8. Next steps

Now it’s up to you to make your website so popular that you would need a caching solution 😉

Let me know in the comments below if you have suggestions on how to improve this guide.

Leave a Reply

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