Before installing WordPress, we will need to setup a web server for our Raspberry Pi. We will use Nginx as the web server itself, MariaDB as a database management system, and PHP for dynamic script processing. We choose Nginx, PHP-FPM and MariaDB for its performance and lightweight that are both important factors for Raspberry Pi.

Nginx is a popular lightweight web server application available for Raspberry Pi. PHP-FPM (FastCGI Process Manager) is an alternative PHP FastCGI implementation with some additional features useful for sites of any size, especially busier sites. MariaDB is one of the most popular database servers in the world. It’s made by the original developers of MySQL and guaranteed to stay open source.

This article has been updated on 5 Jan 2018 based on Raspbian Stretch

Install Nginx

Like Apache, NGINX (pronounced engine x) can serve HTML files over HTTP, and with additional modules can serve dynamic web pages using scripting languages such as PHP. First install the nginx package by typing the following command in to the Terminal:

sudo apt-get install nginx

and start the server with:

sudo service nginx start

Test the web server

By default, Ngnix puts a test HTML page in the web folder. This default web page is served when accessing the Raspberry Pi via web browser (in my case, the Raspberry Pi’s IP address is http://192.168.1.101). When nginx is up and running, the webpage will shown as:

Nginx is up and running

The default webpage is served from the directory /var/www/html.

Customize nginx

Now that Nginx has successfully installed it’s time to perform some basic configuration. Out of the box Nginx is pretty well optimized, however there are a few basic adjustments to make for better performance by altering the settings in nginx.conf:

sudo nano /etc/nginx/nginx.conf

Make the following changes for improving potential performance:
1) Uncommented the multi_accept directive and set to on, which informs each worker_process to accept all new connections at a time, opposed to accepting one new connection at a time.

2) Change the keepalive_timeout from default 65 to 10. The keepalive_timeout determines how many seconds a connection to the client should be kept open before it’s closed by Nginx. This directive should be lowered so that idle connections can be closed earlier at 10 seconds instead of 65 seconds.

3) Uncomment the server_tokens directive and ensure it is set to off. This will disable emitting the Nginx version number in error messages and response headers.

4) Following the server_tokens, add the client_max_body_size directive and set this to 16m.

5) Uncomment the gzip_vary on, this tell proxies to cache both the gzipped and regular version of a resource where a non-gzip capable client would not display gibberish due to the gzipped files.

6) Uncomment the gzip_proxied directive and set it to any, which will ensure all proxied request responses are gzipped.

7) Uncomment the gzip_comp_level and change the value to 5. Level 5 provides approximate 75% reduction in any ASCII type of files to achieve almost same result as level 9 but not have significant impact on CPU usage as level 9.

8) Uncomment gzip_http_version 1.1;, this will enable compression both for HTTP/1.0 and HTTP/1.1.

9) Add a line gzip_min_length 256; right before gzip_types directive, this will ensure that the file smaller than 256 bytes would not be gzipped, the default value was set at 20 bytes which are too small and could cause the gzipped file even bigger due to the overhead.

10) Replace the gzip_types directive with the following MIME types to avoid the complains from Google PageSpeed test on certain files types that were not included in the default MIME types provided.

Save the file and reload the configuration file with the following command:

sudo service nginx reload

Test PHP

Create an index.php file with:

sudo nano /var/www/html/index.php

Add some dynamic PHP content by replacing the current content:

<?php echo phpinfo(); ?>

