Virtual Server for WordPress with Subversion

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 Subversion, so that it is always up to date and that allows you to work with the same material in which the developer community does.

Requirements

In principle it is not necessary any special requirement, simply to have access to the WordPress SVN.

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.

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

Installing ImageMagick

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

pecl channel-update pecl.php.net
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
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'

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

Installing Subversion

To work we will need the Subversion client tools, so we will install the tool.

apt -y install subversion subversion-tools libsvn-dev

Installing NodeJS

In order for us to run the entire development system and some other tests we will need NPM and NodeJS, so we will proceed to install it.

cd
curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
apt -y install nodejs

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;
  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 will make a copy of the WordPress software depending on the official WordPress Subversion. 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/
mkdir /webs/wordpress-svn/
cd /webs/wordpress-svn/
svn co https://develop.svn.wordpress.org/trunk/ .

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

cd /etc/nginx/sites-available/
vim wordpress-svn.conf

Where we will create a minimum configuration.

server {
  listen 80;
  listen [::]:80;
  server_tokens off;
  server_name example.com;
  root /webs/wordpress-svn/src;
  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-svn.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-svn/src

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-svn.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;
  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-svn/src;
  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-svn/

We set up Subversion 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.

To begin with, we will have to install NPM so that we can make it work as needed.

cd /webs/wordpress-svn/
npm install
npm run dev

With this we will have a lot of tools and specific configurations for WordPress development.

Installing WordPress

Before entering our WordPress development, it is best to create a fairly advanced configuration file. By default we will install it in the root folder of our installation.

cd /webs/wordpress-svn/src/
vim wp-config.php

That would contain content similar to the following. You can create your own configuration from wp-config.pro. NOTE: Remember to update the database data with your own data.

<?php

/* Database connection */
define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wordpress' );
define( 'DB_PASSWORD', '__PASSWORD__' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', 'utf8mb4_bin' );

/* Tables */
$table_prefix = 'wp_';

/* Security */
/* Security Keys */
define( 'AUTH_KEY', 'put your unique phrase here' );
define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );
define( 'LOGGED_IN_KEY', 'put your unique phrase here' );
define( 'NONCE_KEY', 'put your unique phrase here' );
define( 'AUTH_SALT', 'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT', 'put your unique phrase here' );
define( 'NONCE_SALT', 'put your unique phrase here' );
/* HTTPS */
define( 'FORCE_SSL_LOGIN', true );
define( 'FORCE_SSL_ADMIN', true );
define( 'WP_DISABLE_FATAL_ERROR_HANDLER', false );

/* URL / Path */

/* Cookies */

/* Content */
define( 'AUTOSAVE_INTERVAL', 30 );
define( 'WP_POST_REVISIONS', 5 );
define( 'MEDIA_TRASH', true );
define( 'EMPTY_TRASH_DAYS', 7 );
define( 'WP_MAIL_INTERVAL', 86400 );

/* Memory */
define( 'WP_MEMORY_LIMIT', '128M' );
define( 'WP_MAX_MEMORY_LIMIT', '256M' );

/* Updating */
define( 'AUTOMATIC_UPDATER_DISABLED', true );
define( 'WP_AUTO_UPDATE_CORE', 'minor' );
define( 'CORE_UPGRADE_SKIP_NEW_BUNDLED', true );

/* File edition */
define( 'DISALLOW_FILE_MODS', false );
define( 'DISALLOW_FILE_EDIT', false );
define( 'IMAGE_EDIT_OVERWRITE', true );

/* Performance */
define( 'WP_CACHE', true );
define( 'WP_CACHE_KEY_SALT', 'aaaaaaaaaa:' );
define( 'COMPRESS_CSS', true );
define( 'COMPRESS_SCRIPTS', true );
define( 'CONCATENATE_SCRIPTS', false );
define( 'ENFORCE_GZIP', true );

/* Cron */
define( 'DISABLE_WP_CRON', false );
define( 'ALTERNATE_WP_CRON', false );
define( 'WP_CRON_LOCK_TIMEOUT', 60 );

/* FTP Access */

/* Plugins Must-Use */

/* Filtering */
define( 'DISALLOW_UNFILTERED_HTML', false );
define( 'ALLOW_UNFILTERED_UPLOADS', false );

/* Feed reader */
define( 'MAGPIE_CACHE_ON', true );
define( 'MAGPIE_CACHE_DIR', 'cache' );
define( 'MAGPIE_CACHE_AGE', 3600 );
define( 'MAGPIE_CACHE_FRESH_ONLY', false );
define( 'MAGPIE_DEBUG', false );
define( 'MAGPIE_USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0' );
define( 'MAGPIE_FETCH_TIME_OUT', 5 );
define( 'MAGPIE_USE_GZIP', true );

/* MultiSite */
define( 'WP_ALLOW_MULTISITE', false );
define( 'WP_DEFAULT_THEME', 'twentytwenty' );

/* External URL Requests */

/* File permissions */

/* Proxy */

/* Debug */
define( 'WP_DEBUG', true );
if ( WP_DEBUG ) {
  define( 'WP_DEBUG_DISPLAY', true );
  define( 'WP_DEBUG_LOG', false );
}
define( 'SCRIPT_DEBUG', false );
define( 'SAVEQUERIES', false );

/* Do not change anything else after this line! Thank you! */

if ( ! defined( 'ABSPATH' ) )
  define( 'ABSPATH', dirname( __FILE__ ) . '/' );
require_once ABSPATH . 'wp-settings.php';

Now that we have everything ready at the systems level to have a WordPress ready to show 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.

System maintenance

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

apt -y update && apt -y upgrade && apt -y dist-upgrade && apt -y autoremove
cd /webs/wordpress-svn/
svn up

And so far we have an installation from scratch of the main SVN of WordPress development, where we can analyze or verify our developments for future versions, or apply patches already developed by the community but pending verification.


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.