
The LAMP stack (Linux, Apache, MySQL, PHP) is a set of open-source tools used to build and deliver dynamic web applications. Linux serves as the operating system, Apache handles web requests, MySQL manages databases, and PHP processes dynamic content.
In this article, you are to install the LAMP stack on 22.04.
Prerequisites
Before you begin:
Have an Ubuntu 22.04 server.
Access the server using SSH as a non-root user with sudo privileges.
Set up a new A record for your domain that points to the server IP address.
Install Apache
Follow the steps below to update the package index and install the latest Apache web server on your server.
Update the server's package index.
console$ sudo apt update
Install Apache.
console$ sudo apt install apache2 -y
Start the Apache service.
console$ sudo systemctl start apache2
Enable the Apache service to start automatically at boot time.
console$ sudo systemctl enable apache2
Verify that the service is running.
console$ sudo systemctl status apache2
Output:
● apache2.service - The Apache HTTP Server Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2025-04-06 10:56:28 UTC; 20s ago Docs: https://httpd.apache.org/docs/2.4/ Main PID: 2622 (apache2) Tasks: 55 (limit: 9415) Memory: 6.9M CGroup: /system.slice/apache2.service ├─2622 /usr/sbin/apache2 -k start ├─2623 /usr/sbin/apache2 -k start └─2624 /usr/sbin/apache2 -k startAllow incoming connections to the HTTP port
80.console$ sudo ufw allow 80/tcp
Access your domain or server IP using a web browser such as Chrome and verify that the default Apache web page displays.
http://SERVER-IP
Install MySQL
MySQL serves as the database backend in the LAMP stack, but you can replace it with MariaDB if needed. Follow the steps below to install the latest MySQL version using the default APT package manager.
Install the MySQL database server package.
console$ sudo apt install -y mysql-server
Enable the to start automatically at boot time.
console$ sudo systemctl enable mysql
Start the MySQL service.
console$ sudo systemctl start mysql
View the MySQL service status and verify that it's running.
console$ sudo systemctl status mysql
Output:
● mysql.service - MySQL Community Server Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2025-04-06 10:58:51 UTC; 41s ago Main PID: 17189 (mysqld) Status: "Server is operational" Tasks: 38 (limit: 9415) Memory: 364.8M CGroup: /system.slice/mysql.service └─17189 /usr/sbin/mysqldRun the MySQL secure installation script to disable insecure defaults and enable authentication on your database server.
console$ sudo mysql_secure_installation
Reply to the following MySQL database server options when prompted:
VALIDATE PASSWORD: Enteryto enable password strength validation on the database server.Password strength policy: Enter2to enforce multi-character password requirements.Remove anonymous users: Enteryto delete anonymous users from the server.Disallow root login remotely: Enteryto block remote access for therootuser.Remove test database: Enteryto remove the default MySQL test database.Reload privilege tables now: Enteryto apply the changes by reloading privilege tables.
Your output should look like the one below when successful:
Success. All done!Log in to the MySQL console as the
rootuser.console$ sudo mysql
Alter the
rootdatabase user to use a new strong password. ReplaceStrong@@password123with your desired password.sqlmysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Strong@@password123';
Replace
passwordwith a strong password.Flush the MySQL privileges table to apply the new user changes.
sqlmysql> FLUSH PRIVILEGES;
Exit the MySQL console.
sqlmysql> EXIT;
Log in to the MySQL console again as the
rootuser and enter the password you set earlier when prompted.console$ mysql -u root -p
Create a new sample database
content_database.sqlmysql> CREATE database content_database;
Verify that the new database is available.
sqlmysql> SHOW DATABASES;
Output:
+--------------------+ | Database | +--------------------+ | information_schema | | content_database | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.01 sec)Create a new MySQL user such as
dbadminwith a strong password. ReplaceStrong@@password123with your desired password.sqlmysql> CREATE USER 'dbadmin'@'localhost' IDENTIFIED BY 'Strong@@password123';
Grant the user full privileges to your sample database
content_database.sqlmysql> GRANT ALL PRIVILEGES ON content_database.* TO 'dbadmin'@'localhost';
Flush the MySQL privileges table to apply changes.
sqlmysql> FLUSH PRIVILEGES;
Exit the MySQL shell.
sqlmysql> EXIT;
Install PHP and Configure PHP-FPM
PHP is a key component of the LAMP stack, processing dynamic content and interacting with the MySQL database. PHP-FPM (FastCGI Process Manager) handles PHP connections and improves performance through worker process pools. Follow the steps below to install the latest PHP version and configure PHP-FPM for dynamic web application processing on your server.
Install PHP and the PHP-FPM module.
console$ sudo apt install -y php php-fpm
Install common PHP extensions on your server.
console$ sudo apt install -y php-mysql php-opcache php-cli libapache2-mod-php
The above command installs the following PHP modules:
php-mysql: Allows PHP to connect and interact with the MySQL database.libapache2-mod-php: Enables Apache to process and run PHP scripts.php-opcache: Caches precompiled PHP scripts in memory for faster execution.php-cli: Provides access to PHP via the server terminal.
View the installed PHP version on your server.
console$ php -v
Output:
PHP 8.1.2-1ubuntu2.21 (cli) (built: Mar 24 2025 19:04:23) (NTS) Copyright (c) The PHP Group Zend Engine v4.1.2, Copyright (c) Zend Technologies with Zend OPcache v8.1.2-1ubuntu2.21, Copyright (c), by Zend TechnologiesStart the PHP-FPM service based on the installed PHP version on your server. For example,
PHP 8.1.console$ sudo systemctl start php8.1-fpm
Enable the service to start at boot time.
console$ sudo systemctl enable php8.1-fpm
View the service status and verify that it's running.
console$ sudo systemctl status php8.1-fpm
Output:
● php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2025-04-06 11:11:44 UTC; 12min ago Docs: man:php-fpm8.1(8) Process: 14282 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/8.1/fpm/pool.d/www.conf 81 (code=exited, status=0/SUCCESS) Main PID: 14279 (php-fpm8.1) Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/sec" Tasks: 3 (limit: 9385) Memory: 7.4M CPU: 87ms CGroup: /system.slice/php8.1-fpm.service ├─14279 "php-fpm: master process (/etc/php/8.1/fpm/php-fpm.conf)" ├─14280 "php-fpm: pool www" └─14281 "php-fpm: pool www"
Configure PHP-FPM
PHP-FPM optimizes PHP application performance on your server by managing pools based on available memory. Follow the steps below to configure PHP-FPM with Apache and adjust the default pool settings for better resource management.
Enable the required Apache modules.
console$ sudo a2enmod proxy_fcgi setenvif
The above command enables the following modules on your web server:
proxy_fcgi: Enables Apache to work as a proxy with PHP-FPM.setenvif: Sets the necessary environment variables to enable connections between Apache and PHP-FPM.
Enable the default PHP-FPM configuration.
console$ sudo a2enconf php8.1-fpm
Restart the Apache web server to apply the changes.
console$ sudo systemctl restart apache2
Switch to the PHP-FPM pool configurations directory.
console$ cd /etc/php/8.1/fpm/pool.d/
Open the default
www.confPHP-FPM pool configuration.console$ sudo nano /etc/php/8.1/fpm/pool.d/www.conf
Verify that the default PHP-FPM pool name
www.ini[www]Find and verify the following directives are set to
www-datato enable PHP-FPM to use the default web server user profile.iniuser = www-data group = www-data listen.owner = www-data listen.group = www-data
Locate the following pool configurations and adjust them according to your server's needs:
pm: Sets the process manager. Thedynamicvalue allows PHP child processes to scale dynamically.pm.start_servers: Defines the number of PHP child processes to start. Default is2.pm.max_children: Sets the maximum number of simultaneous active PHP child processes. Default is5.pm.min_spare_servers: Specifies the minimum number of idle PHP child processes. Default is1.pm.max_spare_servers: Sets the maximum number of idle PHP child processes. Default is3.pm.max_requests: Limits the number of requests a PHP child process can handle before being recycled.
Save and close the file.
Restart the PHP-FPM service to apply your configuration changes.
console$ sudo systemctl restart php8.1-fpm
Configure Apache with PHP-FPM
The Apache web server uses the mod_proxy_fcgi module to communicate with PHP-FPM via the UNIX socket or the default TCP port 9000, depending on your pool configuration. In the below steps, you’ll configure a new Apache virtual host and connect it to the PHP-FPM service using the UNIX socket.
Remove the default Apache virtual host configuration files.
console$ sudo rm -rf /etc/apache2/sites-enabled/000-default.conf && sudo rm -rf /etc/apache2/sites-available/000-default.conf
Create a new Apache virtual host configuration file. For example,
app.example.com.conf.console$ sudo nano /etc/apache2/sites-available/app.example.com.conf
Add the following configurations to the file. Replace
app.example.comwith your actual domain.ini<VirtualHost *:80> ServerAdmin webmaster@app.example.com ServerName app.example.com DocumentRoot /var/www/html/app.example.com <Directory /var/www/html/app.example.com> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch \.php$> SetHandler "proxy:unix:/var/run/php/php8.1-fpm.sock|fcgi://localhost/" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/app.example.com_error.log CustomLog ${APACHE_LOG_DIR}/app.example.com_access.log combined </VirtualHost>
Save and close the file.
The configuration above creates an Apache virtual host that listens on port
80and serves content for theapp.example.comdomain. All PHP requests are forwarded to PHP-FPM via the/var/run/php/php8.1-fpm.sockUNIX socket. Key points in the configuration:<VirtualHost *:80>: Listens for connections on port80.<Directory /var/www/html/app.example.com>: Sets the web root directory.<FilesMatch \.php$>: Forwards PHP requests to the PHP-FPM socket/var/run/php/php8.1-fpm.sockusing FastCGI.ErrorLog,CustomLog: Specify custom paths for error and access logs.
Enable the new Apache virtual host configuration.
console$ sudo a2ensite app.example.com.conf
Test the Apache configuration for syntax errors.
console$ sudo apache2ctl configtest
Output:
Syntax OKCreate the virtual host web root directory
/var/www/html/app.example.comdefined in your configuration.console$ sudo mkdir -p /var/www/html/app.example.com
Create a new sample PHP file
info.php.console$ sudo nano /var/www/html/app.example.com/info.php
Add the following contents to the file.
php<?php phpinfo(); ?>
Save and close the file.
The above application code displays information about the PHP version and installed modules on your server when accessed in a web browser.
Restart Apache to apply your configuration changes.
console$ sudo systemctl restart apache2
Access your domain using a web browser such as Chrome and append the
/info.phppath to verify that your PHP application information displays.http://app.example.com/info.php
Secure the server
Follow the steps below to configure the default firewall to allow connections on port 80 for HTTP and set up trusted SSL certificates to enable HTTPS on port 443.
Configure the Firewall
View the default firewall status and verify that it's active.
console$ sudo ufw status
Output:
Status: active ...View the available UFW application profiles and verify that the Apache profile is available.
console$ sudo ufw app list
Output:
Apache Apache Full Apache Secure OpenSSHAllow the
Apache Fullprofile to enable HTTP and HTTPS connections on the server.console$ sudo ufw allow "Apache Full"
Reload the firewall rules to apply changes.
console$ sudo ufw reload
View the UFW status and verify that the Apache connection rules are available in the firewall table.
console$ sudo ufw status
Output:
To Action From -- ------ ---- 1022/tcp ALLOW Anywhere Apache Full ALLOW Anywhere 1022/tcp (v6) ALLOW Anywhere (v6) Apache Full (v6) ALLOW Anywhere (v6)
Generate Trusted Let's Encrypt SSL Certificates
Install the Certbot Let's Encrypt client tool using Snap.
console$ sudo snap install certbot --classic
Request a new SSL certificate for your domain. Replace
app.example.comwith your actual domain andadmin@example.comwith your email.console$ sudo certbot --apache -d app.example.com -m admin@example.com --agree-tos
Test the Certbot automatic SSL certificate renewal process.
console$ sudo certbot renew --dry-run
Restart the Apache web server to apply your SSL configuration changes.
console$ sudo systemctl restart apache2
Test the LAMP Stack Installation
Follow the steps below to create a new sample table in your content_database MySQL database, connect it to your PHP application, and display the message Hello World! Greetings from Vultr in a web browser.
Log in to the MySQL console using the sample database user
dbadminyou created earlier.console$ mysql -u dbadmin -p
Enter the
dbadminuser password when prompted to access the MySQL console.Switch to the sample database
content_database.sqlmysql> USE content_database;
Create a new sample table
messageswith two columns,content_idandcontentto store your data.sqlmysql> CREATE TABLE IF NOT EXISTS messages ( content_id INT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(255) NOT NULL );
The above SQL query creates a new table with the following specifications:
content_id: Contains numeric values and automatically increments unique data on each new row.content: Holds mixed content data with up to255characters.
Insert new data to the
messagestable. For example, add a newHello World! Greetings from Vultrstring to thecontentcolumn.sqlmysql> INSERT INTO messages (content) VALUES ('Hello World! Greetings from Vultr');
View all table data to verify that the new string is added to the column.
sqlmysql> SELECT * from messages;
Output:
+----+------------------------------------------+ | content_id | content | +------------+-----------------------------------+ | 1 | Hello World! Greetings from Vultr | +------------+-----------------------------------+ 1 row in set (0.00 sec)Exit the MySQL console.
sqlmysql> EXIT;
Create a new sample PHP application file
setup.phpin your web root directory/var/www/html/app.example.com.console$ sudo nano /var/www/html/app.example.com/setup.php
Add the following contents to the file.
php<?php $hostname = "localhost"; $username = "dbadmin"; $password = "Strong@@password123"; $dbname = "content_database"; // Establish Connection $conn = new mysqli($hostname, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection Failed: " . $conn->connect_error); } $sql = "SELECT content FROM messages"; $result = $conn->query($sql); if ($result && $result->num_rows > 0) { $row = $result->fetch_assoc(); echo "<h2 style='color: blue; text-align: center; margin-bottom: 15px;'>" . htmlspecialchars($row["content"]) . "</h2>"; } else { echo "<h1>No records found.</h1>"; } $conn->close(); ?>
Save and close the file.
The PHP code connects to the
content_databaseMySQL database and displays data from the content column in the messages table. If no records are found, it showsNo records found, orConnection Failedif the database connection fails.Grant the Apache user
www-datafull privileges to your web root directory.console$ sudo chown -R www-data:www-data /var/www/html/app.example.com/
Access your domain using the
/setup.phppath in your web browser to verify that your PHP application displays theHello World! Greetings from Vultrstring content from your MySQL database.https://app.example.com/setup.php
Conclusion
You’ve set up Apache, MySQL, and PHP (LAMP stack) on your Ubuntu 22.04 server, and created sample applications to test the connections between all components. To explore more configuration options, refer to the official documentation for each component: