LAMP Stack on Ubuntu 24.04 LTS | cloudimg
LAMP Stack on Ubuntu 24.04 LTS
The LAMP stack — Linux, Apache, MySQL, PHP — remains the most widely deployed web application platform in the world. This cloudimg image ships Apache 2.4 from Ubuntu, MySQL 8.4 LTS from Oracle's upstream repository, and PHP 8.3 with mod_php and the common extension set (mysqli, pdo_mysql, gd, mbstring, curl, xml, zip, intl, opcache). A unique MySQL root password and application database password are generated on first boot — no credentials are baked into the image.
Prerequisites
- An Azure subscription with permission to create virtual machines
- An SSH key pair — the public key is uploaded during VM creation
curlor a web browser to exercise the running stack
Step 1 — Deploy from the Azure Portal
- Find LAMP Stack on Ubuntu 24.04 LTS by cloudimg in the Azure Marketplace and click Create.
- On the Basics tab, select or create a Resource Group, choose East US as the region, and set the VM size to Standard_D2s_v3 (2 vCPU / 8 GB RAM). Larger sizes are fine for higher traffic.
- Under Administrator account, choose SSH public key and paste your public key.
- On the Networking tab, open inbound ports 80 (HTTP) and 443 (HTTPS) in addition to 22 (SSH).
- Click Review + create, then Create.
Step 2 — Deploy with the Azure CLI
az vm create \
--resource-group <your-resource-group> \
--name lamp-stack \
--image "$(az sig image-version list \
--resource-group AZURE-CLOUDIMG \
--gallery-name cloudimgGallery \
--gallery-image-definition lamp-stack-ubuntu-24-04 \
--query '[0].id' -o tsv)" \
--size Standard_D2s_v3 \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_rsa.pub \
--public-ip-sku Standard
Open the HTTP and HTTPS ports from your local workstation (replace <your-resource-group>):
az vm open-port --port 80 --priority 1001 --resource-group <your-resource-group> --name lamp-stack
az vm open-port --port 443 --priority 1002 --resource-group <your-resource-group> --name lamp-stack
Step 3 — Connect via SSH
ssh azureuser@<vm-ip>
On first boot, lamp-firstboot.service initialises the MySQL data directory, rotates the root password, creates the default application database and user, and starts Apache and MySQL. Allow up to 60 seconds for the service to complete.
Check that firstboot has finished:
sudo test -f /var/lib/cloudimg/lamp-firstboot.done && echo "firstboot done"
Step 4 — Retrieve Your MySQL Credentials
sudo cat /stage/scripts/lamp-credentials.log
The file is owned root:root (mode 0600) and contains the MySQL root password, the default application database name, the application user name, and the application password.
Example content:
mysql_root_password=<generated>
app_db=cloudimg_app
app_db_user=cloudimg_app
app_db_password=<generated>
Extract the passwords into shell variables for the rest of this guide:
MYSQL_ROOT_PASS=$(sudo awk -F= '/^mysql_root_password=/ {print $2}' /stage/scripts/lamp-credentials.log)
APP_DB_PASS=$(sudo awk -F= '/^app_db_password=/ {print $2}' /stage/scripts/lamp-credentials.log)
echo "root password length: ${#MYSQL_ROOT_PASS}"
echo "app password length: ${#APP_DB_PASS}"
Step 5 — Verify Apache and MySQL are Running
sudo systemctl is-active apache2.service
sudo systemctl is-active mysql.service
ss -tln | grep -E ':(80|3306)\b'
You should see active twice and both ports listening.
Step 6 — Exercise the Sample PHP Page
curl -s -o /tmp/index.html -w "HTTP %{http_code}\n" http://127.0.0.1/
grep -q "LAMP Stack" /tmp/index.html && echo "cloudimg welcome page served"
A browser visit to http://<vm-ip>/ will render the same page with a live MySQL connection test and the runtime details of your server.

