Setting up WordPress on Raspberry Pi 3 with Raspbian Stretch Lite, Nginx, MariaDB and PHP 7 as the LEMP stack

Raspbian Stretch was released on 17th August 2017 and this will mean that we will be able to get a variant of Debian 9 on our Raspberry Pi. With Raspbian Stretch, we will be able to run WordPress or any PHP framework with PHP 7.0 which Zend had indicated a performance boost of up to two folds as compared to PHP 5.6.

Just like the benefits that blogging brings to a programmer, the performance boost that PHP 7 brings about is a good reason for me to port my blog over to PHP 7.

Before porting my blog over to PHP 7, it will make sense for me to perform a little proof of concept on my Raspberry Pi 3 first. With Raspbian Stretch Lite, I can see for myself that my blog runs well with PHP 7.0 before spawning a new Digital Ocean instance for Techcoil.

This post documents how I setup an instance of WordPress on Raspberry Pi 3 with Raspbian Stretch Lite, Nginx, MariaDB and PHP 7 as the LEMP stack.

Hardware List to build my Raspberry Pi 3 LEMP server

I used the following hardware for my Raspberry Pi 3 LEMP server:

Setting up Raspbian Stretch Lite with SSH server enabled on my microSD card

Once I had gathered all the necessary hardware, proceed to setup Raspbian Stretch Lite with SSH server enabled on my microSD card. Doing so allowed me to SSH into my Raspbian Stretch Lite to perform further configurations.

Assembling the hardware for the Raspberry Pi LEMP Server

Next, I removed my microSD card from my SD card reader and inserted it to the microSD card slot on the Raspberry Pi 3 board. After that, I went ahead to assemble the Raspberry Pi 3 board to the Official Raspberry Pi case.

Starting the Raspbian Stretch Lite operating system

With the assembly of my Raspberry Pi 3 board and Official Raspberry Pi case, I connected one end of the RJ45 cable to the RJ45 port on my Raspberry Pi 3 board and the other end of the cable to one of the switch port of my home router. After that, I connected my micro USB cable and supply power to my Raspberry Pi 3 board.

Changing default password, Locale and Timezone of my Raspbian Stretch Lite

There are a few configurations that we should perform on the first run of our Raspbian Stretch Lite. Therefore I proceeded on to change the default password, Locale and Timezone of Raspbian Stretch Lite.

Installing Nginx on Raspbian Stretch Lite

After configuring the locale settings, I proceeded to install Nginx on my Raspbian Stretch Lite. To do so, I entered the following command:

sudo apt-get update
sudo apt-get install nginx -y

After the installation had completed, I ran the following command to verify that the installation was successful:

systemctl status nginx.service

The command returned me with the following output to tell me that Nginx was installed successfully on my Raspbian Stretch Lite:

● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2017-09-23 07:25:05 UTC; 7min ago
     Docs: man:nginx(8)
  Process: 15356 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
  Process: 15353 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS
 Main PID: 15357 (nginx)
   CGroup: /system.slice/nginx.service
           ├─15357 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
           ├─15358 nginx: worker process
           ├─15359 nginx: worker process
           ├─15360 nginx: worker process
           └─15361 nginx: worker process

Sep 23 07:25:05 raspberrypi systemd[1]: Starting A high performance web server and a reverse proxy server...
Sep 23 07:25:05 raspberrypi systemd[1]: Started A high performance web server and a reverse proxy server.

Installing MariaDB database server and command line client on my Raspbian Stretch Lite

After I had gotten Nginx on my Raspbian Stretch Lite, I then proceeded on to install the MariaDB database server and the command line client on my Raspbian Stretch Lite. To do so, I ran the following command:

sudo apt-get install mariadb-server mariadb-client -y

Unlike my previous experience with installing mysql servers, this installation of mariadb-server did not prompt me for a root password. This was because the root user was by default set to use the unix_socket plugin, which allows the user to use operating system credentials when connecting to MariaDB via Unix socket.

Since the pi user of my Raspbian Stretch installation was configured to use sudo without password prompt, I could get into my mariadb-server through the following command without supplying any password:

sudo mariadb 

Installing PHP 7 on my Raspbian Stretch Lite

Next item on the list was to install the components needed to run WordPress. For that, I needed PHP7, PHP7 Fast CGI Process Manager and PHP7 MySQL bridge to be installed. To install these three items, I ran the following command:

sudo apt-get install php7.0 php7.0-fpm php7.0-mysql -y

After the installation had completed, I entered the following command to check the status:

systemctl status php7.0-fpm.service

Seeing the following output assured me that my PHP Fast CGI Manager had started successfully:

● php7.0-fpm.service - The PHP 7.0 FastCGI Process Manager
   Loaded: loaded (/lib/systemd/system/php7.0-fpm.service; enabled; vendor prese
   Active: active (running) since Sat 2017-09-23 07:59:21 UTC; 1min 54s ago
     Docs: man:php-fpm7.0(8)
 Main PID: 12314 (php-fpm7.0)
   Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/se
   CGroup: /system.slice/php7.0-fpm.service
           ├─12314 php-fpm: master process (/etc/php/7.0/fpm/php-fpm.conf)
           ├─12315 php-fpm: pool www
           └─12316 php-fpm: pool www

Getting a copy of WordPress

Once I had completed installation of the required components to run WordPress on Raspbian Stretch Lite, I proceeded to get a copy of WordPress.

To do so, I ran the following commands:

cd /var/www
sudo wget https://wordpress.org/latest.tar.gz
sudo tar xvfz latest.tar.gz
sudo rm latest.tar.gz
sudo mv wordpress my_new_wordpress_site

Doing so would leave a my_new_wordpress_site folder inside the /var/www folder. The my_new_wordpress_site folder will contain the codes necessary to run WordPress.

Changing the owner of my_new_wordpress_site folder to www-data

In order for media upload to work, the www-data user needs to owned the my_new_wordpress_site folder. Hence, the next step was to change the ownership of the my_new_wordpress_site folder to the same user that runs PHP Fast CGI Manager:

sudo chown -R www-data:www-data /var/www/my_new_wordpress_site

Configuring Nginx to proxy HTTP requests to the PHP 7 Fast CGI Manager

Next, I went on to configure Nginx to serve as a reverse proxy server for my WordPress site. Referencing the informative guide on configuring nginx for a PHP web application like WordPress, I created /etc/nginx/sites-enabled/anewwebsite.com.conf:

# WordPress single site rules.
# Designed to be included in any server {} block.
# Upstream to abstract backend connection(s) for php
upstream php {
        server unix:/run/php/php7.0-fpm.sock;
}

server {
        listen   80;
        ## Your website name goes here.
        server_name anewwebsite.com www.anewwebsite.com;
        ## Your only path reference.
        root /var/www/my_new_wordpress_site;
        ## This should be in your http block and if it is, it's not needed here.
        index index.php;

        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }

        location / {
                # This is cool because no php is touched for static content.
                # include the "?$args" part so non-default permalinks doesn't break when using query string
                try_files $uri $uri/ /index.php?$args;
        }

        location ~ \.php$ {
                #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
                include fastcgi.conf;
                fastcgi_intercept_errors on;
                fastcgi_pass php;
                fastcgi_buffers 16 16k;
                fastcgi_buffer_size 32k;
        }

        location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
        }
}

Once I had created the configuration file, I got Nginx to take in the configurations by restarting it with the following command:

sudo systemctl restart nginx.service

Creating a MariaDB user and a database instance for the WordPress site

Next up was to create the necessary items for WordPress to use the MariaDB database. It is good practice to create a separate database user for the WordPress site to interact with the database. As such, the next step that I did was to create a Maria DB user that only have the privileges to interact with a particular database instance.

To do so, I first got into the mariadb-server using mariadb with the root user:

sudo mariadb 

After keying in my password for that root user, I first ran the following command to create a new database instance:

CREATE DATABASE newWordPressDb;

With that database instance created successfully, I then created the MariaDB user with the relevant privileges for accessing that database instance:

CREATE USER 'anewuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL ON newWordPressDb.* TO 'anewuser'@'localhost';

With that, I would have a MariaDB user for my WordPress site to use a MariaDB database instance.

Setting cgi.fix_pathinfo = 0 in php.ini of PHP 7 Fast CGI Manager

Since WordPress suggested that we should have "cgi.fix_pathinfo = 0;" in php.ini, the next step that I did was to do that.

