Ansible Guide: Create Ansible Playbook for LEMP Stack

Ansible is a simple automation tool that automates software application deployment, cloud provisioning, and configuration management. It's a server orchestration tool that helps you to manage and control a large number of server nodes from single places called 'Control Machines'. Ansible was created by Michael DeHaan in 2012 and is written in Python and Powershell.

In this tutorial, we're going to show you how to create basic Ansible Playbook for provisioning the LEMP Stack on Ubuntu 18.04 Server. You will learn how to create basic Ansible Playbook that can be scaled for other PHP project applications such as WordPress, Nextcloud etc.


  • 2 Ubuntu OS.
    •   ansible
    •   provision
  • Knowledge of  basic usage Ansible
  • Root privileges

What we will do:

  1. Setup Ansible Playbook Project
  2. Generate Ansible Playbook Roles Directory Structure
  3. Setup hosts and site.yml
  4. Setup 'common' Roles - Basic setup
  5. Setup 'web' Roles - Nginx and PHP-FPM Configuration
  6. Setup 'db' Roles - MySQL Database Configuration
  7. Testing

Step 1 - Setup Ansible Playbook Project

Ansible Playbook is a set of instructions that you send to run on a single or group of server hosts. It represents the ansible-provisioning, where the automation is defined as tasks, and all jobs like installing packages, editing files, will be done by ansible modules.

The Ansible Playbook contains some basic configuration, including hosts and user information of the provision servers, a task list that will be implemented to the provision servers, template and custom configurations, and a group of variables part of templates and tasks.

Firstly, create the master project directory on the 'ansible-control' machine. The master project directory will be stored all our playbook directories, files, and configurations.

Create the Ansible project directory called 'project-lemp' and go into it.

mkdir project-lemp/
cd project-lemp

Now create new configuration file 'hosts' and 'site.yml', then create a new directory called 'roles'.

touch hosts site.yml
mkdir -p roles/

Details about configurations:

hosts - It's an inventory file that contains pieces of information about managed servers by ansible. It allows you to create a group of servers that make you more easier to manage and scale the inventory file itself. The inventory file can be created with many different formats, including the INI and YAML formats.

site.yml - The master playbook file that contains which group of hosts that will be managed using our available roles.

roles - it's a group of Ansible playbooks that will be used to provision the server. The ansible roles have their own directory structures, each role will contain directories such as tasks, handlers, vars etc.

Step 2 - Generate Ansible Roles for the Directory Structure

In this step, we're going to generate ansible roles directory using the ansible-galaxy command. We will generate two of roles called 'common' roles and the 'web' roles.

Inside the 'project-lemp' directory, go to the directory 'roles'.

cd roles/

Generate roles structure directory and files for the 'common' and 'web' roles by running the ansible-galaxy command below.

ansible-galaxy init common
ansible-galaxy init web
ansible-galaxy init db

After that, check all available ansible roles directory structures using the following command.

tree .

You will be shown the result as below.

Directory structure

Step 3 - Setup hosts and site.yml

The 'hosts' file will contain list and group of the server managed by the Ansible. For this guide, we will create a group called 'lemp' with the member named 'server01' and the IP address

Edit the 'hosts' file using vim editor.

vim hosts

Paste configuration below.

server01 ansible_host=

Save and close.

Next, edit the site.yml configuration file.

vim site.yml

Paste configurations below.


- hosts: lemp
  remote_user: hakase
  become: yes

    - common
    - web
    - db

Save and close.

Setup hosts and site.yml

Step 3 - Setup Common Roles

In this step, we're going to set up the common roles. And in order to do that, we need to create a list of tasks that we're going to do.

Below the list of tasks that we're going to do on the 'common' roles.

  1. Change repository
  2. Update repository
  3. Upgrade packages to the latest version
  4. Setup the server timezone

Now go to the 'common' directory and edit the 'tasks/main.yml' configuration.

cd common/
vim tasks/main.yml

Create a task for changing the repository, and we will be using the 'copy' module that will copy the base 'sources.list' on the 'files' directory to the remote host '/etc/apt/'.

- name: Change repository Ubuntu 18.04Step 4 - Setup 'web' Roles
    src: sources.list
    dest: /etc/apt/
    backup: yes

Create a task for updating the repository and upgrade all packages to latest version using the 'apt' module.

- name: Update repository and Upgrade packages
    upgrade: dist
    update_cache: yes

Now create the task for configuring the system timezone using the ansible timezone module.

- name: Setup timezone to Asia/Jakarta
    name: Asia/Jakarta
    state: latest

Save and close.

Setup Common Roles

After that, create a new repository configuration 'sources.list' inside the 'files' directory.

vim files/sources.list

Choose the nearest repository of your server location, below is mine.

deb bionic main restricted
deb bionic-updates main restricted
deb bionic universe
deb bionic-updates universe
deb bionic multiverse
deb bionic-updates multiverse
deb bionic-backports main restricted universe multiverse
deb bionic-security main restricted
deb bionic-security universe
deb bionic-security multiverse

Save and close.

lastly, the 'common' roles configuration has been completed.

Step 4 - Setup 'web' Roles

In this step, we're going to set up the 'web' roles. It will do some tasks including install the Nginx web server, PHP-FPM with some basic extentions, and configuring the PHP-FPM with Nginx.

Below are details tasks that we will do on the 'web' roles:

  1. Install Nginx
  2. Install PHP-FPM
  3. Configure php.ini
  4. Create a virtual host
  5. Add file phpinfo

Goto the 'web' directory and edit the 'tasks/main.yml' file.

cd web/
vim tasks/main.yml

Create the first task for nginx installation using the apt module.

- name: Install Nginx
    name: nginx
    state: latest

