WordPress with HAProxy

Having several WordPress in subdomains, folders, and without using WordPress Multisite, can be a necessity that for some situation of the project is necessary. And to achieve this, we need HAProxy.

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

Objective to be achieved

Let’s pose a situation that could occur, either with several WordPress or with several different technologies.

On the one hand, we have on //example.com/ a website; for example, a corporate website.

On the other hand, we have in //example.com/shop/ a store.

And, on the other, we have //example.com/education/ a system of courses.

Yes, the simplest thing would be to create a WordPress Multisite with everything, but, for whatever reason, it is not possible, so you have to mount 3 simple, different WordPress on 3 different servers.

If the system were in 3 subdomains, it would be easy, since we would point the A entries of the DNS to the 3 different IPs. But in this case, this is not the case.

And this is where HAProxy comes in, the tool that will be ahead of our sites and that will be responsible for sending the traffic.

What we are going to create

For this experiment we are going to create the following:

  • A HAProxy that manages all the traffic, ahead.
  • A WordPress in the root folder (//example.com/).
  • A WordPress in a folder (//example.com/shop/).

Each of these parts we are going to mount in a different VPS, although in reality the WordPress could be, theoretically, in a shared hosting if you wanted, for example, of several agencies that manage them.

Installing WordPress

The first thing we will prepare are the two WordPress. Keep in mind that the domain to install it will not be pointing in the DNS (since it will point to the proxy machine), so we will have to install the 2 WordPress, one in the root and one in the folder, without web access.

On the other hand, although Let’s Encrypt certificates can be mounted, some validations are likely to fail.

In any case, you can follow the WordPress installation manuals for both cases.

Installing HAProxy

In this example we are going to use:

  • Ubuntu 22
  • HAProxy 2.6

Installing HAProxy

Not all operating systems are the most current version of HAProxy available, so we will upload it from an alternative repository.

add-apt-repository -y -s ppa:vbernat/haproxy-2.6

And we will do the installation.

apt-get -y install haproxy

We can validate that version 2.6.x is installed.

haproxy -v

We are going to treat it as a service, so that when starting the machine it always turns on and is available. To do this we need to activate the daemon.

cat > /etc/default/haproxy << EOF
#CONFIG="/etc/haproxy/haproxy.cfg"
#EXTRAOPTS="-de -m 16"
ENABLED=1
EOF

Now that we have everything ready, let’s activate it and leave it running.

systemctl stop haproxy
systemctl enable haproxy
systemctl start haproxy
systemctl status haproxy

To save the logs, we will activate the system.

cat > /etc/rsyslog.d/haproxy.conf << EOF
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
EOF
systemctl restart rsyslog

Temporary TLS certificates

Until we configure the definitive certificates, we would need to have temporary certificates to treat the system always as HTTPS.

So we will create our temporary certificate with OpenSSL.

openssl req -nodes -x509 -newkey rsa:2048 -keyout /etc/ssl/private/temporal.key -out /etc/ssl/private/temporal.crt -days 365 -subj "/C=ES/ST=Barcelona/L=Barcelona/O=IT/OU=IT/CN=example.com"

And when we have them, we will create the certificate that HAProxy will use.

cat /etc/ssl/private/temporal.key /etc/ssl/private/temporal.crt > /etc/ssl/private/temporal.pem

Configuring HAProxy

Foremost, we will make a backup of the configuration base file.

cd /etc/haproxy
cp haproxy.cfg haproxy.cfg-original

And we will make a first configuration to control the HTTP and be able to create the certificates with Let’s Encrypt.

cat > /etc/haproxy/haproxy.cfg << EOF
global
  log /dev/log local0
  log /dev/log local1 notice
  chroot /var/lib/haproxy
  stats socket /run/haproxy/admin.sock mode 660 level admin
  stats timeout 30s
  user haproxy
  group haproxy
  daemon
  # Default SSL material locations
  ca-base /etc/ssl/certs
  crt-base /etc/ssl/private
  # See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
  ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
  ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
  ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
  log global
  mode http
  option httplog
  option dontlognull
  timeout connect 5000
  timeout client  50000
  timeout server  50000
  errorfile 400 /etc/haproxy/errors/400.http
  errorfile 403 /etc/haproxy/errors/403.http
  errorfile 408 /etc/haproxy/errors/408.http
  errorfile 500 /etc/haproxy/errors/500.http
  errorfile 502 /etc/haproxy/errors/502.http
  errorfile 503 /etc/haproxy/errors/503.http
  errorfile 504 /etc/haproxy/errors/504.http
frontend wordpress-http
  bind 1.2.3.4:80
  acl letsencrypt-acl path_beg /.well-known/acme-challenge/
  use_backend letsencrypt-backend if letsencrypt-acl
  acl wordpress-shop-acl path_beg /shop/
  use_backend wordpress-shop if wordpress-shop-acl
  default_backend wordpress-www
backend letsencrypt-backend
  server certbot 127.0.0.1:8899
backend wordpress-www
  server wordpress-www 10.0.0.2:443 ssl verify none
backend wordpress-tienda
  server wordpress-tienda 10.0.0.3:443 ssl verify none
EOF

We will check that the code is valid.

haproxy -c -V -f /etc/haproxy/haproxy.cfg

And finally, we will restart the system to validate that everything is working.

systemctl stop haproxy
systemctl start haproxy
systemctl status haproxy

Some details to comment on this code…

frontend wordpress-http
  bind 1.2.3.4:80
  acl letsencrypt-acl path_beg /.well-known/acme-challenge/
  use_backend letsencrypt-backend if letsencrypt-acl
  acl wordpress-shop-acl path_beg /shop/
  use_backend wordpress-shop if wordpress-shop-acl
  default_backend wordpress-www

Here we see the configuration of the front (frontend). We can indicate the public IP (1.2.3.4) to which it has to respond, if the machine has several, or if we want it to respond to everything we can replace it with an asterisk (*).

In order for let’s Encrypt requests to be correctly responsive, we will create the system so that it is the machine itself that is responsible.

Subsequently, we indicate that the subsite “shop” sends the traffic to the store server, and that the subsite (or the rest of the traffic) goes to the main server “www”.

And here we will have the three backends:

backend letsencrypt-backend
  server certbot 127.0.0.1:8899

backend wordpress-www
  server wordpress-www 10.0.0.2:443 ssl verify none

backend wordpress-shop
  server wordpress-shop 10.0.0.3:443 ssl verify none

The first of them “letsencrypt-backend” will be used so that certificate requests are answered by Let’s Encrypt (through certbot).

The second of them, “wordpress-www”, will be the backend that responds to all the requests by default, and that corresponds to the WordPress that is in the root folder. In this case, we already indicate the IP address, which goes by HTTPS and that, to avoid internal problems, do not verify the private certificate that the sites may have. In this way, the connection will be encrypted, with a valid or self-signed certificate.

The third of them “wordpress-shop”, will correspond to the WordPress that is in the backend of the store. The operation is the same as in the previous case.

Generating the Let’s Encrypt certificate

As at any other time, we will make the call to the generation, and in this case, the validation will be done by the system that we have configured in the HAPRoxy.

certbot certonly --standalone -d example.com --non-interactive --http-01-port=8899

Once the files have been created, we will do as before and create one for the HAPRoxy to use.

cat /etc/letsencrypt/live/example.com/fullchain.pem /etc/letsencrypt/live/example.com/privkey.pem > /etc/ssl/example.com.pem

Final configuration of HAProxy

Now that we have the certificate and the different sites working, we will make the last adjustments in the configuration file.

vim /etc/haproxy/haproxy.cfg

There we will replace and add some blocks:

frontend wordpress-http
  bind 1.2.3.4:80
  acl letsencrypt-acl path_beg /.well-known/acme-challenge/
  use_backend letsencrypt-backend if letsencrypt-acl
  redirect scheme https

frontend wordpress-https
  bind 1.2.3.4:443 ssl crt /etc/ssl/example.pem
  acl wordpress-shop-acl path_beg /shop/
  use_backend wordpress-shop if wordpress-shop-acl
  default_backend wordpress-www

backend letsencrypt-backend
  server certbot 127.0.0.1:8899

backend wordpress-www
  server wordpress-www 10.0.0.2:443 ssl verify none

backend wordpress-tienda
  server wordpress-tienda 10.0.0.3:443 ssl verify none

To the frontend that responds to the HTTP port (80), we will continue to give you the data to validate the certificate, but we will tell you that the rest of the traffic is sent to HTTPS.

We will create a new frontend that is solely responsible for HTTPS (443). In this case, we will tell you where the certificate we have just created is, and we will give you the instructions to send the traffic to the corresponding backends.

And finally, we will keep the two backends that we already had previously.

Now, once again, we will validate the configuration file and restart the HAProxy, to leave it running.

haproxy -c -V -f /etc/haproxy/haproxy.cfg

systemctl stop haproxy
systemctl start haproxy
systemctl status haproxy

Testing sites

Once we have the HAProxy, it means that we can already access our different WordPress.

If we enter //example.com/shop/ we can access one of them, and if we access we //example.com/ will have access to the other.

A simple way to check that they are really two independent WordPress is to put each of them a different theme.


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.

WordPress System Administration Services

Do you have a high traffic WordPress website? Are you an Agency with servers with cPanel, Plesk or other panel on which you maintain WordPress for your clients?

If so, and you are interested in a professional WordPress infrastructure maintenance and performance improvement service for your or your clients’ websites, contact me.