The php.ini file for PHP 7 Fast CGI Manager is found in the /etc/php/7.0/fpm folder. With that, I used nano to open up /etc/php/7.0/fpm/php.ini and replaced:

;cgi.fix_pathinfo = 1;

with:

cgi.fix_pathinfo = 0;

I then restarted the PHP 7 Fast CGI Manager with the following command:

sudo systemctl restart php7.0-fpm.service

Creating the WordPress configurations to use the database instance

With the MariaDB user and database instance in place, the next thing that I did was to create the WordPress configurations for WordPress to use the database instance.

To do so, I first renamed /var/www/my_new_wordpress_site/wp-config-sample.php to /var/www/my_new_wordpress_site/wp-config.php:

sudo mv /var/www/my_new_wordpress_site/wp-config-sample.php /var/www/my_new_wordpress_site/wp-config.php

I then used my browser to access https://api.wordpress.org/secret-key/1.1/salt/ to generate some key and salt values for my /var/www/my_new_wordpress_site/wp-config.php.

With that, I opened up /var/www/my_new_wordpress_site/wp-config.php with nano and updated some sections of the file to look like the following:

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'newWordPressDb');

/** MySQL database username */
define('DB_USER', 'anewuser');

/** MySQL database password */
define('DB_PASSWORD', 'password');

/** MySQL hostname */
define('DB_HOST', 'localhost');

/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8');

/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', '');

/**#@+
 * Authentication Unique Keys and Salts.
 *
 * Change these to different unique phrases!
 * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
 * You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
define('AUTH_KEY',         '3nGgSDEQkxEnCPDi,l/v?=bx@9KN`OmB?/g=:Adj+Nn<sR+6iH_Q2Ch&-=An.tU!');
define('SECURE_AUTH_KEY',  'l]iOF-zG.%p+A|C3}?2-f<vCu*ar|dA:_[/h&<X~1os=}S:[$.]z[2m#Yto>g:T=');
define('LOGGED_IN_KEY',    'du-Xi](g:HvJ;yl$I-nj+)B)$lCWI@,[vorWnj,7^g_N|6CCeE=_js%m[.80:+O@');
define('NONCE_KEY',        'T<:<9+%}L1^|1O+hoP{!vRgy-7Z90XJoO(|s =HMYxf;D+@Q|En-3.8c.[fEt%*d');
define('AUTH_SALT',        'OxlI/I4eq_9dQ{C|fm<sN~E=~-2iX}4%A3+1EF$2kV2N6v8V%zU^|31$8@-G^gNf');
define('SECURE_AUTH_SALT', '=+Oo8d*Y(@wqleOK[9sq3Yg*7x}i&0J!k;J#i>8d|YUioee~($Cb2_CL*aeEJLzm');
define('LOGGED_IN_SALT',   '$lr:;X8HG3$o<4s%>gM:0SfZ+1cME _VcQv[%.{UK!:E`7EI[:-^aT:sjYZ5h>&x');
define('NONCE_SALT',       'oVm~a@[$7kb?xxv`_tx?MbOlv:~NV7;{FX/NfvCnb}V|u+9ozG&iJ<=rW+{#qY,{');
/**#@-*/

Editing the hosts file to access my WordPress site from my work computer

After putting in the configurations that are necessary for WordPress to function, the next thing that I did was to get my computer to resolve the domains anewwebsite.com and www.anewwebsite.com with the IP Address of my Raspberry Pi 3: 192.168.1.109. To do so, I opened up /etc/hosts on my Mac and add in the following entries:

192.168.1.109 anewwebsite.com
192.168.1.109 www.anewwebsite.com

Running the WordPress installation script

Once I had added the necessary hosts entries, I then accessed www.anewwebsite.com with my browser.

Once the installation page appeared, I then keyed in the necessary input:

WordPress installation page 1 running from Raspbian Stretch Lite

And clicked the Install WordPress button:

WordPress installation successful message from Raspbian Stretch Lite

With that, I had WordPress running on Raspberry Pi 3 with Raspbian Stretch Lite, Nginx, MariaDB and PHP 7 as the LEMP stack.

Until I am ready to expose my Raspberry Pi 3 WordPress to the Internet, this setup is sufficient for now.

Further enhancements that I will want to make to my Raspberry Pi 3 WordPress site

When I have clocked enough Raspberry Pi 3 WordPress site in the future, there are some enhancements that will want to make.

