Lemp Stack

Set Up WordPress Website with Ansible

Introduction

In this post I explained the process of setting up a WordPress website on an Ubuntu 20.04 LTS server, Nginx, MySQL, and PHP. This LEMP stack project served as an excellent opportunity to apply and further develop my DevOps and system administration skills.

Step 1: Setting Up Your Ansible Environment

To get started, I needed to build an Ansible environment. This involved creating a directory and the necessary configuration files:

Ansible-Wordpress/
├── ansible.cfg
├── buildwordpress.yml
├── inventory.yml
├── roles
│   ├── database
│   │   └── tasks
│   │   └── mysql.yml
│   ├── reverseproxy
│   │   ├── files
│   │   │   └── wordpress
│   │   └── tasks
│   │   └── nginx.yml
│   └── wordpress
│   └── tasks
│   └── wordpress.yml
└── vars.yml

This directory represented the entire environment for deploying my site.

Step 2: How does this Playbook work?

ansible.cfg: This file was used to configure Ansible settings and preferences for my project. It looked like this:

[defaults]
private_key_file = /to/ssh/keyfile
remote_user = aws_user
INVENTORY = inventory.yml

inventory.yml: This file defined my Ansible inventory, specifying the hosts that Ansible would manage. I used this command to check if my Ansible could connect to the remote server:

ansible wordpress -m ping

The output:

| SUCCESS => {

	"ansible_facts": {

	"discovered_interpreter_python": "/usr/bin/python3"

	},

	"changed": false,

	"ping": "pong"

	}

buildwordpress.yml: This was my Ansible playbook where I defined tasks and roles to deploy WordPress.

roles: This directory was where I organized my Ansible roles. Ansible roles were reusable units of tasks that could be applied to multiple hosts.

     database: Inside the “roles” directory, there was a “database” role that contained tasks related to setting up and configuring the MySQL database for my WordPress site.

         mysql.yml: This YAML file contained Ansible tasks for configuring MySQL.

     reverseproxy: This role dealed with configuring Nginx as a reverse proxy for my site.

         wordpress: This file contained Nginx configuration.

         nginx.yml: This YAML file contained Ansible tasks for configuring Nginx.

     wordpress: This role was responsible for tasks related to setting up and configuring WordPress itself.

         wordpress.yml: This YAML file was where I defined Ansible tasks for installing and configuring WordPress.

vars.yml: This file contained Ansible variables that I used in the playbooks or roles to customize configurations based on different conditions or environments.

Step 3: Ansible Playbook for WordPress

Encrypting Variables: To secure sensitive data, I began by encrypting the vars.yml file using Ansible Vault’s AES256 encryption:

ansible-vault encrypt vars.yml

This step ensured that confidential information remained protected.

Running the Playbook: To execute the playbook, I used the –ask-vault-pass flag, which prompted me to enter the Ansible Vault password:

ansible-playbook buildwordpress.yml --ask-vault-pass

This password was essential for decrypting the variables during playbook execution.

Automating WordPress Deployment: With sensitive data secured, I created an Ansible playbook to automate the installation and configuration of WordPress. Here were the key tasks and templates:

Installed MySQL:

     Ensured that MySQL was up-to-date and running.

     Installed MySQL client packages.

     Created the WordPress database.

     Created a MySQL user with appropriate privileges.

- name: install mysql
  apt:
    package:
      - mysql-server
      - python3-pymysql
      - python3
      - python3-pip
      - libmysqlclient-dev
      - python3-dev
      - pkg-config
    state: latest
    update_cache: true
  become: yes

- name: install mysqlclient
  pip:
    name:
      - mysqlclient

- name: ensure mysql service is running
  service: 
    name: mysql
    state: started

- name: create database for wordpress
  community.mysql.mysql_db:
    name: "{{db_name}}"
    state: present
    collation: utf8_unicode_ci
    login_unix_socket: /run/mysqld/mysqld.sock

- name: create MySQL User
  mysql_user:
    name: "{{db_username}}"
    host: localhost
    password: "{{db_password}}"
    login_unix_socket: /run/mysqld/mysqld.sock

- name: crant all privileges to MySQL user
  mysql_user:
    name: "{{db_username}}"
    host: localhost
    priv: '{{db_name}}.*:ALL'
    append_privs: yes
    login_unix_socket: /run/mysqld/mysqld.sock

Configured Nginx:

     Installed Nginx and ensuring it was the latest version.

     Removed the default Nginx site configuration.

     Replaced it with a custom WordPress configuration.

     Created a symbolic link and enabled the WordPress site.

     Restarted NGINX for the changes to take effect.

