{"id":5935,"date":"2021-05-31T19:58:02","date_gmt":"2021-05-31T19:58:02","guid":{"rendered":"https:\/\/blog.ssdnodes.com\/blog\/?p=5935"},"modified":"2025-05-18T13:40:28","modified_gmt":"2025-05-18T13:40:28","slug":"ansible-lamp-setup","status":"publish","type":"post","link":"https:\/\/www.ssdnodes.com\/blog\/ansible-lamp-setup\/","title":{"rendered":"Automated LAMP Setup Using Ansible"},"content":{"rendered":"<p>Continuing with our <a href=\"https:\/\/www.ssdnodes.com\/blog\/ansible-initial-setup\/\">Ansible Series<\/a>, we will move on to a pretty common use case for most VPSes, and that is of LAMP Stack setup. LAMP stands for Linux, Apache, MySQL and PHP.This is a very popular stack of technologies that web developers use to build apps.<\/p>\n<\/p>\n<p>Linux refers to the base operating system which in our case will be Ubuntu 20.04 LTS, Apache is the web server that will serve the web content, MySQL (or MariaDB) is the database that will be used to store the state of the app (things like user content, usernames, encrypted passwords, etc) and PHP is the programming language that is used to generate dynamic content. This tech stack is the base for many popular apps like WordPress, Drupal and more.<\/p>\n<p>So let us create an Ansible playbook that will reliably setup LAMP stack on Ubuntu 20.04 LTS. As a developer, this would allow you to experiment and break things, and simply reinstall your VPS and get LAMP up and running with a single ansible command!<\/p>\n<h2>Prerequisites<\/h2>\n<ul>\n<li>A VPS running Ubuntu 20.04 LTS with a public IP. If you don't have one, feel free to pick one up <a href=\"https:\/\/www.ssdnodes.com\/pricing\/\">here<\/a>.<\/li>\n<li><a href=\"https:\/\/www.ssdnodes.com\/blog\/ansible-initial-setup\/\">Initial Server Setup using Ansible<\/a> to get you up to speed with ansible, and to harden your server's security.<\/li>\n<\/ul>\n<h2>Installing Packages<\/h2>\n<p>Let's start by create a <code>lamp-setup.yaml<\/code> Ansible Playbook like we did during our <a href=\"https:\/\/www.ssdnodes.com\/blog\/ansible-initial-setup\/\">initial setup blog<\/a>, except this time we will use the <code>package<\/code> builtin module to install the necessary packages:<\/p>\n<pre><code>---\n- name: LAMP Setup\n  hosts: all\n  remote_user: root\n\n  tasks:\n\n  - name: Installing Packages\n    package:\n      name: &quot;{{ item }}&quot;\n      state: present\n    with_items:\n      - apache2\n      - mysql-server\n      - php \n      - libapache2-mod-php \n      - php-mysql\n      - python3-pymysql<\/code><\/pre>\n<p>This will install all the basic packages that are needed for a typical LAMP setup. Linux is obviously the base system, <code>mysql-server<\/code>, <code>apache2<\/code>, <code>php<\/code> are pretty self-explanatory as well. We will also need <code>libapache2-mod-php<\/code> to enable <code>apache2<\/code> to talk to the PHP language interpreter. We would also need <code>php-mysql<\/code> which is a library that allows <code>php<\/code> to interface with the <code>mysql<\/code> database. Finally, we will also install <code>python3-pymysql<\/code> which is not a part of LAMP stack but it is required by Ansible to make MySQL requests on the target system.<\/p>\n<h2>Ansible Modules for Setting Up MySQL<\/h2>\n<p>Next we need to setup MySQL. This would involve setting up MySQL's root user's password (which is quit different from Linux's <code>root<\/code> user). Please be very careful and <strong>read the following<\/strong>:<\/p>\n<ul>\n<li><strong>DO NOT USE the same password as below<\/strong>, rather generate a random, unique and strong password.<\/li>\n<li><strong>DO NOT LEAVE the password in your ansible playbook<\/strong>. We will learn about dealing with passwords later in the future.<\/li>\n<li><strong>DO NOT PUSH the file <code>.mycnf<\/code>in your target's root directory to a git repository<\/strong>. This will contain your MySQL credentials and it should be ignored by git and other tools.<\/li>\n<\/ul>\n<p>Now, that we have that out of the way, we can start using Ansible to configure MySQL. To do this, we will first need to download and install a <em>collection<\/em> of Ansible modules on our Ansible host machine<\/p>\n<pre><code>$ ansible-galaxy collection install community.mysql<\/code><\/pre>\n<p>Now we can use the module <code>mysql_user<\/code> to set <code>root<\/code> user's password. We will also create a <code>.my.cnf<\/code> file in the Linux root user's Home directory. This file contains username and password that can be used by Linux's root user to log into MySQL. This will also be used by Ansible in subsequent tasks to automatically authenticate as root user for MySQL without us having to repeatedly supply username and password for each task.  <\/p>\n<pre><code>  - name: Set root password\n    no_log: true\n    community.mysql.mysql_user:\n      name: root\n      password: PLACE_YOUR_PASSWORD_HERE\n      login_unix_socket: \/var\/run\/mysqld\/mysqld.sock\n\n  - name: Copy .my.cnf for easier mysql automation\n    blockinfile:\n      path: ~\/.my.cnf\n      create: yes\n      block: |\n        [client]\n        user=root\n        password=&quot;PLACE_YOUR_PASSWORD_HERE&quot;<\/code><\/pre>\n<p>Notice, we also add <code>no_log: true<\/code> for each of the tasks above, this is to prevent Ansible from accidentally mentioning the MySQL root password in any of its log files.<\/p>\n<h2>Secure MySQL Installation<\/h2>\n<p>Most installation of MySQL comes with a default script called <code>mysql_secure_installation<\/code> which you are meant to run as root. This script prompts you through a list of choices such as setting root password, removing anonymous user and test databases, as well as prohibiting the root user to connect to the the database remotely.<\/p>\n<p>We will use Ansible to carry out these tasks without explicitly calling the script. Since we have already set root password, we only need to prohibit remote root login, remove anonymous user (represented by an empty string <code>&#039;&#039;<\/code>) and we need to delete the <code>test<\/code> database. The following tasks help us with this:<\/p>\n<pre><code>  - name: Removes test database\n    no_log: true\n    community.mysql.mysql_db:\n      name: test\n      state: absent\n\n  - name: Prohibit Remote Root login\n    no_log: true\n    community.mysql.mysql_query:\n      login_db: mysql\n      query: &quot;{{ item }}&quot;\n    with_items:\n      - DELETE FROM mysql.user WHERE User=&#039;root&#039; AND Host NOT IN (&#039;localhost&#039;, &#039;127.0.0.1&#039;, &#039;::1&#039;);\n      - FLUSH PRIVILEGES;<\/code><\/pre>\n<p>We used different modules above, namely <code>mysql_db<\/code> and <code>mysql_query<\/code>. For more information on this you can always refer to their <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/collections\/community\/mysql\/index.html\" target=\"_blank\" rel=\"noopener\">excellent documentation<\/a>. <\/p>\n<h2>Configuring Apache<\/h2>\n<p>Next we need to configure Apache to serve php files. By default, Apache is configured to look into the directory <code>\/var\/www\/html<\/code> directory and serve the <code>index.html<\/code> file first. If that is not found, it will look for <code>index.htm<\/code>, followed by a few other extensions.<\/p>\n<p>We will configure it to look for <code>index.php<\/code> first. After this, you can deploy your PHP application to <code>\/var\/www\/html<\/code> and Apache will automatically serve it.<\/p>\n<pre><code>  - name: Configure apache2\n    lineinfile:\n      path: \/etc\/apache2\/mods-enabled\/dir.conf\n      regexp: &quot;^\\tDirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm$&quot;\n      line: &quot;\\tDirectoryIndex index.php index.html index.cgi index.pl index.php index.xhtml index.htm&quot;\n    notify: Restart Apache2\n\n  handlers:\n      - name: Restart Apache2\n        service:\n          name: apache2\n          state: restarted<\/code><\/pre>\n<p>The configuration was in file <code>\/etc\/apache2\/mods-enabled\/dir.conf<\/code> and after the task results in a changed state of the file Ansible restarts the Apache web server as specified by <code>notify: Restart Apache2<\/code>.<\/p>\n<p>The <code>Restart Apache2<\/code> handler is defined inside the <code>handlers<\/code> section.<\/p>\n<h2>Final Playbook and Testing PHP rendering<\/h2>\n<p>To combine all the above setups, the <code>lamp-setup.yaml<\/code> playbook would look something like below:<\/p>\n<pre><code>---\n- name: LAMP Setup\n  hosts: all\n  remote_user: root\n\n  tasks:\n\n  - name: Installing Packages\n    package:\n      name: &quot;{{ item }}&quot;\n      state: present\n    with_items:\n      - apache2\n      - mysql-server\n      - php \n      - libapache2-mod-php \n      - php-mysql\n      - python3-pymysql\n\n  - name: Set root password\n    no_log: true\n    community.mysql.mysql_user:\n      name: root\n      password: PLACE_YOUR_PASSWORD_HERE\n      login_unix_socket: \/var\/run\/mysqld\/mysqld.sock\n\n  - name: Copy .my.cnf for easier mysql automation\n    blockinfile:\n      path: ~\/.my.cnf\n      create: yes\n      block: |\n        [client]\n        user=root\n        password=&quot;PLACE_YOUR_PASSWORD_HERE&quot;\n\n  - name: Removes test database\n    no_log: true\n    community.mysql.mysql_db:\n      name: test\n      state: absent\n\n  - name: Prohibit Remote Root login\n    no_log: true\n    community.mysql.mysql_query:\n      login_db: mysql\n      query: &quot;{{ item }}&quot;\n    with_items:\n      - DELETE FROM mysql.user WHERE User=&#039;root&#039; AND Host NOT IN (&#039;localhost&#039;, &#039;127.0.0.1&#039;, &#039;::1&#039;);\n      - FLUSH PRIVILEGES;\n  - name: Configure apache2\n    lineinfile:\n      path: \/etc\/apache2\/mods-enabled\/dir.conf\n      regexp: &quot;^\\tDirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm$&quot;\n      line: &quot;\\tDirectoryIndex index.php index.html index.cgi index.pl index.php index.xhtml index.htm&quot;\n    notify: Restart Apache2\n\n  handlers:\n      - name: Restart Apache2\n        service:\n          name: apache2\n          state: restarted<\/code><\/pre>\n<p>To run the playbook use the below commands:<\/p>\n<pre><code>$ ansible-galaxy collection install community.mysql\n$ ansible-playbook lamp-setup.yaml<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>With the playbook handy, you can really accelerate your development workflow. If something breaks in your testing server, you can reinstall the VPS back to a clean state, setup LAMP stack with a single command. If you wish to deploy your application directly from your git repository we have <a href=\"https:\/\/www.ssdnodes.com\/blog\/git-hooks-deploy\/\">a blog post<\/a> showing you how to do that as well!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Continuing with our Ansible Series, we will move on to a pretty common use case for most VPSes, and that is of LAMP Stack setup. LAMP stands for Linux, Apache, MySQL and PHP.This is a very popular stack of technologies that web developers use to build apps. Linux refers to the base operating system which  &#8230;<\/p>\n","protected":false},"author":20,"featured_media":5952,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[18],"tags":[209,199],"class_list":["post-5935","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-ansible","tag-lamp"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/5935","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/comments?post=5935"}],"version-history":[{"count":4,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/5935\/revisions"}],"predecessor-version":[{"id":13028,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/posts\/5935\/revisions\/13028"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media\/5952"}],"wp:attachment":[{"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/media?parent=5935"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/categories?post=5935"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ssdnodes.com\/wp-json\/wp\/v2\/tags?post=5935"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}