Complete guide to hosting a secure WordPress site, powered by fast nginx web server with Web Application Firewall and brotli compression, on modern, secured PHP 7.2.

There is a large volume of security-related WordPress plugins, but server-side security is even more important. If for any reasons you don’t want to use shared hosting for your WordPress needs and rather like to self-host it in a secure manner on a VPS or dedicated server – this howto is for you. If you are building your own PHP application or planning to use another CMS, you should also find most of this tutorial useful.

Web Application Firewall (WAF) and secured PHP environment are a good start to host a secure WordPress site in 2018. Brotli compression is a nice addition, allowing you to speed up the transfer of your text files by up to 20% in comparison to gzip compression. This means lower transfers and faster loading times. This howto covers installing full web server and PHP language, free Let’s Encrypt SSL certificate, WordPress and also some WordPress – specific security tips. Upon finishing, you should have a secure WordPress site with a green padlock visible in your browser.

Configuration below is intended for sites that will handle modern and a little dated browsers, but not as old as Android 2.x browser, or Internet Explorer on Windows XP. Firefox on Windows XP should work fine.

This tutorial is designed for someone with basic UNIX administration knowledge and will require you to have a root account access on a FreeBSD operating system.

FreeBSD’s default shell – csh – is used here, so there is no need to install bash. If you do use bash, you will need to customize some commands (used in database configuration section). vi is used as a text editor, but FreeBSD comes with a more user-friendly editor, too – Easy Editor (ee). If you are not familiar with vi, simply replace “vi” in commands with “ee”, so “vi /path/file” becomes “ee /path/file”.

About formatting:

this is how a shell command looks like in this post and this is some output

and these are contents of a file

Table of contents

Read this:

Why this and not that?

What will you achieve

What will you need

A few words on versions and updating

Do this:

Update packages and install PHP

Add user that will run PHP processes

Configure PHP

Download required software

Build nginx with brotli and ModSecurity

Install and configure MariaDB database

SSL/TLS and Let’s Encrypt certificate

Configure nginx

WordPress installation and configuration

Clean up

Test your site

Additional WordPress security tips

Why this and not that?

Why FreeBSD and will this work on Linux?

I most often am choosing FreeBSD for my webhosting needs as I treasure its minimalism and ease of use. If you prefer Linux or other UNIX-like operating system however, this howto will not reflect the differences and you are required to customize compilation options and files’ locations to adequate to your OS and environment.

Why nginx and not Apache HTTP Server?

I prefer nginx to Apache HTTP Server simply because it is fast as hell (for example doesn’t search each and every parent directory for .htaccess file) and can be easily used as a load balancer and proxy. We will however be installing Apache HTTP Server for a moment, as its libraries are needed to compile nginx with ModSecurity.

Brotli compression

When modern browsers such as Chrome or Firefox send a request to a HTTPS-enabled website, they include “Accept-Encoding” header with values “gzip, deflate, br” – meaning that they will accept an answer (web page, file) compressed with either gzip, deflate or brotli algorithm. Brotli-enabled nginx will choose gzip over deflate and brotli over gzip. Just like Deflate and gzip, brotli is a lossless algorithm.

You can test Facebook using cURL and you will notice different “Content-Encoding” headers showing you which compression server chose. Your nginx will behave in the same manner.

# this part is not required - this is just an example # you can install curl by: pkg install curl # as we will be installing it later anyway curl --silent -I -H'Accept-Encoding: deflate' https://www.facebook.com/ | grep -i ^content-encoding curl --silent -I -H'Accept-Encoding: gzip, deflate' https://www.facebook.com/ | grep -i ^content-encoding content-encoding: gzip curl --silent -I -H'Accept-Encoding: gzip, deflate, br' https://www.facebook.com/ | grep -i ^content-encoding content-encoding: br

Brotli compression does not improve security, but it does performance. Files sent to brotli-enabled browsers are smaller thus will comsume less bandwidth and will be transferred faster.

You can read more about brotli on Wikipedia.

What will you achieve

Upon finishing, you should have a clean, secure WordPress installation with very good server-side security, green padlock in all modern browsers and A+ score on Qualys SSL Labs test. Web Application Firewall (mod_security) will give you additional web server protection against some common attacks (SQL injection, data leakages, application attacks, protocol attacks, etc) but you should be aware that there are also paid mod_security rules available, and these free ones will not protect you against all new attacks. They are however a decent protection against some WordPress themes and plugins bugs.



Your web server will also be sending text files compressed with brotli instead of gzip or deflate, resulting in faster loading of text files (html, css, js).



WordPress set up with this tutorial, on a t2.small Amazon Web Services EC2 instance:

What will you need

Basically you will need a server that will host the website, with FreeBSD already installed, and a domain name or hostname assigned to the public IP address of this server.

