/ #wordpress #haproxy 

DIY 5$: deploy Wordpress + free SSL with letsencrypt and haproxy

If you are starting your new business or want to have your own blog, hosting your website is something are looking for. But you are asking yourself: “What is a cheap and good way to host my website?”.

Well, I have asked myself the same question and I got the answer. I had already an Orange pi, so I have decided to use it and deploy my website on it. It has been working since 5 months without any trouble or disfunction.

Therefore, I have decided to share this with you guys. We will go step by step:

  • 1- Rent a server
  • 2- Install the tools
  • 3- Deploy wordpress
  • 4- Buy a domain name
  • 5- Deploy HAproxy + create certificate.
  • 6- Optional : Please subscribe to my youtube channel if you like this tutorial to get all the new updates:)

Step 1 : Having a server

Personnally, I deploy it on my Orange pi.

Buy it from Aliexpress: Orange pi

Step 2 : Install the tools

The only 2 tools necessary for this tutorial are :

  • Docker
  • Docker swarm

I have used these commands to provision my ubuntu machine:

#!/bin/bash
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
docker swarm init

Step 3 : Deploy Wordpress

In this tutorial, we will deploy wordpress using docker swarm because it is easy to start using it. Also, you will notice that wordpress and haproxy will be deployed using different swarm stacks.

So that, we should create a network before deploying the stacks to connect them easily.

$ docker network create -d overlay --attachable proxy

Now copy this file to myproject/wordpress/docker-compose.yml.

version: "3.8"

services:
  wordpress:
    image: wordpress
    restart: always
    container_name: wordpress
    # You can uncomment these ligns to test the wordpress without haproxy
    # ports:
    #   - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html
    networks:
      - proxy

  db:
    image: mysql:5.7
    restart: always
    container_name: mydb
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - db:/var/lib/mysql
    networks:
      - proxy

volumes:
  wordpress:
    name: "wordpress"
  db:
    name: db

networks:
  proxy:
    external: true
    name: proxy

and run the docker swarm stack.

docker stack deploy -c docker-compose.yml wordpress

Step 4 : Buy a domain name

Buy the domain name that you want. In general, I use https://namecheap.com. After buying it, add an A record with the IP address of your server to your domain name.

Step 5 : Haproxy configuration + Letsencrypt certificate

In a previous post, I have described how to use letsencrypt with haproxy.

Let’s adapt the configuration used before to the wordpress server. In this part, we will:

  • Create the haproxy configuration
  • Create a dummy certificates necessary for haproxy
  • Run haproxy
  • Create a real certificate for the domain name
  • Add a certificate renewal for the domain name

HAProxy configuration

Until now, we have deployed a wordpress with mysql server only. Let’s create the haproxy configuration that will redirect all HTTP to HTTPS and will pass the request to wordpress.

myproject
|--proxy
    |--haproxy
        |-- haproxy.cfg

On your root project folder, create a folder tree called proxy/haproxy. Add the file haproxy.cfg to the folder haproxy.

global
  log stdout format raw local0
  daemon

  # Default ciphers to use on SSL-enabled listening sockets.
  # For more information, see ciphers(1SSL).
  ssl-default-bind-ciphers kEECDH+aRSA+AES:kRSA+AES:+AES256:RC4-SHA:!kEDH:!LOW:!EXP:!MD5:!aNULL:!eNULL

resolvers docker_resolver
    nameserver dns 127.0.0.11:53

defaults
  log     global
  mode    http
  option  httplog
  option  dontlognull

frontend http
    bind *:80
    mode http

	# if this is an ACME request to proof the domain ownder, then redirect to nginx-certbot server
    acl is_well_known path_beg -i /.well-known/

	# else redirect the traffic to https
    redirect scheme https code 301 if !is_well_known !{ ssl_fc }
    use_backend letsencrypt if is_well_known

backend letsencrypt
    server letsencrypt nginx-certbot:80 resolvers docker_resolver check init-addr none

frontend https
    bind *:443 ssl crt /usr/local/etc/certs/
    http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"

    acl wordpress  hdr(host) -i YOUR_DOMAINNAME
    use_backend wordpress if wordpress

    default_backend wordpress

backend wordpress
    server wordpress wordpress:80
    http-request add-header X-Forwarded-Proto https if { ssl_fc }

The above configuration will:

  • Redirects HTTP to HTTPS then redirect to our application by default
  • Handle letsencrypt HTTP requests for verifying the domain owner. These requests starts by /.well-known/ always and target a specific file that should be served in HTTP. That’s why we will use nginx to serve the target folder.
  • HTTPS requests will be secured using the certificates in /usr/local/etc/certs/. At least one certificate should be present. This is why it is important to create a dummy certificate before running haproxy. Otherwise, if the folder /usr/local/etc/certs/ is empty, the haproxy will show errors in log.

