October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
VGSources
Blog

How to Set Up a LAMP Stack with Ansible on an Ubuntu 24.04 VPS

A complete Ansible playbook for Ubuntu 24.04 that installs Apache, PHP and MySQL, enables services at boot, verifies PHP execution, and explains secrets, idempotency, hardening and common failures.
Length11 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use one idempotent Ansible playbook to install Apache, PHP, and MySQL on an Ubuntu 24.04 VPS, start both services, enable them at boot, and verify that Apache can execute PHP. The example keeps the VPS address, SSH account, key, and privilege settings in inventory rather than hard-coding provider-specific defaults.

This guide assumes a newly provisioned Ubuntu server with SSH access and a control machine running Ansible. It installs distribution packages from Ubuntu’s configured repositories. Firewall rules, TLS certificates, application deployment, database backups, and application-specific hardening remain separate tasks because their correct settings depend on what you will host.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What this playbook installs

In this guide, LAMP means Linux (Ubuntu), Apache HTTP Server, a MySQL-compatible database server, and PHP. The core package set for Ubuntu 24.04 is:

Package Purpose
apache2 Web server that accepts HTTP requests.
php PHP runtime and the distribution’s default PHP metapackage.
libapache2-mod-php Apache integration that allows Apache to pass PHP files to the PHP runtime.
php-mysql PHP driver for MySQL-compatible databases.
mysql-server Database server supplied for the selected Ubuntu release.

Ubuntu’s recommended mechanism for Debian packages is APT. Ansible’s ansible.builtin.apt module manages those packages, while ansible.builtin.service controls services through the service manager detected on the host. Use state: started and enabled: true for a repeatable “running now and after reboot” result; use state: restarted only when a change actually requires a restart.

Prerequisites and deployment decisions

VPS requirements

  • A provisioned Ubuntu 24.04 VPS with a routable address and an SSH server.
  • An SSH user that can use sudo, or a root login for the initial bootstrap.
  • Inbound TCP access for SSH (normally port 22) from your administration network. Open HTTP (80) and HTTPS (443) only when the site is ready to serve them.
  • Enough disk space for the operating system, package cache, web files, logs, and database growth. The exact amount depends on your application and data set.

Control-node requirements

Install Ansible on your workstation or automation runner, then confirm the executable is available with ansible --version. The managed host must have Python and python3-apt; the APT module requires that Python package on Debian and Ubuntu systems. Most Ubuntu images include it, but the bootstrap below checks for it and installs it when necessary.

Choose the database and PHP integration deliberately

MySQL and MariaDB are broadly compatible, but an application can depend on a particular SQL dialect, authentication plugin, or server version. Confirm the application’s support matrix before replacing mysql-server with another package. The example uses Apache’s PHP module because it is the shortest single-VPS setup. PHP-FPM is a separate architecture: Apache proxies PHP requests to a pool, giving you different process isolation and tuning controls. Do not mix the two approaches casually; install and configure the integration your application expects.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create an inventory with provider-specific values

Make a project directory and create inventory.ini. Keep the hostname, address, account, key, and escalation settings here so the playbook is portable between VPS providers.

[lamp]
web01 ansible_host=203.0.113.20