Domain

WordPress can be installed as a domain.com/directory/, not as a domain.com/, but this configuration howto assumes that you will be hosting your secure WordPress in the root directory of the domain (domain.com) or a hostname (blog.domain.com). All commands and config entries containing the string yourdomain.com should be changed to the name of your real domain or host name.

Server or VPS

Hardware required to run your secure WordPress site depends mostly on the traffic and plugins used: the more simultaneous users on the site and heavier plugins – the stronger server or VPS you will need.

You can grab a cheap and decent VPS at DigitalOcean. You will get $10 credit with this link. This translates to one free month of VPS with 1vCPU, 2GB RAM, 50GB SSD and 2TB transfer. Full disclosure: this is a referral link.

We will be caching PHP scripts in memory with OPCache, which greatly improves performance, we will be running PHP processes at all times (in comparison to spawning them only when someone enters the site, like on shared hosting) and these things have their toll on the RAM usage. We will also be running MariaDB database, nginx and PHP on the same machine. MariaDB is a MySQL database fork, and as you probably know, MySQL requires some RAM to operate.

I would like to say that a minimal recommended configuration for this setup is something like 1vCPU and 2GB of RAM, but as stated: the more visitors – the stronger hardware will be required. Also if your site will be content heavy – more RAM will be needed to cache whatever possible in the memory. I recommend a dedicated server or VPS with at least 2 vCPUs and 4GB RAM for smooth operation.

A few words on versions and updating

nginx

I’m using nginx version 1.14 here, as it is the most recent one at the moment of writing this post (post updated on 17 Apr 2018). You should head to nginx downloads page and also grab the most recent version. There are no big backward compatibility problems with nginx so it’s safe to say that the config below should work in a year or so.

What’s important is that this howto covers compiling nginx from source, so it will not be automatically updated by packages or ports. Updating nginx (so downloading the source and compiling it) will be your duty. It is however safe to say that nginx is a very well-written software and obsessive updating to each new version is not necessary.

Be aware that if you install nginx from ports or packages, it will lack brotli and mod_security support and will not start with the below configuration.

PHP

WordPress engine is known to always work on the most recent, public PHP versions but it’s themes and plugins are a different animal. If you really, really need to use some outdated plugins, you might need to use older PHP – like 5.6 – but it is not advised. Just a quick reminder: 7.1 and 7.2 are the only actively developed, public PHP versions at the moment, while versions 5.6 and 7.0 are only receiving security patches. PHP 7 is also faster than 5.6.

You are advised to always upgrade PHP to the newest version in your branch.

WordPress

Always use the latest version of WordPress. Update it ASAP.

WordPress’ site always has link to the latest version, so you will not have to look for the current one.

FreeBSD

Please refer to The FreeBSD Handbook for information on updating FreeBSD. You should always keep your operating system updated.

I have used FreeBSD 11 for this tutorial but it should also work on FreeBSD 10 and FreeBSD 12.

Let’s do it!

Escallate privileges

FreeBSD by default comes without sudo – you are supposed to su to other user to issue commands as him. Building software itself does not require root privileges, but editing root-owned config files, start scripts, installing packages – does, so we’ll just escallate our privileges at the start and leave it this way.

su Password: id uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)

Update packages and install PHP

We’ll install PHP from packages, as we will not be doing any changes to it’s source. First, let’s update packages repository:

pkg update Updating FreeBSD repository catalogue... FreeBSD repository is up to date. All repositories are up to date.

If you encounter problems at this point, check your /usr/local/etc/pkg.conf file and check out the manpage for pkg.conf(5) and the Handbook section on packages.

You can use pkg install -y if it’s a fresh system, but I’m not including “-y” in in commands below as it may mess up currently installed software and it’s better that you see what’s going to be upgraded, deleted and installed.

Various plugins might require you to install additional PHP modules. If so, just use pkg install php72-modulename and restart php-fpm service.

pkg install php72 php72-mysqli php72-session php72-xml php72-hash php72-ftp php72-curl php72-tokenizer php72-zlib php72-zip php72-filter php72-gd php72-openssl

Add user that will run PHP processes

Please note that the command below adds a user named “mysite”. You probably want to change it but you will have to reflect this change also in the PHP and nginx configuration files below.

pw groupadd mysite && pw useradd mysite -g mysite

Configure PHP

php.ini

You will need to copy the default php.ini file and edit it:

cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini

Edit this file in text editor of your choice: vi, ee or other:

vi /usr/local/etc/php.ini

For security reasons it is advised to disallow some functions which may be abused. Find the line that says:

disable_functions =

and change it to:

disable_functions = shell_exec,system,passthru,exec,curl_exec,proc_open,parse_ini_file,popen,eval,show_source,highlight_file