Create a dummy certificate

Create the folder certs at the root of your project.

myproject
|--proxy
    |--certs
...

Create a self signed certificate using openssl.

$ sudo apt-get install openssl
$ openssl req -nodes -x509 -newkey rsa:2048 -keyout test.key -out test.crt -days 30
$ cat test.key test.crt > ./certs/test.pem

Run HAproxy

Create the folder webroot at the root of your project. This is the folder where Letsencrypt will request the file to verify that you are the owner of the domain.

myproject
|--proxy
    |-- certs
    |-- haproxy
    |-- webroot
    |-- docker-compose.yml
...

Now that the configurations and all the necessary folders are ready. Create the docker-compose.yml file on the root project directory.

version: "3.5"

services:
  proxy:
    image: haproxy:latest
    restart: always
    volumes:
      - ./haproxy:/usr/local/etc/haproxy:ro
      - ./certs:/usr/local/etc/certs:ro
    ports:
      - 80:80
      - 443:443

  nginx-certbot:
    image: nginx
    restart: always
    container_name: nginx-certbot
    volumes:
      - ./webroot:/usr/share/nginx/html

Run the haproxy and the nginx.

$ docker-compose up -d

If you are enjoying this tutorial, feel free to leave a comment. It means me a lot to know that you are appreciating my work :)

Create the certificate using certbot

Certbot is the letsencrypt official tool for creating a signed certificate. By default, a production certificate is delivered. For test case, to use the option --staging to avoid Letsencrypt rate limits.

Letsencrypt rate limits: 5 certificates per week. Official documentation

Create the script create-cert.sh at your root project:

#!/bin/bash

set -e

echo "Starting create new certificate..."
if [ "$#" -lt 2 ]; then
    echo "Usage: ...  <domain> <email> [options]"
    exit
fi

DOMAIN=$1
EMAIL=$2
OPTIONS=$3

docker run --rm \
  -v $PWD/letsencrypt:/etc/letsencrypt \
  -v $PWD/webroot:/webroot \
  certbot/certbot \
  certonly --webroot -w /webroot \
  -d $DOMAIN \
  --email $EMAIL \
  --non-interactive \
  --agree-tos \
  $3

# Merge private key and full chain in one file and add them to haproxy certs folder
function cat-cert() {
  dir="./letsencrypt/live/$1"
  cat "$dir/privkey.pem" "$dir/fullchain.pem" > "./certs/$1.pem"
}

# Run merge certificate for the requested domain name
cat-cert $DOMAIN

Explanation :

  • This script will take 3 arguments : domain name, email and options.
  • Webroot option : tell the certbot to create the file for domain verification in the path /webroot
  • Options : this is very important to add any additional option for certbot, example: --staging.

Run the script. Use –staging for test purposes. If your staging certificate is working, you can remove this option and create a production certificate later.

./create-cert yourdomain.com youremail.com  --staging

If you have created successfully your certificate, don’t forget to remove the dummy test certificate created certs/test.pem.

Renew certificate

Letsencrypt certificates are valid only for 90 days. Therefore, it should be renewed always. I have done a script to renews all my certificates each month. Create this script at the project proxy folder and call it renew-certs.sh:

#!/bin/bash

set -e

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd $DIR

echo "$(date) About to renew certificates" >> /var/log/letsencrypt-renew.log
/usr/bin/docker run \
       -i \
       --rm \
       --name certbot \
       -v $PWD/letsencrypt:/etc/letsencrypt \
       -v $PWD/webroot:/webroot \
       certbot/certbot \
       renew -w /webroot

echo "$(date) Cat certificates" >> /var/log/letsencrypt-renew.log

function cat-cert() {
  dir="./letsencrypt/live/$1"
  cat "$dir/privkey.pem" "$dir/fullchain.pem" > "./certs/$1.pem"
}

for dir in ./letsencrypt/live/*; do
  if [[ "$dir" != *"README" ]]; then
    cat-cert $(basename "$dir")
  fi
done

echo "$(date) Reload haproxy" >> /var/log/letsencrypt-renew.log
docker service update --force proxy_proxy

echo "$(date) Done" >> /var/log/letsencrypt-renew.log

Create a crontask to run this renew script every month:

$ echo "0 0 1 * * your_project_path/renew-certs.sh" >> /etc/crontab

Source code

I know that it may be difficult to copy and create all the files using copy paste. Therefore, I have create a github repository to share this code with you.

My Github repository

I hope you have enjoyed this tutorial. If you have any ideas for a new project/tutorial you want to see, don’t hesitate to write it in the comment section below.

Also, I have launched my youtube channel, please subscribe to get all my new updates