- name: download Nginx
  apt:
    name: nginx
    state: latest
    update_cache: yes
  register: nginx_success

- name: delete default nginx site
  file:
    path: /etc/nginx/sites-available/default
    state: absent
        
- name: replace the nginx.default file with wordpress file template
  template:
    src: roles/reverseproxy/files/wordpress
    dest: /etc/nginx/sites-available/

- name: create symlink and enable the wordpress configuration
  file:
    src: /etc/nginx/sites-available/wordpress
    dest: /etc/nginx/sites-enabled/default
    state: link

- name: restart NGINX
  service:
    name: nginx
    state: restarted

Installed PHP and Configured WordPress:

     Installed additional PHP extensions required for WordPress.

     Removed the default Nginx welcome page.

     Downloaded the latest WordPress version.

     Copied the sample configuration to create a custom wp-config.php.

     Assigned proper ownership to the www-data user and group.

     Replaced database credentials in the wp-config.php file.

     Obtained WordPress Salt keys for enhanced security.

     Updated the wp-config.php file with the Salt keys.

- name: installing Additional PHP Extensions
  apt:
    package:
      - php-curl
      - php-gd
      - php-intl 
      - php-mbstring
      - php-soap
      - php-xml
      - php-xmlrpc
      - php-zip
      - php7.4-fpm
      - php-mysql
    state: latest
    update_cache: true

- name: clear /var/www/html
  file:
    path: /var/www/html/index.nginx-debian.html
    state: absent

- name: downloading WordPress
  unarchive:
    src: https://wordpress.org/latest.tar.gz
    dest: /var/www/html/
    remote_src: true
    validate_certs: yes

- name: copy /wordpress/wp-config-sample.php to /wordpress/wp-config.php
  copy:
    src: /var/www/html/wordpress/wp-config-sample.php 
    dest: /var/www/html/wordpress/wp-config.php
    remote_src: yes

- name: assign ownership to the www-data user and group
  file:
    path: /var/www/html/wordpress
    owner: www-data
    group: www-data
    recurse: yes  

- name: replace DB_NAME in wp-config.php
  ansible.builtin.lineinfile:
    path: /var/www/html/wordpress/wp-config.php  
    regexp: 'define\(\s*''DB_NAME'','
    line: "define('DB_NAME', '{{db_name}}');"

- name: replace DB_USER in wp-config.php
  ansible.builtin.lineinfile:
    path: /var/www/html/wordpress/wp-config.php  
    regexp: 'define\(\s*''DB_USER'','
    line: "define('DB_USER', '{{db_username}}');"

- name: replace DB_PASSWORD in wp-config.php
  ansible.builtin.lineinfile:
    path: /var/www/html/wordpress/wp-config.php  
    regexp: 'define\(\s*''DB_PASSWORD'','
    line: "define('DB_PASSWORD', '{{db_password}}');"

- name: get WordPress Salt keys
  ansible.builtin.get_url:
    url: https://api.wordpress.org/secret-key/1.1/salt/
    dest: /home/ubuntu/saltkey.txt

- name: read the contents of saltkey.txt
  command: cat saltkey.txt chdir=/home/ubuntu
  register: saltkey_contents

- name: replace Authentication Keys and Salts
  replace:
    path: /var/www/html/wordpress/wp-config.php
    regexp: '\/\*\*#@(\n|.)*?\/\*\*#@-\*\/'
    replace: "{{saltkey_contents.stdout}}"

Roles: To make maintenance easier, I organized the tasks into separate roles: database, reverse proxy, and WordPress.

---
- name: Setting up WordPress web server
  become: yes
  hosts: wordpress
  become_method: sudo

  tasks:
  - include_vars: vars.yml

  - include_role:
      name: database
      tasks_from: mysql

  - include_role:
      name: reverseproxy
      tasks_from: nginx

  - include_role:
      name: wordpress
      tasks_from: wordpress
...

Conclusion

In conclusion, hosting a website from start to finish was a great experience. It helped me understanding of web hosting, security practices, and automation tools. As a junior, I was constantly in learning mode, and this project served as a good learning experience. I was aware that mistakes could happen along the way, and I was open to feedback, so feel free to contact me via my email or LinkedIn. Thank you so much for your time.

Additional Resources

How to Install WordPress with LEMP on Ubuntu 20.04 – DigitalOcean

How to Get Fully Automated Let’s Encrypt SSL Renewal – Tony Teaches Tech