Now create the task for installing PHP-FPM with some basic extensions. And for the multiple packages installation, we can use python 'list' format such as below.

- name: Instal PHP-FPM
    name: ['php','php-fpm','php-common','php-cli','php-curl']
    state: latest

Next, we will add new lines to the php.ini configuration using the 'blockinfile' module. And at the end of the line, we will notify the ansible to restart the php-fpm service after configuring the php.ini file.

- name: Configure php.ini
    dest: /etc/php/{{ php_version }}/fpm/php.ini
    block: |
      date.time = Asia/Jakarta
      cgi-fix_pathinfo = 0
    backup: yes
  notify: restart php-fpm

Now we will copy the nginx virtual host configuration using the 'template' module. The template module will copy the configuration from the 'templates' directory to the remote server. We're going to copy the jinja2 virtual host template 'vhost.j2' to the '/etc/nginx/sites-enabled/' directory, and the last we will notify the ansible to restart the nginx service.

- name: Create Nginx virtual host
    src: vhost.j2
    dest: /etc/nginx/sites-enabled/vhost-{{ domain_name }}
  notify: restart nginx

After that, we will create new tasks for creating the web-root directory using the 'file' module and copy the index.php template into it.

- name: Create web-root directory
    path: /var/www/{{ domain_name }}
    state: directory

- name: Upload index.html and info.php files
    src: index.php.j2
    dest: /var/www/{{ domain_name }}/index.php

Save and close.

Now we're going to configure the handlers for restarting the nginx and php-fpm service. Edit the 'handlers/main.yml' configuration using vim editor.

vim handlers/main.yml

Paste configurations below.

- name: restart nginx
    name: nginx
    state: restarted
    enabled: yes

- name: restart php-fpm
    name: php{{ php_version }}-fpm
    state: restarted
    enabled: yes

Save and close.

Ansible web roles

Next, we will edit the 'vars/main.yml' configuration. At the top of configurations you will notice the variable configurations '{{ php_version }}' and '{{ domain_name }}'. Those variables represent our environment setup for the php version and the domain name that will be used. The variable makes ansible more reusable because we just need to edit the variable configuration 'vars/main.yml' and not editing the base configuration.

Edit the variables configuration 'vars/main.yml' using vim editor.

vim vars/main.yml

Paste configurations below.

php_version: 7.2

Save and close.

Now we will create jinja2 template configurations 'index.php.j2' and 'vhost.j2' on the 'templates/' directory.

vim templates/index.php.j2

Paste configuration below.


<h1><center>index.html for domain {{ domain_name }}</center></h1>




Save and close.

After that, create the template for nginx virtual host configuration 'vhost.j2'.

vim templates/vhost.j2

Paste configurations below.

server {
    listen 80;
    listen [::]:80;

    root /var/www/{{ domain_name }};
    index index.php index.html index.htm index.nginx-debian.html;

    server_name {{ domain_name }};

    location / {
        try_files $uri $uri/ =404;

    # pass PHP scripts to FastCGI server
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php{{ php_version }}-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;


Save and close the configuration, and we're finished the web roles configuration.

Step 5 - Setup 'db' Roles

At this step, we're going to configure the 'db' roles for the MySQL database installation and configuration.

Below are details tasks that will do on the 'db' roles.

  1. install mysql
  2. Create MySQL database
  3. Create MySQL user
  4. restart mysql

Goto the 'db' directory and edit the 'tasks/main.yml' configuration.

cd db/
vim tasks/main.yml

Now install the MySQL packages using the 'apt' module and python 'list' format for multiple packages installation.

- name: Install MySQL
    name: ['mysql-server','mysql-client','python-mysqldb']
    state: latest
  notify: restart mysql

Then create new tasks for creating the MySQL database and user, then grant all privileges of the user to the database.

- name: Create database
    name: '{{ db_name }}'
    state: present

- name: Create user for the database
    name: '{{ db_user }}'
    password: '{{ db_pass }}'
    encrypted: yes
    priv: '{{ db_name }}.*:ALL'
    state: present

Save and close.

Setup database roles

Next, edit the 'handlers/main.yml' configuration.

vim handlers/main.yml

Paste the configuration of the task for restarting the MySQL service.

- name: restart mysql
    name: mysql
    state: restarted
    enabled: yes

Save and close.

After that, edit the vars variable configuration 'vars/main.yml'.

vim vars/main.yml

Paste these variables for MySQL database and user configuration below.

db_name: hakase-db
db_user: hakase
db_pass: '*C960D382DB42E57D3BAC33891CF87900DCB1A869'

Save and close.

The 'db_pass' variable has the MySQL encrypted password, and you can generate an encrypted MySQL password using online tools.

Step 6 - Run the Ansible Playbook

Goto the Ansible project directory.

cd project-lemp/

Run the ansible-playbook command below.

ansible-playbook -i hosts site.yml

Now the ansible will run all roles that we assign to the host. When it's complete, you will be shown the result as below.

Run the Ansible Playbook

Make sure you get no error.

Step 7 - Testing

Open your web browser and type the domain name on the address bar

And you will be shown the index page with phpinfo as below.


The PHP-FPM and Nginx are working.

Next, back to the server terminal and log in to the MySQL server using the user and password that we've created on the 'mysql' roles variable.

mysql -u hakase -p
PASSWORD: hakasepass

check the list of database owned by the user.

show databases;

And you will be logged to the MySQL shell and will be shown the database named 'hakase-db' on the list.

Database test

Finally, the Ansible Playbook for the LEMP Stack installation and configuration has been created and tested successfully.


Share this page:

2 Comment(s)

Add comment

Please register in our forum first to comment.


By: Michael

Good guide!

By: c

Thank you I am doing something like what you are doing it helped me out a lot