Increasing file upload limit

Since the default file upload size limit is slightly less than 1 Megabyte for Nginx and 2 Megabytes for PHP FPM 7, I can only upload slightly less than 1 Megabyte of content to my Raspberry Pi 3 WordPress site.

When I wish to tweak the file upload limit, I can look at how to configure Nginx and PHP 7 stack in Linux to increase or decrease file upload size limit.

Installing a SMTP server for my Raspberry Pi 3 WordPress site to send out emails

When I want to receive emails from my Raspberry Pi 3 WordPress, I will need to install a SMTP server on my Raspberry Pi 3. For this purpose, I can install Postfix as the SMTP server for applications in Raspbian Stretch Lite to send email.

Let's Encrypt my Raspberry Pi 3 WordPress site with a browser-trusted SSL/TLS certificates

Since Google encourages securing our site with HTTPS for better user experience, I should make my Raspberry Pi 3 WordPress communicate in HTTPS when I put it onto the Internet.

When I make my Raspberry Pi 3 WordPress communicate in HTTPS, I can be pretty sure that the communication channel between browsers and my WordPress site is safely encrypted.

Fortunately, Let's Encrypt makes it easy for web masters to deploy secure web applications that serve HTTPS.

Prequisites to serving my Raspberry Pi 3 WordPress site via HTTPS

In a separate post, I had discussed the topic on how to host multiple websites from home. Following those pointers, I will need the following pieces for my Raspberry Pi 3 WordPress site to be accessed from outside my home network with Let's Encrypt browser-trusted certificate:

Installing Certbot on Raspbian Stretch Lite for obtaining Let’s Encrypt’s browser-trusted certificates

In order to use Let's Encrypt facilities, we will need a ACME client to help us get the SSL artefacts from Let's Encrypt. Therefore, I will need to install Certbot on Raspbian Stretch Lite for obtaining Let’s Encrypt’s browser-trusted certificates.

Configuring Nginx to facilitate Certbot in acquiring the SSL certificate for my domain or subdomain

After installing Certbot, I will need to configure Nginx to facilitate Certbot in acquiring the SSL certificate for my domain. For the purpose of this guide, let's assume that I designate mynewblog.techcoil.com as the domain to reach my WordPress site on my Raspberry Pi 3.

Given these points, I will use nano to create the Nginx configurations file at /etc/nginx/sites-enabled/mynewblog.techcoil.com.conf:

sudo nano /etc/nginx/sites-enabled/mynewblog.techcoil.com.conf

After the editor loads the file, I will include the following content in the file:

server {
    listen   80;
    root /var/www/my_new_wordpress_site;
    index index.php;
   
    server_name mynewblog.techcoil.com;
 
    location ~ /.well-known {
        allow all;
    }  
}

Once I had included the content, I will type Ctrl-X followed by Y to save the file.

Afterwards, I will restart Nginx with the following command:

sudo systemctl restart nginx.service

Using Certbot to get Let's Encrypt to issue browser-trusted SSL certificate for my domain

After Nginx is ready to facilitate Certbot in acquiring the SSL certificate artefacts, I will then run the following command to acquire them:

sudo certbot certonly -a webroot --webroot-path=/var/www/my_new_wordpress_site -d mynewblog.techcoil.com

Generating a strong Diffie-Hellman group

Once Certbot had fetched the SSL certificate artefacts for my domain, I will then generate a Diffie-Hellman group for Nginx to use for exchanging cryptographic keys with its clients:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Updating the Nginx configurations for serving HTTPS for my Raspberry Pi 3 WordPress site

Till this point, I will have the necessary artefacts for Nginx to serve HTTPS. Therefore, I can update the Nginx configurations to use those artefacts in serving HTTPS.

Therefore, I will first use nano to load /etc/nginx/sites-enabled/mynewblog.techcoil.com.conf again:

sudo nano /etc/nginx/sites-enabled/mynewblog.techcoil.com.conf

Once the editor loads the file, I will replace its content with the following:

# WordPress single site rules.
# Designed to be included in any server {} block.
# Upstream to abstract backend connection(s) for php
upstream php {
        server unix:/run/php/php7.0-fpm.sock;
}

server {
    listen 80;
    server_name  mynewblog.techcoil.com;
    return 301 https://$host$request_uri;
}
     