[lamp:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/lamp_ed25519
ansible_become=true
ansible_python_interpreter=/usr/bin/python3

Replace the example address, user, and key path. If the image permits root SSH but does not yet have a sudo-capable account, set ansible_user=root for the first run and remove that exception after creating your administrative account. Test connectivity before installing anything:

ansible -i inventory.ini lamp -m ansible.builtin.ping

A successful response contains "ping": "pong". An unreachable result usually means the address, SSH username, key permissions, cloud firewall, or server-side sshd configuration is wrong; fix that before troubleshooting Ansible modules.

Write the idempotent LAMP playbook

Save the following as lamp.yml. The first pre-task uses a raw SSH command because Ansible cannot use the APT module until the managed host has python3-apt. It is safe to run repeatedly: the command installs the package only when importing the Python module fails.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
---
- name: Install and verify a LAMP stack
  hosts: lamp
  become: true
  gather_facts: false

  vars:
    lamp_packages:
      - apache2
      - php
      - libapache2-mod-php
      - php-mysql
      - mysql-server

  pre_tasks:
    - name: Ensure the APT Python bindings exist
      ansible.builtin.raw: >-
        /usr/bin/python3 -c 'import apt' 2>/dev/null ||
        (apt-get update -y &&
         DEBIAN_FRONTEND=noninteractive apt-get install -y python3-apt)
      changed_when: false

    - name: Gather facts after Python support is ready
      ansible.builtin.setup:

  tasks:
    - name: Refresh the local package index and install LAMP packages
      ansible.builtin.apt:
        name: '{{ lamp_packages }}'
        state: present
        update_cache: true
        cache_valid_time: 3600

    - name: Ensure Apache is running and enabled at boot
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true

    - name: Ensure MySQL is running and enabled at boot
      ansible.builtin.service:
        name: mysql
        state: started
        enabled: true

    - name: Install a temporary PHP execution check
      ansible.builtin.copy:
        dest: /var/www/html/ansible-health.php
        content: '<?php echo "ansible-php-ok";'
        owner: root
        group: root
        mode: '0644'

    - name: Check the Apache endpoint and PHP execution
      ansible.builtin.uri:
        url: http://127.0.0.1/ansible-health.php
        method: GET
        status_code: 200
        return_content: true
      register: php_check
      changed_when: false

    - name: Fail when Apache did not execute PHP
      ansible.builtin.assert:
        that:
          - "'ansible-php-ok' in php_check.content"
        fail_msg: 'Apache returned the PHP file without the expected output.'

    - name: Remove the temporary PHP check
      ansible.builtin.file:
        path: /var/www/html/ansible-health.php
        state: absent

The package task refreshes APT metadata and installs all five packages in one transaction. state: present does not reinstall packages that are already installed. The service tasks likewise make no change when the daemons are already running and enabled. The verification file is deliberately temporary, so it does not become an accidental public diagnostic endpoint.

Run the playbook safely

  1. Run a syntax check: ansible-playbook -i inventory.ini lamp.yml --syntax-check.
  2. Preview changes with a dry run: ansible-playbook -i inventory.ini lamp.yml --check. Package installation and the raw bootstrap may be reported differently in check mode because they need to inspect the remote package manager.
  3. Apply the configuration: ansible-playbook -i inventory.ini lamp.yml.
  4. Run the same command again. A correctly converged host should report no changes except for tasks whose environment genuinely changed. Re-running is the practical idempotency test.

The playbook’s HTTP check runs on the VPS against loopback, so it does not depend on DNS or an external firewall rule. From your own machine, you can make a separate check with curl http://203.0.113.20/ after allowing port 80. Do not expose the temporary health file; the playbook removes it before completion.

What each Ansible module is doing

ansible.builtin.apt

The APT module manages Debian and Ubuntu packages. update_cache: true refreshes the local package index from configured repositories before dependency resolution. cache_valid_time: 3600 avoids refreshing the index more than once per hour during repeated runs. Keep package names explicit for the Ubuntu release you selected; metapackages can change their dependencies when the distribution changes.

ansible.builtin.service

The service module controls services through the host’s detected service manager. On a normal Ubuntu 24.04 VPS that is systemd, but the generic module keeps the playbook independent of systemd-specific options. state: started is idempotent. state: restarted is not: it performs a restart every time the task runs. If you need systemd-only controls such as daemon reload behavior, use ansible.builtin.systemd_service explicitly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Repository modules

The example intentionally uses Ubuntu’s configured repositories and does not add a third-party source. If your application requires a package unavailable in the release repositories, the current Ansible documentation recommends ansible.builtin.deb822_repository for new repository definitions. ansible.builtin.apt_repository can add repositories but is deprecated in favor of the deb822 module. Adding a repository also creates a supply-chain and upgrade decision: pin versions, import the correct signing key through the supported mechanism, and test upgrades before applying them to production.

Configure Apache and deploy an application document root

The base packages provide Apache’s default virtual host and document root. For a real site, manage a named virtual-host file with Ansible rather than editing files interactively. A minimal template can define ServerName, DocumentRoot, directory permissions, and log paths. Notify a handler only when that file changes:

- name: Install the application virtual host
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/apache2/sites-available/app.conf
    owner: root
    group: root
    mode: '0644'
  notify: Reload Apache

- name: Enable the application virtual host
  ansible.builtin.command: a2ensite app.conf
  args:
    creates: /etc/apache2/sites-enabled/app.conf
  notify: Reload Apache

handlers:
  - name: Reload Apache
    ansible.builtin.service:
      name: apache2
      state: reloaded

Use a dedicated template for your domain and application path. A reload applies configuration changes without needlessly terminating existing workers. Validate the configuration before reloading when you add more complex directives; a failed configuration should stop the deployment rather than take down a working site.

Create application database credentials without exposing them

Do not place a database password in plain inventory, a public repository, task output, or a shell command that appears in process listings. Store secrets in Ansible Vault or an approved secret manager:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
ansible-vault create group_vars/lamp/vault.yml

Define variables such as app_db_name, app_db_user, and app_db_password in that encrypted file. Invoke the playbook with --ask-vault-pass or your organization’s configured Vault credential helper. Keep database creation and grants in a separate role if several applications share the host, and grant each application only the privileges it needs.

Ubuntu images may configure the local database administrator for socket-based authentication rather than a password. Do not assume that changing the administrator password is required for an application user. Create a dedicated account for the application, test its connection, and document the authentication method expected by your framework.

Make the VPS survive reboots and routine reruns

  • Enable services: the enabled: true flags create boot-time enablement; confirm with systemctl is-enabled apache2 mysql.
  • Check current state: use systemctl is-active apache2 mysql and inspect journalctl -u apache2 or journalctl -u mysql when a service fails.
  • Separate changes from restarts: notify reload or restart handlers from templates and configuration tasks instead of restarting on every playbook run.
  • Pin intent, not accidental versions: use distribution packages unless you have a tested reason to pin a version. If you pin, include the Ubuntu release and upgrade policy in the role documentation.
  • Keep state out of ad-hoc commands: represent packages, files, services, users, and firewall rules as Ansible resources so the next run can detect drift.

Security and operations that this base playbook does not solve

A working LAMP installation is not a production security baseline. Add these controls according to the application’s risk profile:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Firewall: allow SSH only from administration networks where possible; expose 80 and 443 as needed; do not publish the database port to the internet unless there is a compelling, protected requirement.
  • TLS: obtain and renew a certificate for the real hostname, redirect HTTP to HTTPS where appropriate, and test renewal before the first certificate expires.
  • SSH: use key authentication, remove unused accounts, disable direct root login when your recovery process permits, and keep the operating system patched.
  • Database hardening: remove test data and anonymous accounts where present, restrict listening addresses, use separate application credentials, and review authentication plugins against your framework.
  • Backups: back up database contents and uploaded files to storage independent of the VPS. A backup is not a recovery plan until you have restored it and measured how long restoration takes.
  • Monitoring and logs: watch disk space, memory, Apache error rates, PHP errors, database availability, and certificate expiry. Configure log rotation before logs can fill the root filesystem.
  • Updates: schedule and test security updates. Package upgrades can change PHP extensions, Apache modules, or database behavior, so use staging for significant application changes.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and precise fixes

“Module apt has no attribute” or an import error

The managed host lacks python3-apt, or Ansible is using a different interpreter. Confirm ansible_python_interpreter=/usr/bin/python3 in inventory and run the bootstrap pre-task. If Python itself is absent from an unusually minimal image, install it through the provider’s console or cloud-init before running Ansible.

SSH works manually but Ansible cannot connect

Run ansible -i inventory.ini lamp -m ansible.builtin.ping -vvv. Check that the inventory address resolves to the VPS, the key file is readable only by its owner, the SSH user exists, and sudo does not require an interactive password. If a password is required, configure Ansible’s become prompt rather than putting the password in inventory.

Apache is active but PHP source appears in the browser

Confirm that libapache2-mod-php is installed, inspect enabled modules with apache2ctl -M, and verify that the requested file ends in .php. If you chose PHP-FPM instead of the Apache module, configure the proxy integration and PHP-FPM service; do not install both integrations without a deliberate routing plan.

The PHP check returns 403 or 404

Check the document-root permissions, enabled virtual host, and Apache error log. A custom virtual host may point somewhere other than /var/www/html; place the temporary check in that site’s actual document root or replace the check with an application-specific endpoint.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

MySQL does not start

Inspect systemctl status mysql and journalctl -u mysql -b. Common causes include an exhausted disk, a port conflict, corrupted or incompatible data files, and configuration copied from another MySQL or MariaDB release. Do not delete the data directory as a first response; preserve it and restore from a known backup if recovery is required.

The second playbook run restarts everything

Search for state: restarted tasks or unconditional shell commands. Replace them with state: started and handlers notified only by changed configuration tasks. Also check whether a template renders nondeterministically, for example by embedding a timestamp, which would mark the file changed on every run.

When to split the web and database tiers

Deployment Advantages Costs and cautions
One VPS for Apache, PHP, and MySQL Lowest operational complexity, one playbook target, and simple local database connectivity. Web traffic and database workloads compete for CPU, memory, disk I/O, and failure recovery. A VPS outage affects every tier.
Separate web and database hosts Independent scaling, tighter network boundaries, and maintenance isolation. Requires private networking, firewall rules, encrypted database connections where appropriate, monitoring across hosts, and more involved backup and deployment coordination.

Start with one host when the workload and risk justify its simplicity. Split tiers when resource contention, availability requirements, or security boundaries make the additional operations worthwhile. The same Ansible role can target separate inventory groups, but database connection variables and firewall policy must then be explicit.

Manual commands versus an Ansible role

Approach Best fit Limitation
One-off APT and systemctl commands A disposable experiment or emergency recovery. Hard to audit, repeat, or reproduce accurately after drift.
Single playbook One VPS or a small number of equivalent hosts. Can become difficult to maintain if application, security, and backup concerns are mixed together.
Reusable Ansible roles Several environments, staged releases, or multiple applications. Requires role boundaries, variable conventions, testing, and version control discipline.

A practical progression is to keep the package and service tasks in a LAMP role, put virtual-host and PHP settings in an application role, and manage firewall, TLS, backups, and monitoring through separate roles. That structure lets you rerun only the part that changed while preserving the same idempotent behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rebuild, upgrade, or remove the stack

To upgrade packages, run the playbook after reviewing the target Ubuntu updates; the present state ensures required packages remain installed but does not itself perform a full distribution upgrade. Test PHP and database compatibility in a staging VPS before a production upgrade.

To retire the stack, first export and verify the database, copy application files and certificates, and confirm that DNS and traffic can be moved. Then remove the virtual host and packages deliberately. Do not use an automatic purge on a server that may contain data you have not independently backed up.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.