Virtual server for WordPress with Git

Last Revised: October 2, 2021

If you’ve ever considered contributing to WordPress, there are many ways to do so. From here we propose the first steps for this with the creation of a virtual machine in which to synchronize WordPress with your Git, so that it is always up to date.

This configuration is correct if you want to have a development version of WordPress that can be useful to develop and test your plugins or themes.

Requirements

The first thing we will need to set up a development system is a Git account and a machine where to install the development version of WordPress.

Git

We recommend creating an account directly on Github, where the WordPress master repository is located. From this we will create our testing and development system.

Infrastructure

In principle, you can mount the system on any type of machine, whether it is a virtual one, a Docker or a VPS. In this case, so that it is available to everyone and without minimum requirements, we will use a VPS from any provider. If you are interested, you can find some VPS for developers.

In this case we have used a machine with 1 CPU, 2 GB of RAM and 10 GB of SSD disk. With half the resources it should work without problem. What we are going to show is based on an Ubuntu 18 LTS. We will use PHP 7.4, MariaDB 10.4 and other services.

Hacer un fork del WordPress de Github

The first thing to do is enter the WordPress Github and click on the Fork button in the upper right corner.

Click on the Fork button to create a copy in our account.

This ends up creating something of the style to https://github.com/javiercasares/WordPress.

This tutorial has been created on a Clouding.io VPS. You can create your own VPS from 3€/month.

In addition, you have the possibility to create your VPS with the WordPress image in one click.

PARTNERSHIP

We create a machine

As I said before, we can create a machine anywhere. It can be in a VPS of a hosting company, or it can be a Docker that we have locally. There are also options to use Vagrant’s own system. In this case we will create the machine from scratch with our moderately customized configuration.

As the operating system of this example we are going to use Ubuntu 18 LTS.

System Update

The first thing we will do is update the system.

apt -y update && apt -y upgrade && apt -y dist-upgrade && apt -y autoremove

Once this, we will put the system on time.

timedatectl set-timezone UTC
timedatectl set-ntp on

In addition, we will install some basic tools.

apt -y install software-properties-common curl vim unzip

Database Server

For the database we are going to use MariaDB 10.4; this version has a big change in terms of the key system with respect to its predecessor version, so, to avoid problems, we are going to use this latest version.

The first thing will be to download and install the database server.

curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version="mariadb-10.4"
apt -y update
apt -y install mariadb-server mariadb-client
systemctl restart mysql.service

Now that we have the database installed, we will run the configuration system for the first time.

mysql_secure_installation

Here we will be given some options and questions. Prepare your database root password and save it well.

Enter current password for root (enter for none):
Switch to unix_socket authentication [Y/n] n
Change the root password? [Y/n] y
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] y
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y

Once we have answered the questions, we will restart the database to leave it running.

systemctl restart mysql.service

Web server

For the web server we are going to use nginx. This web server works very well with WordPress when it comes to developing or maintaining large sites, although it does not allow the use of .htaccess files (it must be previously configured, without users or plugins being able to change the configuration).

add-apt-repository ppa:ondrej/nginx
apt -y update
apt -y install nginx
systemctl stop nginx.service
systemctl enable nginx.service
systemctl start nginx.service

PHP Server

For WordPress to work we will need to install PHP, the code interpreter. In this case we will use PHP version 7.4. In addition, we will install the recommended extensions, which will mean extra configuration work.

PHP Base Installation

We’ll start with the PHP core and PHP-FPM, plus the basic extensions that come pre-compiled.

add-apt-repository ppa:ondrej/php
apt -y update
apt -y install php7.4 php7.4-fpm php7.4-curl php7.4-gd php7.4-mbstring php7.4-xml php7.4-zip php7.4-mysql php7.4-mysqlnd php7.4-bcmath php7.4-gmp php7.4-tidy php7.4-dev php-pear pkg-config imagemagick libmagickwand-dev
pecl channel-update pecl.php.net

Installing ImageMagick

This extension allows the management of improved images on top of GD.

