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