If your theme or any of your plugins require any of the above functions – consider replacing them with something with safer code if possible.

To hide the PHP version from HTTP requests, find the line that says

expose_php = On

and change it to:

expose_php = Off

You may want to increase the time alloted for PHP scripts to finish. If so, change (these are seconds):

max_execution_time = 30

to:

max_execution_time = 120

or more, even 300 or 600 seconds. This may be required by some time consuming scripts.

You can also increase max input time. Change:

max_input_time = 60

to:

max_input_time = 120

but carefully read the commented description in the file – it may be safer for you to leave this at the default value.

Depending on the amount of plugins and visitors, it it is recommended to increase the memory allocation of PHP processes. If you are on a rather smallish VPS – it will probably be better to leave this as it is.

Change:

memory_limit = 128M

to something higher – for example:

memory_limit = 256M

Increase max POST size (amount of data that PHP can receive from the user):

post_max_size = 8M

to:

post_max_size = 32M

or more – if needed – e.g. 64M or even 4G. Depends on your needs, really.

You probably also want to be able to send larger files to your WordPress. If so, change:

upload_max_filesize = 2M

to:

upload_max_filesize = 32M

or more.

Next, close down PHP to only be able to access /tmp (for session storing) and WordPress directories. This is a security feature that will hide the rest of your filesystem from a successful attacker. Find the following line:

;open_basedir =

and change it to:

open_basedir = /usr/local/www/wordpress:/tmp

If you are not low on RAM – and this is is advised and will boost your performance greatly – enable OPcache: find the below line. Please note the semicolon (;). This is a comment sign and has to be removed:

;opcache.enable=1

change it to:

opcache.enable=1

You probably will also like to increase other OPcache values in this section of the config file over time, but for start you can stay with the defaults.

PHP user pool

Now to the second part of PHP configuration – FPM. Pool is a process or number of PHP processes assigned to one user. For security reasons it is always preferred to create separate pools for different websites and to cut them down with PHP’s open_basedir, or chroot/chdir settings. We are creating only one site here, so we will be using standard “www” pool.

We will not be running the pool as “www” user, but rather as our new non-system user with UID above 1000.

If you have named your user other than “mysite” – it is necessary to reflect these changes here:

vi /usr/local/etc/php-fpm.d/www.conf

Nginx will be connecting to local FPM over UNIX socket so doing it over TCP is not necessary. Find the line:

listen = 127.0.0.1:9000

and change it to:

listen = /var/run/php-wordpress.sock

Next, find the below lines

user = www group = www

and change them to

user = mysite group = mysite

We also need to uncomment and change socket’s ownership variables. Change:

;listen.owner = www ;listen.group = www ;listen.mode = 0660

to:

listen.owner = mysite listen.group = mysite listen.mode = 0600

Another thing that we will want to change is the list of file extensions that will be parsed with PHP. Again – you might encounter a weird theme or plugin that would require you to add additional extensions here. Find the line:

;security.limit_extensions = .php .php3 .php4 .php5 .php7

and uncomment it by removing semicolon:

security.limit_extensions = .php .php3 .php4 .php5 .php7

And let’s enable PHP FPM and start it:

sysrc php_fpm_enable=YES php_fpm_enable: -> YES service php-fpm start Performing sanity check on php-fpm configuration: [12-Feb-2018 04:37:47] NOTICE: configuration file /usr/local/etc/php-fpm.conf test is successful Starting php_fpm.

Download required software

We will need Apache HTTP Server’s libraries and a few other utilities to compile nginx with ModSecurity. We will delete these packages at the end, as they are only needed for nginx compilation. We’ll also need minimal git to grab and update ngx_brotli, ModSecurity and it’s rules from GitHub.

pkg install git-lite libtool automake autoconf curl libnghttp2 apache24 mkdir /root/build && cd /root/build fetch https://nginx.org/download/nginx-1.14.0.tar.gz git clone https://github.com/SpiderLabs/ModSecurity git clone https://github.com/SpiderLabs/owasp-modsecurity-crs git clone https://github.com/google/ngx_brotli cd ngx_brotli && git submodule update --init && cd .. tar -zxf nginx-1.14.0.tar.gz && rm -f nginx*tar.gz

List directory contents and make sure that all four things have been downloaded:

ls -l total 34 drwxr-xr-x 14 root wheel 26 Feb 14 10:06 ModSecurity drwxr-xr-x 8 root wheel 13 Apr 17 17:22 nginx-1.14.0 drwxr-xr-x 5 root wheel 10 Feb 14 10:06 ngx_brotli drwxr-xr-x 7 root wheel 18 Feb 14 10:06 owasp-modsecurity-crs

On the next page, we will compile nginx with all it’s additions, configure it, and install MariaDB and WordPress.