WordPress with LAMP Stack

👇🏾 Click on the button to access the code repository

This Playbook will install a WordPress Content Management System (CMS) within a LAMP environment (Linux, Apache, MySQL, and PHP) on two remote servers in a private network. The LAMP versioning highlights the following for each layer:

  • Linux - Ubuntu 18.04 ( 1 Virtual machine designated as the master node and two managed nodes for hosting the WordPress CMS). Vagrant and VirtualBox create these machines.
  • Apache2 - The Apache HTTP server is the most widely-used web server in the world. It provides many powerful features, including dynamically loadable modules, robust media support, and extensive integration with other popular software.
  • MySQL 5.7 - MySQL is the world’s most popular open-source relational database management system.
  • PHP 7.4 - PHP is a popular general-purpose scripting language that is especially suited to web development.

Create Vagrant private network

Creating a robust sandbox environment for rapid prototyping eliminates the risk of crashing or breaking other functions when using servers or your local machine when purposed for other vital tasks. We’ll create a private network that you can install on any local device (i.e., laptop) as long as VirtualBox is available via Vagrant. The vagrantfile seen below can build test virtual machines (VM’s) for our Ansible playbook testing environment. If you prefer using a public cloud such as AWS, Azure, GCP, or Digital Ocean, the logical design is easy to follow.

# Vagrantfile #

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.

Vagrant.configure("2") do |config|
  config.vm.define "ansible-master" do |vm1|
    vm1.vm.box = "bento/ubuntu-18.04"
    vm1.vm.hostname = "ansible-master"
    vm1.vm.network "private_network", ip: ""

    config.vm.provider "virtualbox" do |vb|
	  vb.gui = false
	  vb.memory = "4096"
      vb.cpus = "2"
	  vb.customize ['modifyvm', :id, '--cableconnected1', 'on']
	config.vm.provision "shell", run: "always", inline: <<-SHELL
		echo "Welcome to the Ubuntu Ansible network."

  config.vm.define "ansible-node1" do |vm2|
    vm2.vm.box = "bento/ubuntu-18.04"
    vm2.vm.hostname = "ansible-node1"
    vm2.vm.network "private_network", ip: ""

    config.vm.provider "virtualbox" do |vb|
	  vb.gui = false
	  vb.memory = "2048"
      vb.cpus = "2"
	  vb.customize ['modifyvm', :id, '--cableconnected1', 'on']
	config.vm.provision "shell", run: "always", inline: <<-SHELL
		echo "Welcome to the Ubuntu Ansible network."
  config.vm.define "ansible-node2" do |vm3|
    vm3.vm.box = "bento/ubuntu-18.04"
    vm3.vm.hostname = "ansible-node2"
    vm3.vm.network "private_network", ip: ""

    config.vm.provider "virtualbox" do |vb|
	  vb.gui = false
	  vb.memory = "2048"
      vb.cpus = "2"
	  vb.customize ['modifyvm', :id, '--cableconnected1', 'on']
	config.vm.provision "shell", run: "always", inline: <<-SHELL
		echo "Welcome to the Ubuntu Ansible network."

Configure the Master node and SSH connection

To begin using Ansible to manage your server infrastructure, you need to install the Ansible software on the machine that will serve as the Ansible master node. First, connect via SSH to the virtual machine.

PS C:\\..\\vagrant\\ubuntu_ansible> vagrant ssh ansible-master

By default, Vagrant will be the user on the machine; however, an admin account with sudo privileges creates the specific purpose of using Ansible to talk to the other virtual machines in the network. Use the adduser command as the root user first to add a new user to your system:

There will be a prompt to enter a password of your choice.

vagrant@ansible-master:~$ sudo -i
root@ansible-master:~# adduser admin

Use the usermod command to add the user to the sudo group and test if the sudo commands work when logged into the user account.

root@ansible-master:~# usermod -aG sudo admin
root@ansible-master:~# su - admin

admin@ansible-master:~$ sudo apt-get update

Run the following command to include the official project’s PPA (personal package archive) in your system’s list of sources:

admin@ansible-master:~$ sudo apt-add-repository ppa:ansible/ansible

Press ENTER when prompted to accept the PPA addition.

Next, refresh your system’s package index to be aware of the packages available in the newly included PPA and then proceed to install.

admin@ansible-master:~$ sudo apt-get update
admin@ansible-master:~$ sudo apt install ansible

Configure password-less authentication

We will set up password-less authentication for our admin user from master to all the managed nodes by generating a public-private key pair using ssh-keygen. We have pre-defined a blank password using -P “” This step will create private and public key pair located in the ~/.ssh directory.

admin@ansible-master:~$ ls -al ~/.ssh/
total 20
drwx------ 2 admin admin 4096 Apr  3 08:03 .
drwxr-xr-x 6 admin admin 4096 Apr  4 22:57 ..
-rw------- 1 admin admin 1675 Apr  3 07:49 id_rsa
-rw-r--r-- 1 admin admin  402 Apr  3 07:49 id_rsa.pub
-rw-r--r-- 1 admin admin  444 Apr  3 07:48 known_hosts

We will use ssh-copy-id to copy the keys to the remote managed server and add it to authorized_keys.

admin@ansible-master:~$ ssh-copy-id -i ~/.ssh/id_rsa.pub vagrant@
admin@ansible-master:~$ ssh-copy-id -i ~/.ssh/id_rsa.pub vagrant@

SSH and Admin user setup with Setup playbook