Save and refresh the browser (or type the url as http://192.168.0.101). A page with the PHP version, logo and current configuration settings will be displayed.

Run index.php which echo out phpinfo() result

The image was based on php5 installation on Raspbian Jessie, it didn’t get updated to php7.0 for Raspbian Stretch

Change php.ini settings

There is not much to change at least for now on the php.ini settings, except probably two, first is the upload_max_filesize need to be increased to be the same as client_max_body_size directive within Nginx. Open the php.ini file which is stored at /etc/php/7.0/fpm. :

sudo nano /etc/php/7.0/fpm/php.ini

Search the following line and change the value of upload_max_filesize value to match the value we previously assigned to the client_max_body_size directive when configuring Nginx:

upload_max_filesize = 16m

Search for the cgi.fix_pathinfo parameter. We need to uncomment the line and change the value from “1′ to “0”:

cgi.fix_pathinfo=0

This tells PHP not to try to execute a similar named script if the requested file name cannot be found. This prevents potential attacker to craft a specially designed request to try to trick PHP into executing code that it should not.

Hit Control-X and Y to save the configuration.

Install MariaDB

if you are familiar with MySQL, you will find that MariaDB installation looks almost identical:

sudo apt-get install mariadb-server mariadb-client

Once installation is completed, we can login using the root account

sudo mysql

It is not a good idea to use root account for daily operation. So we want to create a super user with all the privileges as root for administration purpose, and disable the remote access of root account later.

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

Please replace the username with the name of your choice and the password should be the real password that you’d want to use for login into the system. Next, we will grant all the privileges this newly create user.

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

Quit the mariaDB and login with the newly created username and password and check if the user has all the privileges settings correctly.

We now need to tighten the security of mariaDB access.

sudo mysql_secure_installation

Secure MariaDB

We already setup the password during the installation of mariaDB, therefore We will skip the “change root password” question. We answer with ‘y’ on the question of “remove anonymous users”. We answer ’y’ for the question of “disallow root login remotely” since we now have an newly created user with all the privileges as ‘root’ user. As far as we know, unlike mysql, there is no ‘test’ database pre-installed, so we answer the question “remove test database” with ’n’. And agreed to “reload privilege tables”.

Database management using Adminer

In order to be able to manage the database table easily via web interface, we install Adminer as it is much cleaner and simpler to use than phpmyadmin. At the time of this writing, the latest stable version is 4.3.1, check the version prior your installation and modify the installation command accordingly. Setup Adminer is very straightforward.

To use the Adminer, run the browser with http:192.168.0.101/admin/adminer.php

Adminer login page

An alternative UI skin (basically a css file) can be downloaded from Adminer website.

Protect the admin directory

As we just created an /admin directory under /var/www/html/ for us to access the Adminer through web interface which is very convenient but it is also accessible by anyone via web browser if someone know the directory exists. We don’t want others to access the directory without permission, it would be good to only allow for accessing via internal IP but block access from public IP.

To do that, we need to configure the nginx for the /admin directory access:

sudo nano /etc/nginx/sites-available/default

Insert the following section within the server section, right after the rest of location directives:

Once the file is saved, we need to restart the server for the new setting to take effect, but would be good to test the setting prior restart.

sudo nginx -t
sudo service nginx restart

Now when we access the /admin directory from external IP (you might need to flush the browser cache first if you access the directory prior the setup of password protection), a Nginx 403 error page will be shown. However, the access will be passed through when accessing via internal network with an IP address between 192.168.0.0 to 192.168.0.255.

That’s all for now, we have a web server up and running and ready for installing WordPress.

Post navigation

18 comments by readers

Hi! I love your guide, but I was wondering if you could help me… I followed your guide to a tee – however when I try and access my wordpress using an external connection it is EXTREMELY slow and then loads index.php with no CSS at all…

When accessing locally WP loads instantly, so I don’t understand…

Creating a PHP test page works locally AND publicly. So does test HTML pages. So it’s something to do with WordPress/Database and the public version of the site… What do you suggest the problem is? It’s weird it half loads publicly, if there was an error in my settings i’d expect it to not load at all!

Joe, Thanks for your comment, I also got your FB messenger messages said that you have solved the problem. If you are still have the problem, let me know and I will try to help to troubleshoot as much as possible.

Thanks for such an amazing, detailed, up-to-date (for Raspbian Stretch) post for Installing WordPress using Nginx.
I need your little help, after installing WP on Raspberry Pi, my website is literally very fast on local network and my site address is 192.168.0.10. But my plan was to serve my website through Hotspot, so I followed this tutorial https://github.com/quangthanh010290/RPI3_HOTSPOTS and I modified his script so it uses 192.168.0.10 as router gateway instead of 192.168.42.1 .
This worked well but my website’s response is too slow on hotspot as compared to on local network. Thanks in advance ☺️

I do tried on using wlan0 and experienced similar speed as eth0 (my wifi router is 600Mbps capable), I never have experience on hotspot though, therefore hard for me to comment. But if your RPi IP is 192.168.0.10, your gateway setting should likely be 192.168.0.1 (you can check with your router).

The setting looks correct. Try to empty the cache of your browser and try again. Also for testing purpose, try to comment out(by adding ‘#’ in front of the line) allow 192.168.0.0/24; and deny all; to see if it works? The location ~/admin/.*\.php$ directive is basically the same as location ~ \.php$ directive setting except that it only allows a specific local IP range 192.168.0.xxx to come through.

Thank You for fast response, You’re great.
I tried on 3 different devices connected to my wifi with IP 192.168.0.xxx, but it’s not working. It’s working without this code, but that’s obvious, because then everybody can log in.

I understand the code and tried to comment out some lines, but still the same error and ofc I was reloading nginx after every change.
EDT.
During writing this post I figured it out.
At the end of the file I added :

Ah, glad that you figure this out. To benefit the future readers, I changed the sentence in my blog from “Add the follow section under the server section” to “Insert the following section within the server section, right after the rest of location directives”. Hope this is more clear of where it should be inserted.

First – great guide Henry, probably the best walk-through for RPI web servers on the web. I am however having the same problem as ADmin_gateaway. I have inserted the location block exactly as stated but it does nothing, even after clearing out my browser cache. I have experimented with just using deny all and commenting out my local network – #allow 192.168.2.0/24 – but I have the same problem, and adminer remains open to the world. Any further suggestions?

I think your case is different from ADmin_gateway who experienced a 502 error. While your case seems that the location directive for /admin/ is not working at all. What is your root directory and `root` directive setting? And how the /admin/ directory correspond to the root directory?

It seems that somehow your try_files redirect and the .php wildcard location directive get executed before it hit the /admin/ location directive. Try to move the location directive for /admin/ up right after the location / directive and before the location ~ \.php$ block, as shown here:

First, thanks for this tutorial. On other tutorials always something is missing. I installed it on a RPI2 which runs quite good till now.

First a hint from me, maybe I missed it. But if you want to use the WordPress APP to connect to a self hosted WordPress you also have to install php7.0-xml and php-xml. Probably you didnt mention it due to security reasons

I had also problems to upload files larger than 8MB. I looked again and again, finally… (dumb me)
phpinfo: Check if itpoints to the right config file: /etc/php/7.0/fpm/php.ini
file_uploads=on; upload_max_filesize=100M
But for me i had also to change this:
post_max_size = 100M

My PHP installation shown as on my post sudo apt-get install php7.0-fpm php7.0-mysqlnd php7.0-xmlrpc php7.0-curl php7.0-gd php7.0-imagick php7.0-imap -y which does not explicitly install php7.0-xml, and works fine in my case. The needs of php7.0-xml might be required by specific theme or plugin as well.

As mentioned in the blog, I set both upload_max_filesize in php.ini and client_max_body_size under the Nginx http directive to 16M, which is sufficient for my case, but should be adjusted based on each individual’s application requirements. The upload_max_filesize is the limit of any single file. The post_max_size is the limit of the entire body of the request, which could include multiple files.