# For ssl
server {
    ssl on;
    ssl_certificate /etc/letsencrypt/live/mynewblog.techcoil.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mynewblog.techcoil.com/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_stapling on;
    ssl_stapling_verify on;
    add_header Strict-Transport-Security max-age=15768000;
         
    default_type  application/octet-stream;
         
    listen 443;
    server_name  mynewblog.techcoil.com;
  
    root /var/www/my_new_wordpress_site;
    index index.php;
  
    location ~ /.well-known {
        allow all;
    }  
 
    location = /favicon.ico {
                log_not_found off;
                access_log off;
    }
 
    location = /robots.txt {
            allow all;
            log_not_found off;
            access_log off;
    }
 
    location / {
            # This is cool because no php is touched for static content.
            # include the "?$args" part so non-default permalinks doesn't break when using query string
            try_files $uri $uri/ /index.php?$args;
    }
 
    location ~ \.php$ {
            #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
            include fastcgi.conf;
            fastcgi_intercept_errors on;
            fastcgi_pass php;
            fastcgi_buffers 16 16k;
            fastcgi_buffer_size 32k;
    }
 
    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
            expires max;
            log_not_found off;
    }
  
}

After that, I will type Ctrl-X followed by Y to save the configuration file.

Once I had saved the configuration file, I will run the following command to restart Nginx:

sudo systemctl restart nginx.service

Updating WordPress on the new URL

Once my Nginx server can serve my Raspberry Pi WordPress site with HTTPS, I will then get into MariaDB shell with the following command:

sudo mariadb

When MariaDB shell gets loaded, I will then run the following SQL statements to update WordPress with my new domain name:

UPDATE wp_options SET option_value = replace(option_value, 'http://anewwebsite.com', 'https://mynewblog.techcoil.com') WHERE option_name = 'home' OR option_name = 'siteurl';

UPDATE wp_posts SET guid = replace(guid, 'http://anewwebsite.com','https://mynewblog.techcoil.com');

UPDATE wp_posts SET post_content = replace(post_content, 'http://anewwebsite.com', 'https://mynewblog.techcoil.com');

UPDATE wp_postmeta SET meta_value = replace(meta_value,'http://anewwebsite.com','https://mynewblog.techcoil.com');

UPDATE wp_options SET option_value = replace(option_value, 'http://www.anewwebsite.com', 'https://mynewblog.techcoil.com') WHERE option_name = 'home' OR option_name = 'siteurl';

UPDATE wp_posts SET guid = replace(guid, 'http://www.anewwebsite.com','https://mynewblog.techcoil.com');

UPDATE wp_posts SET post_content = replace(post_content, 'http://www.anewwebsite.com', 'https://mynewblog.techcoil.com');

UPDATE wp_postmeta SET meta_value = replace(meta_value,'http://www.anewwebsite.com','https://mynewblog.techcoil.com');

Cleaning up unnecessary configurations

Since I will be able to access my Raspberry Pi 3 WordPress site with the new URL, https://mynewblog.techcoil.com, I will not be some of the configurations that I had created earlier.

Therefore, I will run the following command to remove the unnecessary Nginx configuration file at /etc/nginx/sites-enabled/anewwebsite.com.conf:

sudo rm /etc/nginx/sites-enabled/anewwebsite.com.conf

Once I had removed the Nginx configurations, I will then restart Nginx with the following command:

sudo systemctl restart nginx.service

After that, I will go to my laptop and remove the following content from /etc/hosts:

192.168.1.109 anewwebsite.com
192.168.1.109 www.anewwebsite.com

Buying the Raspberry Pi 3 hardware to build your own LEMP server to run WordPress

If you do not have the Raspberry Pi 3 hardware mentioned in this post yet, you may want to purchase them from Amazon. Simply click on the button below to add the Raspberry Pi 3 hardware to your cart. You may remove anything that you already have or replace some of the hardware with other hardware.


Setting up WordPress on Raspberry Pi 3 with Raspbian Stretch Lite, Nginx, MariaDB and PHP 7 as the LEMP stack

About Clivant

Clivant a.k.a Chai Heng enjoys composing software and building systems to serve people. He owns techcoil.com and hopes that whatever he had written and built so far had benefited people. All views expressed belongs to him and are not representative of the company that he works/worked for.