Operations 18 min read

Automate LNMP Deployment with Ansible Playbooks: Step‑by‑Step Guide

This tutorial walks you through installing Ansible, preparing inventory, creating reusable variables, and writing a comprehensive playbook that automates the deployment and configuration of Nginx, MySQL, and PHP on multiple CentOS 8 hosts, complete with service setup and firewall adjustments.

Raymond Ops
Raymond Ops
Raymond Ops
Automate LNMP Deployment with Ansible Playbooks: Step‑by‑Step Guide

Installation of Ansible

Configure the Alibaba Cloud repository, remove existing repo files, and add the official Alibaba mirror:

# cd /etc/yum.repos.d
# rm -rf *
# curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/CentOS-vault-8.5.2111.repo
# sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo
# yum install -y https://mirrors.aliyun.com/epel/epel-release-latest-8.noarch.rpm
# sed -i 's|^#baseurl=https://download.example/pub|baseurl=https://mirrors.aliyun.com|' /etc/yum.repos.d/epel/*
# sed -i 's|^metalink|#metalink|' /etc/yum.repos.d/epel/*
# ansible --version

Result shows Ansible 2.9.27 and Python 3.6.8.

Basic Preparation with Ansible

Create a project directory, copy the default ansible.cfg, and define the inventory:

# mkdir playdemo && cd playdemo
# cp /etc/ansible/ansible.cfg .
# vim /etc/hosts   # add entries for nginx, mysql, php hosts
# cat /etc/hosts
127.0.0.1   localhost
192.168.222.137 nginx
192.168.222.138 mysql
192.168.222.139 php
# mkdir playbook && cd playbook
# vim lnmp.yml   # create the playbook file

Generate SSH keys and copy them to the managed hosts for password‑less login, then verify connectivity:

# ssh-keygen -t rsa
# ssh-copy-id 192.168.222.137
# ssh-copy-id 192.168.222.138
# ssh-copy-id 192.168.222.139
# ansible all --list-hosts -m ping

Disable the firewall and SELinux on the control host:

# systemctl stop firewalld.service
# setenforce 0
# sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

Write Playbook

The lnmp.yml playbook uses variables from dir.yml, hos.yml, and package.yml to download, unpack, compile, and configure each component.

---
- name: Deploy LNMP stack
  hosts: all
  tasks:
  - name: Stop firewalld
    service:
      name: firewalld.service
      state: stopped
      enabled: no
  - name: Disable SELinux
    lineinfile:
      path: /etc/selinux/config
      regexp: '^SELINUX='
      line: SELINUX=disabled

  # Nginx section
  - name: Install Nginx
    vars_files:
      - ../var/dir.yml
      - ../var/hos.yml
      - ../var/package.yml
    hosts: "{{ host_ip['nginx'] }}"
    tasks:
      - name: Create nginx user
        user:
          name: nginx
          system: yes
          shell: /sbin/nologin
          create_home: no
          state: present
      - name: Download Nginx source
        get_url:
          url: "{{ url_dir }}{{ package['nginx'] }}.tar.gz"
          dest: "{{ dow_dir }}"
      - name: Unarchive Nginx
        unarchive:
          src: "{{ dow_dir }}{{ package['nginx'] }}.tar.gz"
          dest: "{{ una_dir }}"
          remote_src: yes
      - name: Install build dependencies
        yum:
          name: pcre-devel,openssl,openssl-devel,gd-devel,make,gcc,gcc-c++,wget
          state: present
      - name: Configure Nginx
        shell: |
          cd {{ una_dir }}{{ package['nginx'] }} && \
          ./configure --prefix={{ ins_dir['nginx'] }} \
          --user=nginx --group=nginx \
          --with-http_ssl_module --with-http_gzip_static_module
      - name: Build and install Nginx
        shell: |
          cd {{ una_dir }}{{ package['nginx'] }} && \
          make -j$(nproc) && make install
      - name: Add Nginx to PATH
        copy:
          dest: /etc/profile.d/nginx.sh
          content: "export PATH=$PATH:{{ ins_dir['nginx'] }}/sbin"
      - name: Create Nginx systemd service
        copy:
          dest: /usr/lib/systemd/system/nginx.service
          content: |
            [Unit]
            Description=nginx server daemon
            After=network.target
            [Service]
            Type=forking
            ExecStart={{ ins_dir['nginx'] }}/sbin/nginx
            ExecStop={{ ins_dir['nginx'] }}/sbin/nginx -s stop
            ExecReload=/bin/kill -HUP $MAINPID
            [Install]
            WantedBy=multi-user.target
      - name: Deploy Nginx config
        copy:
          dest: "{{ ins_dir['nginx'] }}/conf/nginx.conf"
          content: |
            user  nginx;
            worker_processes 1;
            events { worker_connections 1024; }
            http {
              include mime.types;
              default_type application/octet-stream;
              sendfile on;
              keepalive_timeout 65;
              server {
                listen 80;
                server_name localhost;
                location / {
                  root html;
                  index index.php;
                }
                location ~ \.php$ {
                  root /var/www;
                  fastcgi_pass {{ host_ip['php'] }}:9000;
                  fastcgi_index index.php;
                  include fastcgi_params;
                }
              }
            }
      - name: Create test index.php
        copy:
          dest: "{{ ins_dir['nginx'] }}/html/index.php"
          content: "<?php phpinfo(); ?>"
      - name: Start and enable Nginx
        service:
          name: nginx.service
          state: restarted
          enabled: yes

  # MySQL section (similar structure, omitted for brevity)
  # PHP section (similar structure, omitted for brevity)

Variable Usage

Variables are defined in separate YAML files for directories, host IPs, and package names, then referenced in the playbook using Jinja2 syntax (e.g., {{ ins_dir['nginx'] }}).

# tree playdemo
playdemo
├── ansible.cfg
├── inventory
├── package
│   ├── mysql-5.7.38-linux-glibc2.12-x86_64.tar.gz
│   ├── nginx-1.22.0.tar.gz
│   └── php-8.1.11.tar.gz
├── playbook
│   └── lnmp.yml
└── var
    ├── dir.yml
    ├── hos.yml
    └── package.yml

# cat var/package.yml
package:
  nginx: nginx-1.22.0
  mysql: mysql-5.7.38-linux-glibc2.12-x86_64
  php: php-8.1.11

# cat var/dir.yml
url_dir: ../package/
 dow_dir: /usr/local/src/
 una_dir: /usr/src/
 ins_dir:
   nginx: /usr/local/nginx
   mysql: /usr/local/mysql
   php: /usr/local/php

# cat var/hos.yml
host_ip:
  nginx: 192.168.222.137
  mysql: 192.168.222.138
  php: 192.168.222.139

Running ansible-playbook playbook/lnmp.yml -vv completes the deployment with all tasks reporting ok and changed counts, confirming a successful automated LNMP setup.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PHPLNMP
Raymond Ops
Written by

Raymond Ops

Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.