Install and Configure the LAMP Stack on Ubuntu 20.04
The LAMP stack—Linux, Apache, MySQL, and PHP—is a suite of open-source tools for building and hosting dynamic web applications. In this setup:
- Linux serves as the operating system
- Apache delivers web content
- MySQL manages databases
- PHP processes dynamic content
This guide shows how to install and configure the LAMP stack on Ubuntu 20.04 to host web applications on your server.
Prerequisites
Before starting, make sure you have the following:
- An Ubuntu 20.04 server
- SSH access as a non-root user with sudo privileges
- A new A record for your domain pointing to the server’s IP address
- An updated server
Install Apache
Ubuntu 20.04’s default APT repositories provide the latest Apache version. Follow these steps to update the package index and install Apache.
Update the package index
$ sudo apt update
Install Apache
$ sudo apt install apache2 -y
Start Apache
$ sudo systemctl start apache2
Enable Apache to start on boot
$ sudo systemctl enable apache2
Check Apache service status
$ sudo systemctl status apache2
Sample 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 start
Allow HTTP traffic through the firewall
$ sudo ufw allow 80/tcp
Open a browser and visit your domain or server IP (for example http://SERVER-IP) to confirm that Apache’s default page appears.
Install MySQL
MySQL provides the database backend for the LAMP stack, though you can use MariaDB as an alternative. Ubuntu 20.04’s default repositories contain the latest MySQL package. Use these commands to install MySQL via APT.
Install the MySQL server package
$ sudo apt install -y mysql-server
Enable MySQL to start on boot
$ sudo systemctl enable mysql
Start MySQL
$ sudo systemctl start mysql
Check MySQL service status
$ sudo systemctl status mysql
Sample 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/mysqld
If the status shows active (running), the MySQL server is operational.
Secure the MySQL installation
Run the MySQL secure installation script to remove unsafe defaults and enable authentication.
$ sudo mysql_secure_installation
When prompted, configure the options as follows:
- Enable password validation: y
- Choose password policy: 2 for strong passwords
- Remove anonymous users: y
- Disallow remote root login: y
- Remove test database: y
- Reload privilege tables: y
You should see a confirmation similar to “Success. All done!” when the configuration is complete.
Log in to the MySQL console as root
$ sudo mysql
Set a strong password for the root user
Replace Strong@@password123 with your own secure password.
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Strong@@password123';
Apply privilege changes
mysql> FLUSH PRIVILEGES;
Exit the MySQL console
mysql> EXIT;
Log in again with the new password
$ mysql -u root -p
Create a sample database
mysql> CREATE database content_database;
List databases to confirm
mysql> SHOW DATABASES;
Expected output:
+--------------------+
| Database |
+--------------------+
| information_schema |
| content_database |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.01 sec)
Create a new MySQL user
Create a user such as dbadmin with a strong password (replace the example password with your own).
mysql> CREATE USER 'dbadmin'@'localhost' IDENTIFIED BY 'Strong@@password123';
Grant privileges on the database
mysql> GRANT ALL PRIVILEGES ON content_database.* TO 'dbadmin'@'localhost';
Apply privilege changes
mysql> FLUSH PRIVILEGES;
Exit the MySQL shell
mysql> EXIT;
Install PHP and Configure PHP-FPM
PHP is a crucial part of the LAMP stack, responsible for processing dynamic content and communicating with the MySQL database. PHP-FPM (FastCGI Process Manager) improves performance by handling PHP requests through a pool of worker processes.
Install PHP and PHP-FPM
$ sudo apt install -y php php-fpm
Install common PHP extensions
$ sudo apt install -y php-mysql php-opcache php-cli libapache2-mod-php
This command installs:
- php-mysql: Enables PHP to connect to MySQL
- libapache2-mod-php: Allows Apache to execute PHP scripts
- php-opcache: Caches precompiled PHP scripts for faster performance
- php-cli: Provides command line interface access to PHP
Check the PHP version
$ php -v
Sample output:
PHP 7.4.3-4ubuntu2.29 (cli) (built: Mar 25 2025 18:57:03) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.3-4ubuntu2.29, Copyright (c), by Zend Technologies
Start and enable PHP-FPM
Start the PHP-FPM service according to your installed version (for example, PHP 7.4):
$ sudo systemctl start php7.4-fpm
$ sudo systemctl enable php7.4-fpm
Check PHP-FPM status
$ sudo systemctl status php7.4-fpm
Sample output:
● php7.4-fpm.service - The PHP 7.4 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php7.4-fpm.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2025-04-06 11:11:45 UTC; 10min ago
Docs: man:php-fpm7.4(8)
Process: 27868 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/7.4/fpm/pool.d/www.conf 74 (code=exited, status=0/SUCCESS)
Main PID: 27851 (php-fpm7.4)
Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/sec"
Tasks: 3 (limit: 9415)
Memory: 7.3M
CGroup: /system.slice/php7.4-fpm.service
├─27851 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)
├─27866 php-fpm: pool www
└─27867 php-fpm: pool www
Configure PHP-FPM
PHP-FPM improves PHP application performance by managing worker process pools. Adjust the default pool settings based on your server’s memory. Follow these steps to integrate PHP-FPM with Apache and fine-tune the pool configuration.
Enable Apache modules required for PHP-FPM
$ sudo a2enmod proxy_fcgi setenvif
This command enables:
- proxy_fcgi: Allows Apache to act as a proxy for PHP-FPM
- setenvif: Sets environment variables to connect Apache and PHP-FPM
Enable default PHP-FPM configuration
$ sudo a2enconf php7.4-fpm
Restart Apache to apply changes
$ sudo systemctl restart apache2
Edit PHP-FPM pool configuration
$ cd /etc/php/7.4/fpm/pool.d/
$ sudo nano /etc/php/7.4/fpm/pool.d/www.conf
Verify the default pool name:
[www]
Ensure the following directives are set to www-data:
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
Adjust these settings as needed:
- pm: Set to dynamic to let PHP child processes scale with demand.
- pm.start_servers: Number of child processes to start (default: 2).
- pm.max_children: Maximum concurrent child processes (default: 5).
- pm.min_spare_servers: Minimum idle child processes (default: 1).
- pm.max_spare_servers: Maximum idle child processes (default: 3).
- pm.max_requests: Number of requests a child process serves before recycling.
Save and close the file.
Restart PHP-FPM to apply changes
$ sudo systemctl restart php7.4-fpm
Configure Apache with PHP-FPM
Apache interacts with PHP-FPM through the mod_proxy_fcgi module, using either a UNIX socket or the default TCP port 9000. Follow these steps to create a new Apache virtual host that connects to PHP-FPM through the UNIX socket.
Remove the default Apache virtual host configuration
$ 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, create app.example.com.conf:
$ sudo nano /etc/apache2/sites-available/app.example.com.conf
Add the following content (replace app.example.com with your actual domain):
ServerAdmin webmaster@app.example.com
ServerName app.example.com
DocumentRoot /var/www/html/app.example.com
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
SetHandler "proxy:unix:/var/run/php/php7.4-fpm.sock|fcgi://localhost/"
ErrorLog ${APACHE_LOG_DIR}/app.example.com_error.log
CustomLog ${APACHE_LOG_DIR}/app.example.com_access.log combined
This configuration:
- Listens on port 80 for the domain
app.example.com. - Sets the web root to
/var/www/html/app.example.com. - Forwards PHP file requests to the PHP-FPM socket via FastCGI.
- Defines custom paths for error and access logs.
Enable the new Apache virtual host
$ sudo a2ensite app.example.com.conf
Test the Apache configuration
$ sudo apache2ctl configtest
Sample output:
Syntax OK
Create the virtual host web root directory
$ sudo mkdir -p /var/www/html/app.example.com
Create a sample PHP file
$ sudo nano /var/www/html/app.example.com/info.php
Add the following PHP code:
This script displays PHP version details and enabled modules in your browser.
Restart Apache to apply changes
$ sudo systemctl restart apache2
Finally, open your domain in a browser (for example http://app.example.com/info.php) to confirm that the PHP information page is displayed.
Secure the Server
Ubuntu 20.04 servers can have the Uncomplicated Firewall (UFW) enabled. Apache serves dynamic web content over HTTP port 80, while MySQL (3306) and PHP-FPM (9000) use internal TCP ports. Follow these steps to allow traffic on port 80 and configure trusted SSL certificates for HTTPS on port 443.
Configure the Firewall
Check that the firewall is active:
$ sudo ufw status
Sample output:
Status: active
...
List available UFW application profiles:
$ sudo ufw app list
Sample output:
Apache
Apache Full
Apache Secure
OpenSSH
Allow the Apache Full profile to enable both HTTP and HTTPS:
$ sudo ufw allow "Apache Full"
Reload the firewall to apply the changes:
$ sudo ufw reload
Check UFW status again to confirm that Apache connection rules are active:
$ sudo ufw status
Sample 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 client using Snap:
$ sudo snap install certbot --classic
Request a new SSL certificate (replace app.example.com and admin@example.com with your own domain and email):
$ sudo certbot --apache -d app.example.com -m admin@example.com --agree-tos
Sample output:
Requesting a certificate for app.example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/app.example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/app.example.com/privkey.pem
This certificate expires on 2025-07-05.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for app.example.com to /etc/apache2/sites-available/000-default-le-ssl.conf
Congratulations! You have successfully enabled HTTPS on https://app.example.com
Test the automatic SSL renewal process:
$ sudo certbot renew --dry-run
Restart Apache to apply the SSL configuration:
$ sudo systemctl restart apache2
Test the LAMP Stack Installation
Create a sample table in the existing content_database database to display “Hello World! Greetings from centron” via a PHP application.
Log in to MySQL as dbadmin
$ mysql -u dbadmin -p
Enter the password for the dbadmin user when prompted.
Switch to the sample database
mysql> USE content_database;
Create the messages table
mysql> CREATE TABLE IF NOT EXISTS messages (
content_id INT AUTO_INCREMENT PRIMARY KEY,
content VARCHAR(255) NOT NULL
);
This table includes:
- content_id: Auto-incrementing primary key
- content: Text column for up to 255 characters
Insert a sample row
mysql> INSERT INTO messages (content) VALUES ('Hello World! Greetings from centron');
View the table data
mysql> SELECT * from messages;
Sample output:
+----+------------------------------------------+
| content_id | content |
+------------+-----------------------------------+
| 1 | Hello World! Greetings from centron|
+------------+-----------------------------------+
1 row in set (0.00 sec)
Exit the MySQL console
mysql> EXIT;
Create a sample PHP application
Create the setup.php file in your web root directory:
$ sudo nano /var/www/html/app.example.com/setup.php
Add the following code:
<?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();
?>
This script connects to the content_database and retrieves data from the messages table. If no records are found, it displays “No records found.” If the database connection fails, it shows a connection error.
Set directory permissions
$ sudo chown -R www-data:www-data /var/www/html/app.example.com/
Open your domain in a browser (for example https://app.example.com/setup.php) to confirm that the PHP application displays “Hello World! Greetings from centron”.
Conclusion
You have successfully installed and configured Apache, MySQL, and PHP (LAMP stack) on Ubuntu 20.04. You also created sample dynamic applications to test the integration of all components and ensured the server runs securely.