pecl install imagick
echo 'extension=imagick.so' >> /etc/php/7.4/mods-available/imagick.ini
ln -s /etc/php/7.4/mods-available/imagick.ini /etc/php/7.4/fpm/conf.d/30-imagick.ini

Installing XDiff

This extension allows the application of patches that include file differences.

cd /usr/src
wget http://www.xmailserver.org/libxdiff-0.23.tar.gz
tar -xzf libxdiff-0.23.tar.gz
rm -rf libxdiff-0.23.tar.gz
cd libxdiff-0.23
./configure
make
make install
pecl install xdiff
echo 'extension=xdiff.so' >> /etc/php/7.4/mods-available/xdiff.ini
ln -s /etc/php/7.4/mods-available/xdiff.ini /etc/php/7.4/fpm/conf.d/30-xdiff.ini

Installing APCu

This extension improves the caching system of PHP code.

cd
pecl install apcu
echo 'extension=apcu.so' >> /etc/php/7.4/mods-available/apcu.ini
ln -s /etc/php/7.4/mods-available/apcu.ini /etc/php/7.4/fpm/conf.d/30-apcu.ini

Installing Redis

We will also install the Redis cache server extension.

pecl install redis
echo 'extension=redis.so' >> /etc/php/7.4/mods-available/redis.ini
ln -s /etc/php/7.4/mods-available/redis.ini /etc/php/7.4/fpm/conf.d/30-redis.ini

PHP Configuration

For the development, we will make some changes to the PHP configuration file, especially to give more room for maneuver to the memory and the display of errors per screen and when testing.

The first thing we will do is open the configuration file.

vim /etc/php/7.4/fpm/php.ini

And there we will make some changes in the configuration.

max_execution_time = 60
memory_limit = 256M
error_reporting = E_ALL
display_errors = On
post_max_size = 32M
upload_max_filesize = 32M
date.timezone = UTC

NOTE: It is configured that errors are displayed since it is assumed that this machine is for development.

Once this, we will configure PHP-FPM to be activated automatically with the system.

systemctl stop php7.4-fpm.service
systemctl enable php7.4-fpm.service
systemctl start php7.4-fpm.service

Redis Cache Server

We will install the Redis server and give you some changes to the configuration so that it does not get saturated.

First we will install it and synchronize it with PHP.

apt -y install redis-server php-redis

We will open the configuration file.

vim /etc/redis/redis.conf

And we’ll make some changes to the settings.

maxmemory 256mb
maxmemory-policy allkeys-lru

Finally we will restart it, next to PHP so that its extension is applied.

systemctl stop redis-server.service
systemctl enable redis-server.service
systemctl start redis-server.service
systemctl restart php7.4-fpm.service

TLS Certificate Server

Nowadays all sites should work under HTTPS, so to make the development environment more real and similar we will use a Let’s Encrypt certificate. For this we will need the site to have a hostname / public domain. Normally VPS providers usually put a hostname to the machines, so we can use that one.

We will generate a unique clause in addition to installing the Certbot system for nginx.

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
add-apt-repository ppa:certbot/certbot
apt -y update
apt -y install python-certbot-nginx

And so that we do not forget, we will configure a scheduled task that automatically updates the certificate. To do this we will open the crony editor.

crontab -e

And there we will add the execution, every day at 06:45.

45 6 * * * certbot renew

Completing the configuration

In principle we already have everything we need installed, so, before continuing, we will do a complete system update (again) to remove old software or incompatible with all the one we have installed.

apt -y update && apt -y upgrade && apt -y dist-upgrade && apt -y autoremove

Site Configuration

Now that we have everything ready at the level of system and tools, what we will do is configure the software so that we can work with it as if it were a normal website, although with that development version.

The first thing we will do is remove the default website that brings the system and replace it with one that has little information.

rm /var/www/html/index.*
vim /var/www/html/index.html

Here we will add the following HTML code by default.

<!DOCTYPE html>
<p>Hello World!</p>