The setup_ubuntu1804 folder in the Github repository runs an independent playbook that will execute an initial server setup for the managed nodes. The options stores in the vars/default.yml variable file. We define the following setting below:

  • create_user: The name of the remote sudo user to create. In our case, it will be admin.
  • copy_local_key: Path to a local SSH public key that will be copied as an authorized key for the new user. By default, it copies the key from the current system user running Ansible.
  • sys_packages: An array with a list of fundamental packages to be installed.

Run the playbook with the following commands.

admin@ansible-master:~/.../setup_ubuntu1804$ ansible-playbook -i inventory -u admin playbook.yml

Once the Playbook completes as successful, you can test the SSH connection with the following Ansible commands for our inventory with ansible all -i inventory -m ping and ansible-inventory -i inventory --list.

admin@ansible-master:~/.../setup_ubuntu1804$ ansible all -i inventory -m ping
ansible-node2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
ansible-node1 | SUCCESS => {
    "changed": false,
    "ping": "pong"

admin@ansible-master:~/.../wordpress-lamp_ubuntu1804$ ansible-inventory -i inventory --list
    "_meta": {
        "hostvars": {
            "ansible-node1": {
                "ansible_host": "",
                "ansible_python_interpreter": "/usr/bin/python3"
            "ansible-node2": {
                "ansible_host": "",
                "ansible_python_interpreter": "/usr/bin/python3"
    "all": {
        "children": [
    "servers": {
        "hosts": [

Run the WordPress LAMP Playbook

Navigate to the wordpress-lamp_ubuntu1804 folder and use the tree command to see the following structure:

admin@ansible-master:~/wordpress-ansible/wordpress-lamp_ubuntu1804$ tree
├── files
│   ├── apache.conf.j2
│   ├── client.my.cnf
│   ├── client.my.cnf.j2
│   └── wp-config.php.j2
├── inventory
├── playbook.yml
├── readme.md
└── vars
    └── default.yml

Here is the following description of what each of these files is:

  • files/apache.conf.j2: Template file for setting up the Apache VirtualHost.
  • files/wp-config.php.j2: Template file for setting up WordPress’s configuration file.
  • files/client.my.cnf: The initial client.my.cnf is provided without a password and used to obtain a connection from which roots password updates to the managed nodes we want to store for MySQL database connection.
  • files/client.my.cnf.j2: Contains the same structure as the initial client.my.cnf file but as a jinja2 Template file for better portability.
  • inventory: Keeps track of which nodes and hosts will be a part of the infrastructure on which playbooks and ad-hoc commands will run.
  • vars/default.yml: Variable file for customizing playbook settings.
  • playbook.yml: The playbook.yml file is where all tasks from this setup are defined. It starts by defining the group of servers that should target this setup (all). It uses become: true to describe that tasks should be executed with privilege escalation (sudo) by default. Then, it includes the vars/default.yml variable file to load configuration options.

Here are the contents of each of the files respectively that should be edited:




password={{ mysql_password }}


ansible-node1 ansible_host=
ansible-node2 ansible_host=



#System Settings
php_modules: [ 'php7.4-curl', 'php7.4-cli', 'php7.4-dev', 'php7.4-gd', 'php7.4-mbstring', 'php7.4-mcrypt', 'php7.4-json', 'php7.4-tidy', 'php7.4-opcache', 'php
7.4-xml', 'php7.4-xmlrpc', 'php7.4-pdo', 'php7.4-soap', 'php7.4-intl', 'php7.4-zip' ]

#MySQL Settings
mysql_root_password: "Passw0rd"
mysql_db: "Wordpress_db"
mysql_user: "db_user"
mysql_password: "admin123!"

#HTTP Settings
http_host: "your_domain"
http_conf: "your_domain.conf"
http_port: "80"

The following list contains a brief explanation of each of these variables if they should edit or change for any reason.

  • php_modules: An array containing PHP 7.4 extensions that support your WordPress setup. This list is extensive to support more features.
  • mysql_root_password: The desired password for the root MySQL account.
  • mysql_db: The name of the MySQL database intended for WordPress.
  • mysql_user: The name of the MySQL user for WordPress.
  • mysql_password: The password for the new MySQL user.
  • http_host: Your domain name.
  • http_conf: The name of the configuration file within Apache.
  • http_port: HTTP port for the virtual host. By default, it is 80.

Once you’ve verified that the variables are correct, you can run the Playbook to install WordPress on the managed node(s) with the ansible-playbook command below:

admin@ansible-master:~/.../wordpress-lamp_ubuntu1804$ ansible-playbook -i inventory -u admin playbook.yml

When the Playbook finishes all of its tasks, you should see a Play recap of all the events. “OK” means the task is done and configured correctly from the last run, or “changed” if Ansible finds the task alters the current state configured on the managed node(s).

PLAY RECAP ****************************************************************************************************************************************************
ansible-node1              : ok=28   changed=10   unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
ansible-node2              : ok=28   changed=11   unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

To see if WordPress is up and running, navigate to the managed node’s domain name or IP address. In our case, it would be the following:

[<>](<>) →

[<>](<>) →


After selecting the language you’d like to use for your WordPress installation, you’ll be presented with a final step to set up your WordPress user and password so you can log into your control panel. Setting up the name of your site, email, and login credentials is straightforward at this stage.


Once you log in, you will be taken to the WordPress administration dashboard:


Some common next steps for customizing your WordPress installation include choosing the permalinks setting for your posts (can be found in Settings > Permalinks) and selecting a new theme (in Appearance > Themes). Below is a snapshot of our “Hello World!” blog post.


comments powered by Disqus