How to Install LAMP Stack on Ubuntu 24.04 — Apache + MySQL + PHP
Quick Summary
The LAMP stack (Linux, Apache, MySQL, PHP) is the classic open-source web
platform that powers WordPress, Drupal, Joomla, Magento, phpBB, and an
enormous share of the public web. This guide walks you through a clean,
production-grade install on Ubuntu 24.04 LTS with PHP 8.3, MySQL 8,
Apache 2.4, SSL via Let's Encrypt, opcache + APCu tuning, and hardening.
Estimated time: 25 minutes.>
Skip the setup? Use our 1-click LAMP installer — starts at EUR 7.99/mo on CloudCore Starter.
Table of Contents
- What is LAMP?
- LAMP vs LEMP — Which Should You Choose?
- Prerequisites
- Step 1: Install Apache
- Step 2: Install MySQL
- Step 3: Install PHP 8.3
- Step 4: Test PHP + MySQL Integration
- Step 5: Create a Production Virtual Host
- Step 6: Enable SSL with Certbot
- Step 7: Performance Tuning
- Step 8: Security Hardening
- Optional: Install phpMyAdmin
- Backups
- Troubleshooting
- FAQ
- Next Steps
What is LAMP?
LAMP is an acronym for four open-source components that, when combined, form a complete server platform for dynamic websites and web applications:
- Linux — the operating system (Ubuntu 24.04 in this guide)
- Apache — the HTTP web server that handles incoming requests
- MySQL — the relational database for structured data storage
- PHP — the server-side scripting language that generates dynamic content
Unlike managed PaaS platforms, a self-hosted LAMP stack gives you root access to every layer. You control the Apache modules, the MySQL configuration, the PHP extensions, the firewall, and the filesystem layout. There is no vendor lock-in and no per-request pricing — you pay a flat monthly VPS fee and the server can host one site or dozens.
LAMP vs LEMP — Which Should You Choose?
LEMP is the same stack with Nginx (pronounced "Engine-X", hence the "E") in place of Apache. Both are battle-tested and both will serve your PHP application reliably. Here is how to decide:
| Consideration | LAMP (Apache) | LEMP (Nginx) |
|---|---|---|
.htaccess support | Native and per-directory | Not supported — config only in server blocks |
| WordPress compatibility | Plug-and-play | Requires rewriting permalinks in nginx config |
| Static file performance | Good | Excellent (lower memory per connection) |
| High-concurrency workloads | Prefork can get heavy | Event-driven, handles 10k+ connections easily |
| Module ecosystem | Huge — mod_rewrite, mod_ssl, mod_security | Smaller but growing |
| Learning curve | Gentler for beginners | Slightly steeper |
| PHP integration | libapache2-mod-php (in-process) or PHP-FPM | Always PHP-FPM (FastCGI) |
| Memory footprint at idle | ~40-80 MB | ~15-30 MB |
- You run WordPress, Drupal, Joomla, or any CMS that ships
.htaccess
- You use shared hosting conventions and expect
.htaccessoverrides. - You are new to self-hosting and want the most tutorial coverage.
- You value the widest compatibility with third-party PHP apps.
- You expect very high concurrent traffic (10k+ simultaneous users).
- You are serving a large volume of static assets or streaming media.
- You are building a custom application where
.htaccessflexibility
- You want the lowest possible memory footprint per connection.
Prerequisites
Before you begin, you need:
- A VPS with at least 1 vCPU, 1 GB RAM, 20 GB storage
- Ubuntu 24.04 LTS (fresh install strongly recommended)
- SSH access with a non-root sudo user
- A domain name pointed to your server IP (required for SSL)
- Ports 80 and 443 reachable from the public internet
sudo apt update && sudo apt upgrade -yStep 1: Install Apache
Install Apache 2.4 from the official Ubuntu repositories:
sudo apt install -y apache2Once the installation completes, Apache starts automatically and is enabled on boot. Verify it is running:
sudo systemctl status apache2You should see active (running) in green.
Open the firewall. Ubuntu 24.04 ships with UFW (Uncomplicated Firewall). Apache registers application profiles during install, so you can allow HTTP and HTTPS with a single rule:
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw --force enable
sudo ufw statusApache Full opens ports 80 and 443. The OpenSSH rule keeps your SSH
session from getting locked out when the firewall activates.
Test Apache by visiting your server's IP address in a browser:
http://YOUR_SERVER_IPYou should see the default "Apache2 Ubuntu Default Page". If the page does not load, double-check UFW rules and your cloud provider's network-level firewall (if any).
Step 2: Install MySQL
Install the MySQL 8 server package:
sudo apt install -y mysql-serverMySQL starts automatically after installation. Verify:
sudo systemctl status mysqlSecure the MySQL installation
Run the interactive hardening script. This sets a root password, removes anonymous users, disables remote root login, and drops the test database:
sudo mysql_secure_installationAnswer the prompts:
- VALIDATE PASSWORD COMPONENT — answer
y, then select2for
- Remove anonymous users? —
y - Disallow root login remotely? —
y - Remove test database and access to it? —
y - Reload privilege tables now? —
y
On fresh Ubuntu 24.04 installs, MySQL 8 uses auth_socket for the
root user by default, which means root logs in via Unix socket
authentication, not password. Do not fight this — leave root on
socket auth and create a dedicated application user instead.
Create an application database and user
Never run your application as MySQL root. Create a dedicated database and user with the narrowest privileges the app needs:
sudo mysqlInside the MySQL shell, run:
CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'ChangeThisToAStrongPassword!';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, CREATE TEMPORARY TABLES, LOCK TABLES, REFERENCES, TRIGGER, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
SHOW GRANTS FOR 'appuser'@'localhost';
The SHOW GRANTS output should list the privileges you just granted —
all scoped to appdb., never to .*. This means a SQL injection
exploit in your app cannot reach other databases on the same server.
Exit the MySQL shell:
EXIT;Replace appdb, appuser, and the password with values appropriate
for your project. Store the credentials in a secrets manager or a
.env file outside the web root — never commit them to Git.
Step 3: Install PHP 8.3
Ubuntu 24.04 ships with PHP 8.3 as the default, which is exactly what you want for modern applications. Install PHP plus the Apache module and the most commonly required extensions in one command:
sudo apt install -y php libapache2-mod-php php-mysql php-common \
php-cli php-mbstring php-xml php-curl php-gd php-zip php-bcmathWhat each package does:
phpandphp-cli— the PHP runtime and command-line toollibapache2-mod-php— the Apache module that runs PHP in-processphp-mysql— MySQL driver (enables PDO_MySQL and mysqli)php-common— shared files used by every PHP extensionphp-mbstring— multibyte string handling (UTF-8 safe functions)php-xml— DOM, SimpleXML, XMLReaderphp-curl— HTTP client library used by virtually every frameworkphp-gd— image manipulation (thumbnails, resizing) used by WordPressphp-zip— zip archive support, required by Composer and WordPress updatesphp-bcmath— arbitrary-precision arithmetic (required by Magento and many e-commerce apps)
sudo systemctl restart apache2Verify PHP with phpinfo()
Create a temporary info page to confirm PHP is wired in correctly:
sudo tee /var/www/html/info.php > /dev/null <<'EOF'
<?php
phpinfo();
EOFOpen http://YOUR_SERVER_IP/info.php in your browser. You should see
the PHP configuration dashboard with version 8.3.x at the top and a
mysqli and PDO_mysql section further down.
Delete info.php the moment you finish testing. It exposes your
entire PHP configuration, loaded modules, environment variables, and
file paths — a goldmine for attackers.
sudo rm /var/www/html/info.phpPrefer PHP over index.html
By default, Apache serves index.html before index.php. For a PHP
application, flip the order:
sudo nano /etc/apache2/mods-enabled/dir.confChange:
DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htmTo:
DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htmSave, then reload Apache:
sudo systemctl reload apache2Step 4: Test PHP + MySQL Integration
Drop a small PDO test script that proves PHP can connect to the database you created in Step 2:
sudo tee /var/www/html/dbtest.php > /dev/null <<'EOF' <?php $dsn = 'mysql:host=localhost;dbname=appdb;charset=utf8mb4'; $user = 'appuser'; $pass = 'ChangeThisToAStrongPassword!';
try { $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); $row = $pdo->query('SELECT VERSION() AS version')->fetch(); echo 'Connected OK. MySQL version: ' . htmlspecialchars($row['version']); } catch (PDOException $e) { http_response_code(500); echo 'Database error: ' . htmlspecialchars($e->getMessage()); } EOF
Visit http://YOUR_SERVER_IP/dbtest.php. You should see:
Connected OK. MySQL version: 8.0.xx-0ubuntu0.24.04.xDelete the file immediately after testing — like info.php, it leaks
credentials if left in production:
sudo rm /var/www/html/dbtest.phpStep 5: Create a Production Virtual Host
Never deploy a real site into /var/www/html. Create a dedicated
directory per site so you can host multiple domains cleanly:
sudo mkdir -p /var/www/example.com/public
sudo chown -R $USER:$USER /var/www/example.com
sudo chmod -R 755 /var/www/example.comCreate a placeholder index:
cat > /var/www/example.com/public/index.php <<'EOF'
<?php
echo '<h1>example.com is live</h1>';
echo '<p>PHP version: ' . PHP_VERSION . '</p>';
EOFCreate the virtual host config:
sudo nano /etc/apache2/sites-available/example.com.confPaste the following, replacing example.com with your domain:
<VirtualHost *:80> ServerName example.com ServerAlias www.example.com ServerAdmin [email protected] DocumentRoot /var/www/example.com/public<Directory /var/www/example.com/public> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory>
<FilesMatch "\.(env|ini|log|sh|sql|bak|swp)$"> Require all denied </FilesMatch>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined </VirtualHost>
Key directives explained:
Options -Indexesdisables automatic directory listings — without it,
index.php see your file tree.
AllowOverride Alllets.htaccessfiles work (required for WordPress
- The
FilesMatchblock blocks direct download of sensitive file types
.env and .sql dumps accidentally left in the web root.Enable the site, enable mod_rewrite, disable the default site, and
reload:
sudo a2ensite example.com.conf
sudo a2enmod rewrite
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
sudo systemctl reload apache2Point your domain's A record to the server IP, wait for DNS to
propagate (usually under 5 minutes), and visit
http://example.com. You should see your placeholder page.
Step 6: Enable SSL with Certbot
Let's Encrypt provides free, trusted TLS certificates that auto-renew. Install Certbot with the Apache plugin:
sudo apt install -y certbot python3-certbot-apacheRun the interactive wizard:
sudo certbot --apache -d example.com -d www.example.comCertbot will:
2 when asked)Verify auto-renewal works:
sudo certbot renew --dry-runYour site is now reachable at https://example.com with an A+ grade
certificate.
Step 7: Performance Tuning
A default PHP install will work, but a 5-minute tune-up delivers a large performance jump — especially for WordPress and PHP frameworks.
Enable OPcache
OPcache caches compiled PHP bytecode in memory, eliminating parse + compile time on every request. It is shipped with PHP but worth tuning.
Edit the PHP config:
sudo nano /etc/php/8.3/apache2/conf.d/10-opcache.iniRecommended production settings:
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.validate_timestamps=1
opcache.save_comments=1For even more speed on production (where code does not change often),
set opcache.validate_timestamps=0 — but remember to run
sudo systemctl reload apache2 after every code deploy, otherwise
changes will not be picked up.
Install APCu (user-land cache)
APCu lets applications cache arbitrary data (session data, expensive queries, API responses) in shared memory:
sudo apt install -y php-apcu
sudo systemctl reload apache2Production php.ini settings
Edit the Apache PHP config:
sudo nano /etc/php/8.3/apache2/php.iniRecommended values for a production site:
; Memory and execution memory_limit = 256M max_execution_time = 60 max_input_time = 60; File uploads (tune to your app) upload_max_filesize = 64M post_max_size = 64M max_file_uploads = 20
; Error handling — never display errors to users display_errors = Off display_startup_errors = Off log_errors = On error_log = /var/log/php_errors.log error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
; Session security session.cookie_httponly = 1 session.cookie_secure = 1 session.cookie_samesite = "Lax" session.use_strict_mode = 1
; Hide PHP version from HTTP headers expose_php = Off
; Realpath cache speeds up include/require realpath_cache_size = 4096k realpath_cache_ttl = 600
Create the error log with the right permissions:
sudo touch /var/log/php_errors.log
sudo chown www-data:www-data /var/log/php_errors.log
sudo chmod 640 /var/log/php_errors.logRestart Apache to apply:
sudo systemctl restart apache2Step 8: Security Hardening
Hide the Apache and PHP version
Apache and PHP advertise their version in HTTP headers by default, helping attackers match known CVEs to your server. Turn that off.
Edit Apache security config:
sudo nano /etc/apache2/conf-enabled/security.confSet:
ServerTokens Prod
ServerSignature Offexpose_php = Off in php.ini (from Step 7) handles PHP's side.
Disable dangerous PHP functions
Most web apps never need exec, shell_exec, system, or passthru.
Disabling them massively reduces the blast radius of a file-upload
vulnerability:
sudo nano /etc/php/8.3/apache2/php.iniFind disable_functions = and set:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_multi_exec,parse_ini_file,show_sourceIf your application legitimately needs one of these (some backup
plugins need exec), remove just that function from the list.
Block PHP execution in upload directories
Even with uploads validated by your app, an attacker who finds a bypass
and uploads shell.php should not be able to execute it. Add a
per-directory rule:
sudo nano /var/www/example.com/public/wp-content/uploads/.htaccessContents:
<FilesMatch "\.(php|phtml|phar|php3|php4|php5|php7|php8)$">
Require all denied
</FilesMatch>Or enforce it at the virtual host level so it cannot be overridden:
<Directory /var/www/example.com/public/wp-content/uploads>
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
</Directory>Apply the same pattern to any directory that accepts user uploads — avatar folders, attachment folders, temp dirs.
Install mod_security and mod_evasive (optional)
For an extra layer against common web attacks (SQL injection, XSS, HTTP floods):
sudo apt install -y libapache2-mod-security2 libapache2-mod-evasive
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
sudo systemctl restart apache2Install Fail2Ban
Protect SSH and Apache login endpoints from brute-force attempts:
sudo apt install -y fail2ban
sudo systemctl enable --now fail2banDefault SSH jails activate automatically. Add Apache jails by editing
/etc/fail2ban/jail.local and enabling apache-auth, apache-badbots,
and apache-botsearch.
Optional: Install phpMyAdmin
phpMyAdmin is a web GUI for MySQL that many developers find convenient.
Security warning. phpMyAdmin is one of the most-scanned URLs on
the public internet. Attackers run automated tools looking for
/phpmyadmin,/pma,/mysql. If you install it, **do not leave it
on the default URL, and do not expose it to the open internet
without an extra auth layer**.
Install:
sudo apt install -y phpmyadminWhen prompted:
- Choose
apache2with spacebar, Tab, Enter - Answer
Yestodbconfig-common - Set a strong MySQL password for the phpmyadmin account
Secure phpMyAdmin — three mandatory steps
/etc/apache2/conf-enabled/phpmyadmin.confAlias /phpmyadmin to something obscure like
Alias /admin-db-7f3x. Reload Apache.sudo htpasswd -c /etc/apache2/.phpmyadmin-htpasswd admin Then in phpmyadmin.conf, inside the <Directory> block, add:
AuthType Basic
AuthName "Restricted"
AuthUserFile /etc/apache2/.phpmyadmin-htpasswd
Require valid-userRequire ip 203.0.113.45For most production sites, skip phpMyAdmin entirely and use an SSH tunnel with a desktop client like DBeaver, TablePlus, or the MySQL CLI over SSH — safer and just as convenient.
Backups
A LAMP stack has two things worth backing up: the MySQL database and the web root.
Database backups with mysqldump
Create a backup user with minimal privileges:
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'StrongBackupPassword!';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, PROCESS, RELOAD
ON . TO 'backup'@'localhost';
FLUSH PRIVILEGES;Store credentials in /root/.my-backup.cnf:
[client]
user=backup
password=StrongBackupPassword!Protect the file:
sudo chmod 600 /root/.my-backup.cnfCreate a daily backup script at /usr/local/bin/mysql-backup.sh:
#!/bin/bash
BACKUP_DIR=/var/backups/mysql
DATE=$(date +%F)
mkdir -p "$BACKUP_DIR"
mysqldump --defaults-extra-file=/root/.my-backup.cnf \
--single-transaction --quick --routines --triggers --events \
--all-databases | gzip > "$BACKUP_DIR/all-$DATE.sql.gz"
find "$BACKUP_DIR" -name 'all-*.sql.gz' -mtime +14 -deleteMake it executable and schedule it:
sudo chmod +x /usr/local/bin/mysql-backup.sh
sudo crontab -eAdd:
0 3 * /usr/local/bin/mysql-backup.shWeb root backups
Sync /var/www nightly to an off-server destination (S3, Backblaze B2,
another VPS) using rclone or restic. Never store backups only on
the same VPS they came from.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
apache2.service failed to start | Port 80 already in use | sudo ss -tlnp \</td><td>grep :80 to find conflict, stop or reassign |
| Browser shows raw PHP source | PHP module not enabled | sudo a2enmod php8.3 && sudo systemctl restart apache2 |
PDOException: SQLSTATE[HY000] [2002] | MySQL socket path mismatch | Use host=127.0.0.1 instead of localhost to force TCP |
Access denied for user 'appuser'@'localhost' | Wrong password or missing grants | Re-run GRANT and FLUSH PRIVILEGES |
| Upload fails silently over ~2 MB | Default upload_max_filesize | Raise in php.ini and restart Apache |
.htaccess rules ignored | AllowOverride None in vhost | Change to AllowOverride All and a2enmod rewrite |
500 Internal Server Error | PHP fatal error | sudo tail -f /var/log/apache2/example.com-error.log |
| Certbot fails challenge | DNS not propagated or UFW blocking 80 | dig example.com, check sudo ufw status |
| Site is slow under load | OPcache disabled or too small | Verify OPcache in phpinfo(), raise memory_consumption |
| MySQL eats all RAM | InnoDB buffer pool too large | Tune innodb_buffer_pool_size to ~50% of RAM |
FAQ
Q: Can I run LAMP on 1 GB of RAM?
A: Yes, for a low-traffic personal site or staging environment. MySQL 8 alone wants around 400 MB at idle. For anything production, step up to at least 2 GB — our CloudCore Starter plan at EUR 7.99/mo gives you 4 GB.
Q: How do I install a different PHP version (8.2 or 8.4)?
A: Add the Ondřej Surý PPA — the standard source for extra PHP versions
on Ubuntu: sudo add-apt-repository ppa:ondrej/php && sudo apt update && sudo apt install php8.2 libapache2-mod-php8.2. You
can then switch the default with sudo a2dismod php8.3 && sudo a2enmod php8.2.
Q: Should I use MariaDB instead of MySQL?
A: MariaDB is a drop-in MySQL replacement originally forked in 2009. Both work fine with any PHP app. MySQL 8 has a slight edge in JSON performance and official Oracle support; MariaDB has a slight edge in some write-heavy benchmarks and a stronger open-source governance story. Pick one and move on — it rarely matters for typical workloads.
Q: Is LAMP ready for WordPress out of the box?
A: Yes. After finishing this guide, drop the WordPress tarball in
/var/www/example.com/public, create a WordPress database and user
(same pattern as Step 2), and run the famous 5-minute install. All
required extensions (php-mysql, php-gd, php-curl, php-mbstring,
php-xml, php-zip) are already installed.
Q: How do I host multiple sites on one LAMP server?
A: Repeat Step 5 for each domain. Each gets its own folder under
/var/www/, its own virtual host file in /etc/apache2/sites-available/,
and its own MySQL database and user. Run sudo certbot --apache once
per domain to issue certificates.
Q: How do I upgrade from LAMP to LEMP later?
A: Install Nginx and PHP-FPM, translate your Apache virtual hosts and
.htaccess rules into Nginx server blocks, stop Apache, start Nginx.
The MySQL and PHP application layers do not change. Plan for 1-2 hours
of rewriting if you use heavy .htaccess rewrites.
Q: Do I need PHP-FPM with Apache?
A: Not for this guide — libapache2-mod-php runs PHP inside the
Apache process (prefork MPM) and is simpler. For higher concurrency,
switch Apache to the event MPM and use PHP-FPM:
sudo a2dismod php8.3 mpm_prefork && sudo a2enmod mpm_event proxy_fcgi setenvif && sudo a2enconf php8.3-fpm.
Q: How do I monitor the LAMP stack?
A: Pair it with Uptime Kuma for uptime checks and Netdata for real-time metrics on Apache, MySQL, and PHP-FPM.
Next Steps
- How to Install WordPress on LAMP — the most common next move
- How to Install LEMP Stack on Ubuntu 24.04 — when you outgrow Apache
- How to Set Up Fail2Ban — stop brute-force attacks
- How to Set Up Automated Backups — off-server backups with restic
- How to Install Redis for PHP Caching — dramatically speed up WordPress and Laravel
### Skip the Manual Install>
We offer LAMP as a 1-click app on all VPS plans.
Apache + MySQL + PHP 8.3 + OPcache + Certbot, pre-configured and
production-ready in under 2 minutes.>
Deploy LAMP Now | Starting from EUR 7.99/mo on CloudCore Starter | 172+ 1-click apps | 9 global locations