We will do the same with the robots file.txt

vim /var/www/html/robots.txt

In which we will block your tracking.

User-Agent: *
Disallow: /

Now that we have the default site fixed, we are going to tweak the nginx settings to be able to work more properly with our development site. First we will delete the default configuration and replace it with a retouch.

cd /etc/nginx/sites-enabled/
rm default
cd /etc/nginx/
cp nginx.conf nginx.conf.original
vim nginx.conf

And we will replace the configuration with the following.

user www-data;
pid /run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;
include /etc/nginx/modules-enabled/*.conf;
events {
  multi_accept on;
  worker_connections 65535;
  use epoll;
}
http {
  charset utf-8;
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  server_tokens off;
  log_not_found off;
  types_hash_max_size 2048;
  client_max_body_size 64m;
  keepalive_timeout 65;
  server_names_hash_bucket_size 128;
  server_names_hash_max_size 1024;
  # MIME
  include /etc/nginx/mime.types;
  default_type application/octet-stream;
  # logging
  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;
  # ssl
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers on; 
  # gzip
  gzip on;
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 9;
  gzip_disable "msie6";
  gzip_buffers 16 8k;
  gzip_min_length 1100;
  gzip_types application/atom+xml application/javascript application/json application/x-javascript application/xml application/xml+rss image/svg+xml text/css text/javascript text/plain text/xml;
  # more
  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

In addition, we will add a configuration to work with PHP and WordPress.

vim wordpress_fastcgi.conf

It will contain the following.

fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_buffers 256 16k;
fastcgi_buffer_size 128k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_intercept_errors off;
fastcgi_split_path_info ^(.+.php)(/.+)$;
try_files $fastcgi_script_name =404;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PHP_ADMIN_VALUE open_basedir=$document_root/:/usr/lib/php/:/tmp/;
fastcgi_param PATH_INFO $path_info;
set $path_info $fastcgi_path_info;
include fastcgi.conf;

Once we have this, we should be able to restart nginx without problem and leave it running.

nginx -t
nginx -s reload

From here we are going to make a copy of the WordPress software depending on the Git account we have previously created. In this case we will mount the entire system in the folder /webs/ from the root of the system, but it can be done on anyone.

mkdir /webs/
cd /webs/
git clone https://github.com/javiercasares/WordPress.git
cd /webs/WordPress/

Now that we have the software cloned, let’s make it navigable as any website is.

cd /etc/nginx/sites-available/
vim WordPress.conf

Where we will create a minimum configuration.

server {
  listen 80;
  listen [::]:80;
  server_tokens off;
  server_name example.com;
  root /webs/WordPress;
  index index.php index.html;
  location = /favicon.ico {
    log_not_found off;
    access_log off;
  }
  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }
  location ~ /.well-known {
    allow all;
  }
  location ~ /.ht {
    deny all;
  }
}

And we will restart nginx for the changes to be applied.

ln -s /etc/nginx/sites-available/WordPress.conf /etc/nginx/sites-enabled/
nginx -t
nginx -s reload

Now we have to create and install the TLS certificate to have a site with HTTPS.

certbot --email hello@example.com --agree-tos --authenticator webroot --installer nginx

When you ask us about the structure of our software, we will tell you that it is in the corresponding folder.

/webs/WordPress

Now we have to create the database for our website. We will need the root password that we previously configured.

mysql -u root -p

You will need a password for this exclusive database for our wordpress development. Please create your own password that is somewhat secure.

CREATE DATABASE wordpress CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin;
GRANT ALL ON wordpress.* TO 'wordpress'@'localhost' IDENTIFIED BY '__PASSWORD__';
GRANT ALL ON wordpress.* TO 'wordpress'@'127.0.0.1' IDENTIFIED BY '__PASSWORD__';
FLUSH PRIVILEGES;
quit

Now that we have the TLS certificate and database, we are going to create our site with all the secure settings, plus some specific settings for WordPress.

cd /etc/nginx/sites-available/
vim WordPress.conf

Here we can replace the basic initial configuration with a more advanced one.

# All HTTP traffic will be sent to HTTPS
server {
  listen 80;
  listen [::]:80;
  server_name example.com;
  return 301 https://example.com$request_uri;
  access_log off;
}
# REAL SITE
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  # SSL
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:128m;
  ssl_session_tickets off;
  # SSL modern configuration
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers on;
  ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
  # SSL OCSP Stapling
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 208.67.222.222 8.8.8.8 valid=300s;
  resolver_timeout 2s;
  # Security headers
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
  #logs
  access_log /var/log/nginx/WordPress-access.log combined buffer=64k flush=5m;
  error_log /var/log/nginx/WordPress-error.log;
  #CONFIG
  server_name example.com;
  root /webs/WordPress;
  index index.php;
  # ROOT
  location / {
    try_files $uri $uri/ /index.php;
  }
  # ROOT PHP
  location ~ .php$ {
    include wordpress_fastcgi.conf;
  }
  location ~ wp-config {
    deny all;
  }
  # HIDDEN FILES
  location ~ /.well-known {
    allow all;
  }
  location ~ /.ht {
    deny all;
  }
  # WEB FILES
  location ~ /favicon.(ico|png) {
    log_not_found off;
    access_log off;
  }
  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }
  # STATIC CACHES
  location ~* .(aac|avi|bmp|bz2|cur|docx?|eot|exe|flv|gif|gz|heic|htc|ico|jpe?g|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|png|pptx?|rar|rtf|svgz?|tar|tgz|tiff?|ttc|ttf|txt|wav|webm|webp|wmv|woff|woff2|xlsx?|zip)$ {
    expires max;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* .(atom|css|js|rss)$ {
    expires 7d;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* .(?:eot|otf|ttf|woff|woff2)$ {
    add_header Access-Control-Allow-Origin "*";
  }
  location ~* /wp-admin/load-(?:scripts|styles).php {
    if ( $query_string ~* "^.{512,}$" ) {
      return 444;
    }
  }
}

We will restart nginx and access the folder of our new WordPress.

nginx -t
nginx -s reload
cd /webs/WordPress/

Installing WordPress

Now that we have everything ready at the systems level to have a WordPress ready to blew errors and problems, we will start the installation as it is any WordPress. To do this we will simply access the web address of our hostname / domain that we have previously configured.

We set up Git with a stream for WordPress

Now we have to configure our software so that it can be kept up to date with the WordPress development source code. It should be remembered that this version of the software is usually an alpha or beta, so the chances of errors are great, and therefore should be reported in the WordPress Trac.

We’ll start by looking at what remote data we have.

cd /webs/WordPress/
git remote -v

Something like this should appear to us:

origin  https://github.com/javiercasares/WordPress.git (fetch)
origin  https://github.com/javiercasares/WordPress.git (push)

We will add another source, that of the main Git.

git remote add wordpress https://github.com/WordPress/WordPress.git
git remote -v

This will return to us that we have several sources, such that.

origin  https://github.com/javiercasares/WordPress.git (fetch)
origin  https://github.com/javiercasares/WordPress.git (push)
wordpress  https://github.com/WordPress/WordPress.git (fetch)
wordpress  https://github.com/WordPress/WordPress.git (push)

From this momeno, we can update and synchronize our code, executing an update of Git and, incidentally, of the entire system.

apt -y update && apt -y upgrade && apt -y dist-upgrade && apt -y autoremove
cd /webs/WordPress/
git pull wordpress master

And so far we have an installation from scratch of the main WordPress development Git, where we can analyze or verify our developments for future versions.

If you enter the URL, it will ask you to fill in the data like any normal installation. another option is to create your own wp-config file.php and have the installation start working.


About this document

This document is regulated by the EUPL v1.2 license, published in WP SysAdmin and created by Javier Casares. Please, if you use this content in your website, your presentation or any material you distribute, remember to mention this site or its author, and having to put the material you create under EUPL license.