The green CONNECTED badge is the sample page's self-test: it reads the app user password from /stage/scripts/lamp-credentials.log (readable by www-data at mode 0640 root:www-data), opens a PDO connection to MySQL on 127.0.0.1:3306, and prints the server version returned by SELECT VERSION(). If the page shows the first-boot-not-complete fallback instead, wait another minute and refresh.
Step 7 — MySQL Round-Trip as the App User
APP_DB_PASS=$(sudo awk -F= '/^app_db_password=/ {print $2}' /stage/scripts/lamp-credentials.log)
MYSQL_PWD="$APP_DB_PASS" mysql -u cloudimg_app --host=127.0.0.1 cloudimg_app <<'SQL'
CREATE TABLE IF NOT EXISTS demo (id INT PRIMARY KEY, label VARCHAR(64));
INSERT INTO demo (id, label) VALUES (1, 'hello') ON DUPLICATE KEY UPDATE label='hello';
SELECT * FROM demo;
SQL
You will see the row 1 | hello in the output.
Step 8 — Replace the Sample Page with Your Application
The document root is /var/www/html. Drop your PHP application in place:
sudo rm /var/www/html/index.php
sudo tee /var/www/html/index.php >/dev/null <<'PHP'
<?php
echo "Hello from my LAMP app!";
PHP
curl -s http://127.0.0.1/
For a real application, upload over SCP, SFTP, or git clone into /var/www/html (or a subdirectory). Make sure ownership is www-data:www-data and permissions are readable by Apache.
Step 9 — Serve a Custom Domain
Point your DNS A record at the VM's public IP, then create an Apache virtual host:
sudo tee /etc/apache2/sites-available/example.com.conf >/dev/null <<'CONF'
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/html
ErrorLog /var/log/apache2/example.com-error.log
CustomLog /var/log/apache2/example.com-access.log combined
</VirtualHost>
CONF
sudo a2ensite example.com.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
Step 10 — Add HTTPS with Let's Encrypt
Install certbot and obtain a certificate:
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com --non-interactive --agree-tos -m you@example.com
Certbot will configure a second virtual host on port 443, install the certificate, and add a systemd timer to auto-renew every 60 days.
Step 11 — Common Admin Tasks
Restart either service after a config change:
sudo systemctl reload apache2.service
sudo systemctl restart mysql.service
Tail the logs (use -F for continuous output in an interactive session; -n for a one-shot look):
sudo tail -n 20 /var/log/apache2/error.log /var/log/mysql/error.log 2>/dev/null || true
Administer MySQL as root — authenticate via the Unix socket with the rotated password from /stage/scripts/lamp-credentials.log. Use sudo env so the password variable survives sudo's default env_reset. This example lists the existing users:
MYSQL_ROOT_PASS=$(sudo awk -F= '/^mysql_root_password=/ {print $2}' /stage/scripts/lamp-credentials.log)
sudo env MYSQL_PWD="$MYSQL_ROOT_PASS" mysql -u root --socket=/var/run/mysqld/mysqld.sock \
-e "SELECT user, host FROM mysql.user ORDER BY user, host;"
To change the root password, replace new-strong-password with your own value and run the ALTER statement against the same socket:
sudo env MYSQL_PWD="$MYSQL_ROOT_PASS" mysql -u root --socket=/var/run/mysqld/mysqld.sock \
-e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'new-strong-password';"
Add another MySQL database and user — pick a unique name and a strong password:
MYSQL_ROOT_PASS=$(sudo awk -F= '/^mysql_root_password=/ {print $2}' /stage/scripts/lamp-credentials.log)
sudo env MYSQL_PWD="$MYSQL_ROOT_PASS" mysql -u root --socket=/var/run/mysqld/mysqld.sock <<'SQL'
CREATE DATABASE reports CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'reports_app'@'localhost' IDENTIFIED BY 'choose-a-strong-password';
GRANT ALL PRIVILEGES ON reports.* TO 'reports_app'@'localhost';
FLUSH PRIVILEGES;
SQL
Step 12 — Security Hardening Checklist
- Put MySQL behind a firewall —
ss -tlnshould show:3306bound only to127.0.0.1on production. - Keep the root password rotated on a schedule and stored in a secret manager.
- Run
sudo apt-get update && sudo apt-get -y upgradeon a regular cadence. - Use
fail2banif SSH is reachable from the public Internet.
Support
cloudimg provides 24/7/365 expert technical support. Guaranteed response within 24 hours, one hour average for critical issues. Contact support@cloudimg.co.uk.
Visit www.cloudimg.co.uk/products for our full catalogue.