Complete LAMP Stack Setup Guide: Apache, MySQL, PHP on Ubuntu
Build a secure, production-ready web server with the classic LAMP stack: Linux, Apache, MySQL, and PHP.
Table of Contents
- Introduction
- What is a LAMP Stack?
- Prerequisites
- Initial Server Setup
- Installing Apache Web Server
- Firewall Configuration
- SSL Certificate with Let’s Encrypt
- Apache Security Hardening
- Installing MySQL Database Server
- Installing PHP
- Performance Optimization
- Monitoring and Maintenance
- Troubleshooting
Introduction
The LAMP stack has been the backbone of web hosting for over two decades. Despite the rise of newer technologies, LAMP remains a powerful, stable, and well-documented solution for hosting everything from simple blogs to complex web applications.
This guide provides a complete, production-ready LAMP stack setup with modern security practices, SSL certificates, and performance optimizations.
What you’ll build:
- Apache 2.4 web server with virtual hosts
- MySQL 8.x database server with security hardening
- PHP 8.x with optimized configuration
- Automatic SSL/TLS certificates via Let’s Encrypt
- phpMyAdmin for database management
- Comprehensive security measures
Time to complete: 60-90 minutes
What is a LAMP Stack?
LAMP is an acronym representing four open-source components:
- Linux - Operating system (Ubuntu in this guide)
- Apache - Web server that handles HTTP requests
- MySQL - Relational database management system
- PHP - Server-side scripting language
How they work together:
- User requests a web page via browser
- Apache receives the request
- If the page requires dynamic content, Apache passes it to PHP
- PHP processes the request, queries MySQL if needed
- PHP generates HTML and returns it to Apache
- Apache sends the HTML to the user’s browser
Alternative stacks:
- LEMP - Replace Apache with Nginx
- MEAN - MongoDB, Express, Angular, Node.js
- MERN - MongoDB, Express, React, Node.js
Prerequisites
Before starting, ensure you have:
Server Requirements
- Ubuntu Server 22.04 LTS or later (20.04 also works)
- Minimum: 1GB RAM, 1 CPU core, 10GB disk
- Recommended: 2GB RAM, 2 CPU cores, 20GB+ disk
- Root or sudo access via SSH
- Public-facing static IPv4 address
Required Preparations
- Locales configured:
sudo dpkg-reconfigure localesSelect
en_US.UTF-8or your preferred locale. - System updated:
sudo apt update && sudo apt upgrade -y - Domain name (optional but recommended for SSL)
- DNS A record pointing to your server IP
What You’ll Need to Know
- Basic Linux command line operations
- Understanding of SSH
- Basic networking concepts
- Text editing (nano or vim)
Initial Server Setup
Creating a Non-Root User
Running services as root is a security risk. Always create a dedicated user.
Step 1: Create a new user
# SSH into your server as root
ssh root@YOUR_SERVER_IP
# Create a new user (replace 'username' with your choice)
adduser username
You’ll be prompted for:
- Password - Use a strong password!
- Full Name - Optional (press Enter to skip)
- Other information - Optional (press Enter to skip)
Step 2: Grant sudo privileges
usermod -aG sudo username
What this does:
-a= append (preserves existing groups)-G= supplementary groupssudo= grants administrative privileges
Step 3: Test the new user
# Switch to the new user
su - username
# Test sudo access
sudo whoami
# Output should be: root
Step 4: Set strong password policies (optional but recommended)
# Install password quality checking library
sudo apt install libpam-pwquality -y
# Edit PAM configuration
sudo nano /etc/pam.d/common-password
Find the line with pam_pwquality.so and modify it:
password requisite pam_pwquality.so retry=3 minlen=12 difok=3 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1
Parameters explained:
retry=3- Allow 3 attemptsminlen=12- Minimum 12 charactersdifok=3- At least 3 characters different from old passworducredit=-1- At least 1 uppercase letterlcredit=-1- At least 1 lowercase letterdcredit=-1- At least 1 digitocredit=-1- At least 1 special character
SSH Key Authentication
SSH keys are more secure than passwords and enable convenient authentication.
On your local machine (not the server):
# Generate SSH key pair (if you don't have one)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Or for RSA (more compatible but larger):
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# Copy your public key to the server
ssh-copy-id username@YOUR_SERVER_IP
Test SSH key authentication:
ssh username@YOUR_SERVER_IP
# Should log in without password
Disable password authentication (after verifying SSH keys work):
sudo nano /etc/ssh/sshd_config
Change or add these lines:
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
Restart SSH:
sudo systemctl restart sshd
Important: Test SSH access from a second terminal before logging out, to ensure you don’t lock yourself out!
Installing Apache Web Server
Apache HTTP Server is the world’s most popular web server software.
Basic Installation
sudo apt update
sudo apt install apache2 -y
Verify Installation
# Check Apache version
apache2 -v
# Check status
sudo systemctl status apache2
# Enable Apache to start on boot
sudo systemctl enable apache2
Basic Apache Commands
# Start Apache
sudo systemctl start apache2
# Stop Apache
sudo systemctl stop apache2
# Restart Apache (stops then starts)
sudo systemctl restart apache2
# Reload configuration (no downtime)
sudo systemctl reload apache2
# Check configuration syntax
sudo apache2ctl configtest
Test Apache
Open your browser and visit:
http://YOUR_SERVER_IP
You should see the Apache2 Ubuntu Default Page.
Basic Configuration
Suppress ServerName warning:
Edit the Apache configuration:
sudo nano /etc/apache2/apache2.conf
Add this line at the end:
ServerName YOUR_SERVER_IP
Or if you have a domain:
ServerName example.com
Test configuration:
sudo apache2ctl configtest
# Output should be: Syntax OK
Apply changes:
sudo systemctl reload apache2
Understanding Apache Directory Structure
/etc/apache2/
├── apache2.conf # Main configuration file
├── ports.conf # Port configurations
├── sites-available/ # Available site configurations
│ ├── 000-default.conf # Default HTTP site
│ └── default-ssl.conf # Default HTTPS site
├── sites-enabled/ # Enabled sites (symlinks)
├── mods-available/ # Available modules
├── mods-enabled/ # Enabled modules (symlinks)
├── conf-available/ # Additional configurations
└── conf-enabled/ # Enabled configurations
/var/www/ # Web root directory
└── html/ # Default document root
/var/log/apache2/ # Log files
├── access.log
└── error.log
Virtual Hosts Setup
Virtual hosts allow you to host multiple websites on one server.
Step 1: Create Directory Structure
# Create directory for your site
sudo mkdir -p /var/www/example.com/public_html
# Create a simple test page
sudo nano /var/www/example.com/public_html/index.html
Add this HTML:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Example.com!</title>
</head>
<body>
<h1>Success! The example.com virtual host is working!</h1>
<p>This is a test page for your Apache virtual host.</p>
</body>
</html>
Step 2: Set Proper Permissions
Assign ownership:
sudo chown -R $USER:www-data /var/www/example.com
Set the setgid bit:
This ensures new files inherit the www-data group:
sudo find /var/www/example.com -type d -exec chmod g+s {} \;
Set directory permissions:
sudo chmod -R 755 /var/www
Permission breakdown:
755means:- Owner: read, write, execute (7)
- Group: read, execute (5)
- Others: read, execute (5)
Step 3: Create Virtual Host Configuration
# Copy the default configuration as a template
sudo cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/example.com.conf
# Edit the new configuration
sudo nano /etc/apache2/sites-available/example.com.conf
Replace the content with:
<VirtualHost *:80>
ServerAdmin admin@example.com
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
Configuration explained:
ServerAdmin- Email for server administratorServerName- Primary domain nameServerAlias- Alternative domain names (www subdomain)DocumentRoot- Directory containing website filesOptions -Indexes- Disable directory listing (security)AllowOverride All- Enable .htaccess filesRequire all granted- Allow access to this directory
Step 4: Enable the Virtual Host
# Disable the default site
sudo a2dissite 000-default.conf
# Enable your new site
sudo a2ensite example.com.conf
# Test configuration
sudo apache2ctl configtest
# Reload Apache
sudo systemctl reload apache2
Step 5: Test Your Virtual Host
Visit your domain or IP:
http://example.com
You should see your test page.
Firewall Configuration
Ubuntu’s UFW (Uncomplicated Firewall) provides simple firewall management.
Basic UFW Setup
# Install UFW if not present
sudo apt install ufw -y
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Check available Apache profiles
sudo ufw app list
Output should show:
Available applications:
Apache
Apache Full
Apache Secure
OpenSSH
Apache profiles explained:
Apache- Opens port 80 (HTTP only)Apache Full- Opens ports 80 and 443 (HTTP and HTTPS)Apache Secure- Opens port 443 only (HTTPS only)
Allow Required Services
# Allow SSH (CRITICAL: Do this before enabling firewall!)
sudo ufw allow OpenSSH
# Allow Apache Full (HTTP and HTTPS)
sudo ufw allow in "Apache Full"
# Enable the firewall
sudo ufw enable
# Type 'y' when prompted
Verify Firewall Status
sudo ufw status verbose
Expected output:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Apache Full ALLOW Anywhere
Additional Firewall Rules
Allow specific ports:
# MySQL (if allowing remote database connections)
sudo ufw allow 3306/tcp
# Custom application port
sudo ufw allow 8080/tcp
Allow from specific IP:
sudo ufw allow from 203.0.113.10 to any port 22
Rate limiting SSH (prevents brute force):
# First remove regular SSH rule if present
sudo ufw delete allow OpenSSH
# Add rate-limited rule
sudo ufw limit OpenSSH
SSL Certificate with Let’s Encrypt
Let’s Encrypt provides free, automated SSL/TLS certificates.
Install Certbot
Modern installation using snap:
# Ensure snap is installed
sudo apt install snapd -y
# Update snap
sudo snap install core
sudo snap refresh core
# Remove old certbot if present
sudo apt remove certbot -y
# Install certbot via snap
sudo snap install --classic certbot
# Create symbolic link
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Enable Required Apache Modules
sudo a2enmod ssl
sudo a2enmod headers
sudo systemctl reload apache2
Obtain SSL Certificate
Prerequisites:
- Your domain must resolve to your server’s IP
- Apache must be running
- Port 80 must be accessible
Run Certbot:
sudo certbot --apache -d example.com -d www.example.com
During the process:
- Enter email address (for renewal notices)
- Agree to Terms of Service
- Choose whether to share email with EFF
- Select option 2: Redirect all HTTP to HTTPS (recommended)
What Certbot does:
- Obtains SSL certificate from Let’s Encrypt
- Modifies Apache virtual host configuration
- Configures automatic HTTPS redirect
- Sets up automatic renewal
Verify SSL Certificate
Visit your domain:
https://example.com
You should see a padlock icon in the browser.
Check certificate details:
sudo certbot certificates
Test Auto-Renewal
Certificates expire after 90 days. Certbot automatically renews them.
Test renewal process:
sudo certbot renew --dry-run
If successful, automatic renewal is configured.
Manual renewal (usually not needed):
sudo certbot renew
Certificate Files Location
/etc/letsencrypt/
├── live/
│ └── example.com/
│ ├── fullchain.pem # Certificate + intermediate
│ ├── privkey.pem # Private key
│ ├── cert.pem # Certificate only
│ └── chain.pem # Intermediate certificate
└── renewal/
└── example.com.conf # Renewal configuration
Apache Security Hardening
Disable Server Signature
Hiding Apache version information reduces information disclosure:
sudo nano /etc/apache2/conf-available/security.conf
Change these lines:
ServerTokens Prod
ServerSignature Off
Enable the configuration:
sudo a2enconf security
sudo systemctl reload apache2
Protect Against Logjam Attack
The Logjam attack targets the Diffie-Hellman key exchange. Generate strong DH parameters:
Create DH parameters directory:
sudo mkdir -p /etc/ssl/private
sudo chmod 710 /etc/ssl/private
Generate DH parameters (this takes time):
cd /etc/ssl/private
sudo openssl dhparam -out dhparams.pem 2048
sudo chmod 600 dhparams.pem
Configure Apache SSL:
sudo nano /etc/apache2/mods-available/ssl.conf
Find the SSL configuration section and update:
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder on
SSLCompression off
SSLSessionTickets off
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"
# Add DH parameters
SSLOpenSSLConfCmd DHParameters "/etc/ssl/private/dhparams.pem"
Protect Against HTTPoxy Vulnerability
Enable headers module and create protection:
# Enable headers module
sudo a2enmod headers
# Create configuration file
sudo nano /etc/apache2/conf-available/httpoxy.conf
Add:
<IfModule mod_headers.c>
RequestHeader unset Proxy early
</IfModule>
Enable and reload:
sudo a2enconf httpoxy
sudo systemctl reload apache2
Security Headers
Create a security headers configuration:
sudo nano /etc/apache2/conf-available/security-headers.conf
Add:
<IfModule mod_headers.c>
# Prevent clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# Prevent MIME type sniffing
Header always set X-Content-Type-Options "nosniff"
# Enable XSS filter
Header always set X-XSS-Protection "1; mode=block"
# Referrer policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Content Security Policy (adjust as needed)
Header always set Content-Security-Policy "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"
# HSTS (only for HTTPS)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>
Enable:
sudo a2enconf security-headers
sudo systemctl reload apache2
Disable Directory Listing
Prevent users from seeing directory contents:
sudo nano /etc/apache2/apache2.conf
Find <Directory /var/www/> and ensure it has:
<Directory /var/www/>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
Hide Sensitive Files
Create .htaccess protection:
sudo nano /var/www/example.com/public_html/.htaccess
Add:
# Deny access to hidden files
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# Protect sensitive files
<FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|orig|psd|sh|sql|sw[op])|~)$">
Require all denied
</FilesMatch>
Installing MySQL Database Server
MySQL is a powerful relational database management system.
Install MySQL
sudo apt update
sudo apt install mysql-server -y
Start and Enable MySQL
sudo systemctl start mysql
sudo systemctl enable mysql
sudo systemctl status mysql
Securing MySQL
Run the security script:
sudo mysql_secure_installation
You’ll be prompted for:
- VALIDATE PASSWORD component - Choose Yes
- Level 0 (Low): Length >= 8
- Level 1 (Medium): Length >= 8, mixed case, numbers, special chars
- Level 2 (Strong): Above + dictionary check
- Recommended: Level 1 or 2
-
Set root password - Enter a strong password
-
Remove anonymous users - Choose Yes
-
Disallow root login remotely - Choose Yes
-
Remove test database - Choose Yes
- Reload privilege tables - Choose Yes
Initial MySQL Configuration
Access MySQL:
sudo mysql -u root -p
Check authentication method:
SELECT user, host, plugin FROM mysql.user;
Modern MySQL uses auth_socket for root by default, which is secure but requires sudo. If you need password authentication:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_strong_password';
FLUSH PRIVILEGES;
EXIT;
Note: Using auth_socket (default) is more secure for local access.
User Management Best Practices
Never use root for applications. Create dedicated users for each application.
Create a Database and User
-- Log into MySQL
sudo mysql -u root -p
-- Create a database
CREATE DATABASE exampledb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Create a user with password
CREATE USER 'exampleuser'@'localhost' IDENTIFIED BY 'secure_password_here';
-- Grant privileges
GRANT ALL PRIVILEGES ON exampledb.* TO 'exampleuser'@'localhost';
-- Apply changes
FLUSH PRIVILEGES;
-- Verify grants
SHOW GRANTS FOR 'exampleuser'@'localhost';
-- Exit
EXIT;
Principle of Least Privilege
Grant only necessary permissions:
-- Read-only access
GRANT SELECT ON exampledb.* TO 'readonly_user'@'localhost';
-- Specific operations
GRANT SELECT, INSERT, UPDATE ON exampledb.* TO 'app_user'@'localhost';
-- All privileges except GRANT
GRANT ALL PRIVILEGES ON exampledb.* TO 'admin_user'@'localhost';
-- Revoke permissions
REVOKE UPDATE ON exampledb.* FROM 'app_user'@'localhost';
Remote Access (Use with Caution)
Only if absolutely necessary:
-- Create user that can connect from specific IP
CREATE USER 'remote_user'@'203.0.113.10' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT ON exampledb.* TO 'remote_user'@'203.0.113.10';
FLUSH PRIVILEGES;
Configure MySQL to listen on external interface:
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
Change:
bind-address = 127.0.0.1
To:
bind-address = 0.0.0.0
Add firewall rule:
sudo ufw allow from 203.0.113.10 to any port 3306
Restart MySQL:
sudo systemctl restart mysql
Rename Root User (Optional Security)
RENAME USER 'root'@'localhost' TO 'admin_root'@'localhost';
FLUSH PRIVILEGES;
Remember to use the new username:
sudo mysql -u admin_root -p
Installing PHP
PHP is the scripting language that powers dynamic web applications.
Install PHP and Extensions
# Install PHP and common extensions
sudo apt install -y \
php \
libapache2-mod-php \
php-mysql \
php-cli \
php-common \
php-mbstring \
php-gd \
php-intl \
php-xml \
php-zip \
php-curl \
php-bcmath \
php-json \
php-soap
Essential extensions explained:
libapache2-mod-php- Apache module for PHPphp-mysql- MySQL database supportphp-cli- Command line interfacephp-mbstring- Multibyte string handlingphp-gd- Image manipulationphp-xml- XML processingphp-curl- HTTP requestsphp-zip- ZIP file handling
Verify PHP Installation
php -v
Expected output:
PHP 8.1.x (cli) (built: ...)
Configure Apache to Prefer PHP
sudo nano /etc/apache2/mods-enabled/dir.conf
Move index.php to the first position:
Before:
<IfModule mod_dir.c>
DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
</IfModule>
After:
<IfModule mod_dir.c>
DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htm
</IfModule>
Reload Apache:
sudo systemctl reload apache2
Test PHP Processing
Create a PHP info page:
sudo nano /var/www/example.com/public_html/info.php
Add:
<?php
phpinfo();
?>
Visit:
http://example.com/info.php
You should see a PHP information page.
Security: Delete this file after testing:
sudo rm /var/www/example.com/public_html/info.php
PHP Configuration
Main PHP configuration file:
/etc/php/8.1/apache2/php.ini
Recommended PHP Settings
sudo nano /etc/php/8.1/apache2/php.ini
Important settings to adjust:
; Maximum execution time (seconds)
max_execution_time = 300
; Maximum memory per script
memory_limit = 256M
; Maximum POST data size
post_max_size = 64M
; Maximum upload file size
upload_max_filesize = 64M
; Maximum number of files per upload
max_file_uploads = 20
; Display errors (disable in production!)
display_errors = Off
display_startup_errors = Off
; Log errors
log_errors = On
error_log = /var/log/php/error.log
; Timezone
date.timezone = America/New_York
; Session security
session.cookie_httponly = 1
session.use_strict_mode = 1
session.cookie_secure = 1 ; Only if using HTTPS
Create PHP error log directory:
sudo mkdir /var/log/php
sudo chown www-data:www-data /var/log/php
Apply changes:
sudo systemctl reload apache2
Installing phpMyAdmin
phpMyAdmin provides a web-based MySQL management interface.
Install phpMyAdmin
sudo apt update
sudo apt install phpmyadmin php-mbstring php-zip php-gd php-json php-curl -y
During installation:
- Web server: Select apache2 (use Space to select, Tab to OK)
- Configure database: Select Yes
- Database password: Enter and confirm a strong password
Enable PHP Extensions
sudo phpenmod mbstring
sudo systemctl reload apache2
Configure Apache for phpMyAdmin
The installer should automatically configure Apache, but verify:
# Check if configuration exists
ls -l /etc/apache2/conf-enabled/phpmyadmin.conf
# If missing, enable it manually
sudo ln -s /etc/apache2/conf-available/phpmyadmin.conf /etc/apache2/conf-enabled/phpmyadmin.conf
sudo systemctl reload apache2
Access phpMyAdmin
Visit:
https://example.com/phpmyadmin
Login credentials:
- Username:
root(or your MySQL admin user) - Password: Your MySQL root password
Securing phpMyAdmin
phpMyAdmin is a common attack target. Implement these security measures:
1. Change Access URL
sudo nano /etc/apache2/conf-available/phpmyadmin.conf
Change the alias:
Alias /secretadmin /usr/share/phpmyadmin
Reload Apache:
sudo systemctl reload apache2
Now access via: https://example.com/secretadmin
2. Enable .htaccess Protection
sudo nano /etc/apache2/conf-available/phpmyadmin.conf
Add AllowOverride All to the Directory block:
<Directory /usr/share/phpmyadmin>
Options FollowSymLinks
DirectoryIndex index.php
AllowOverride All
# ... existing configuration ...
</Directory>
Create .htaccess file:
sudo nano /usr/share/phpmyadmin/.htaccess
Add:
AuthType Basic
AuthName "Restricted Access"
AuthUserFile /etc/phpmyadmin/.htpasswd
Require valid-user
Create password file:
sudo htpasswd -c /etc/phpmyadmin/.htpasswd admin
Enter a password when prompted.
Reload Apache:
sudo systemctl reload apache2
3. Restrict Access by IP (Optional)
sudo nano /etc/apache2/conf-available/phpmyadmin.conf
Add inside the <Directory> block:
<RequireAny>
Require ip YOUR_IP_ADDRESS
Require ip 192.168.1.0/24
</RequireAny>
Troubleshooting phpMyAdmin
Issue: Database tables missing
Create the phpMyAdmin configuration storage:
sudo mysql -u root -p < /usr/share/phpmyadmin/sql/create_tables.sql
Create phpMyAdmin control user:
sudo mysql -u root -p
CREATE USER 'pma'@'localhost' IDENTIFIED BY 'strong_password_here';
GRANT ALL PRIVILEGES ON phpmyadmin.* TO 'pma'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Update phpMyAdmin config:
sudo nano /etc/phpmyadmin/config.inc.php
Add or update:
$cfg['Servers'][$i]['controluser'] = 'pma';
$cfg['Servers'][$i]['controlpass'] = 'strong_password_here';
Performance Optimization
Enable Apache Modules
# Enable compression
sudo a2enmod deflate
# Enable caching
sudo a2enmod expires
sudo a2enmod headers
# Enable HTTP/2 (requires Apache 2.4.17+)
sudo a2enmod http2
# Reload Apache
sudo systemctl reload apache2
Configure Compression
sudo nano /etc/apache2/mods-available/deflate.conf
Add:
<IfModule mod_deflate.c>
# Compress HTML, CSS, JavaScript, Text, XML, fonts
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/font-woff
AddOutputFilterByType DEFLATE application/font-woff2
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
</IfModule>
Configure Browser Caching
sudo nano /etc/apache2/mods-available/expires.conf
Add:
<IfModule mod_expires.c>
ExpiresActive On
# Images
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
# CSS and JavaScript
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
# Fonts
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
# Default
ExpiresDefault "access plus 2 days"
</IfModule>
Enable HTTP/2
sudo a2enmod http2
# Add to your SSL virtual host
sudo nano /etc/apache2/sites-available/example.com-le-ssl.conf
Add after the <VirtualHost> opening tag:
Protocols h2 http/1.1
PHP Opcode Cache
PHP comes with OPcache built-in. Verify it’s enabled:
php -i | grep opcache.enable
Configure OPcache:
sudo nano /etc/php/8.1/apache2/conf.d/10-opcache.ini
Recommended settings:
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
MySQL Performance Tuning
Install MySQL Tuner:
sudo apt install mysqltuner -y
Run MySQL Tuner:
sudo mysqltuner
Follow its recommendations for optimizing MySQL.
Common MySQL optimizations:
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
Add or adjust:
[mysqld]
# InnoDB settings
innodb_buffer_pool_size = 512M # 70-80% of RAM for dedicated DB server
innodb_log_file_size = 128M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
# Query cache (deprecated in MySQL 8.0+)
# query_cache_type = 1
# query_cache_size = 64M
# Connection settings
max_connections = 200
thread_cache_size = 8
# Buffer settings
key_buffer_size = 32M
sort_buffer_size = 2M
read_buffer_size = 2M
Restart MySQL:
sudo systemctl restart mysql
Monitoring and Maintenance
Log Files
Apache logs:
# Access log
sudo tail -f /var/log/apache2/access.log
# Error log
sudo tail -f /var/log/apache2/error.log
# Virtual host specific
sudo tail -f /var/log/apache2/example.com-access.log
MySQL logs:
# Error log
sudo tail -f /var/log/mysql/error.log
# Slow query log (if enabled)
sudo tail -f /var/log/mysql/mysql-slow.log
PHP logs:
sudo tail -f /var/log/php/error.log
Monitoring Tools
Install monitoring tools:
# System monitoring
sudo apt install htop -y
# Apache monitoring
sudo apt install apache2-utils -y
# Network monitoring
sudo apt install iftop -y
Check Apache status:
sudo apache2ctl status
View current connections:
sudo netstat -plant | grep :80
sudo netstat -plant | grep :443
Regular Maintenance Tasks
Update system weekly:
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
Check disk space:
df -h
Check log file sizes:
sudo du -sh /var/log/apache2/*
sudo du -sh /var/log/mysql/*
Rotate logs manually if needed:
sudo logrotate -f /etc/logrotate.conf
Automated Backups
Backup script example:
sudo nano /usr/local/bin/backup-lamp.sh
Add:
#!/bin/bash
# Variables
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_USER="root"
MYSQL_PASS="your_password"
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup databases
mysqldump -u $MYSQL_USER -p$MYSQL_PASS --all-databases | gzip > $BACKUP_DIR/mysql_$DATE.sql.gz
# Backup web files
tar -czf $BACKUP_DIR/www_$DATE.tar.gz /var/www
# Backup Apache configs
tar -czf $BACKUP_DIR/apache_$DATE.tar.gz /etc/apache2
# Delete backups older than 7 days
find $BACKUP_DIR -name "*.gz" -mtime +7 -delete
echo "Backup completed: $DATE"
Make executable:
sudo chmod +x /usr/local/bin/backup-lamp.sh
Schedule with cron:
sudo crontab -e
Add:
# Daily backup at 2 AM
0 2 * * * /usr/local/bin/backup-lamp.sh >> /var/log/backup.log 2>&1
Troubleshooting
Common Apache Issues
Issue: Port 80/443 already in use
# Check what's using the port
sudo netstat -tulpn | grep :80
sudo netstat -tulpn | grep :443
# Stop conflicting service or kill process
sudo systemctl stop nginx # Example: if Nginx is running
Issue: “Permission denied” errors
# Fix ownership
sudo chown -R www-data:www-data /var/www/example.com
# Fix permissions
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
Issue: .htaccess not working
Ensure AllowOverride All is set in your virtual host configuration.
Issue: Apache won’t start
# Check syntax
sudo apache2ctl configtest
# Check error log
sudo tail -50 /var/log/apache2/error.log
# Check status
sudo systemctl status apache2
Common MySQL Issues
Issue: Can’t connect to MySQL
# Verify MySQL is running
sudo systemctl status mysql
# Check MySQL error log
sudo tail -50 /var/log/mysql/error.log
# Test connection
mysql -u root -p
Issue: “Access denied” errors
# Reset root password
sudo mysql
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'new_password';
FLUSH PRIVILEGES;
EXIT;
Issue: MySQL using too much memory
Adjust innodb_buffer_pool_size in /etc/mysql/mysql.conf.d/mysqld.cnf.
Common PHP Issues
Issue: PHP not processing, showing code
# Ensure mod_php is enabled
sudo a2enmod php8.1
# Restart Apache
sudo systemctl restart apache2
Issue: “Upload failed” errors
Check PHP upload settings in php.ini:
upload_max_filesizepost_max_sizememory_limitmax_execution_time
Issue: PHP errors not showing
# Temporarily enable display_errors for debugging
sudo nano /etc/php/8.1/apache2/php.ini
Change:
display_errors = On
Remember to disable after debugging!
Performance Issues
Check resource usage:
# Overall system
htop
# Apache processes
ps aux | grep apache2
# MySQL processes
ps aux | grep mysql
# Disk I/O
iostat -x 1
Analyze slow queries:
Enable MySQL slow query log:
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
Add:
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
Security Issues
Check for failed login attempts:
sudo grep "Failed password" /var/log/auth.log | tail -20
Check Apache access log for suspicious activity:
sudo tail -100 /var/log/apache2/access.log | grep -E "(404|403|500)"
Scan for malware (optional):
sudo apt install clamav clamav-daemon -y
sudo freshclam
sudo clamscan -r /var/www
What’s Changed Since the Original Guide
If you’re familiar with older LAMP setups, here are the major updates:
Technology Versions
Then (circa 2016):
- Ubuntu 16.04
- Apache 2.4.18
- MySQL 5.7
- PHP 7.0
Now (2026):
- Ubuntu 22.04+
- Apache 2.4.52+
- MySQL 8.0+
- PHP 8.1+
Key Changes
PHP Evolution:
- PHP 7.0 → PHP 8.1+
- Improved performance (JIT compiler in PHP 8)
- Type declarations and error handling improvements
- Deprecated functions removed
MySQL Changes:
- Authentication plugin changes
mysql_native_password→caching_sha2_password- InnoDB now default storage engine
- JSON data type support
- Window functions
Apache Improvements:
- HTTP/2 support
- Better performance with event MPM
- Improved SSL/TLS configuration
Certbot Installation:
- Old: PPA repository
- New: Snap package (easier, auto-updates)
Security Practices:
- Stronger cipher suites
- TLS 1.3 support
- Modern security headers
- Better password policies
System Management:
- Systemd everywhere
- Improved firewall (UFW) defaults
- Better package management
Conclusion
You now have a fully functional, secure LAMP stack capable of hosting production websites and web applications!
What You’ve Accomplished
✅ Secure Ubuntu server with proper user management
✅ Apache web server with virtual hosts
✅ SSL/TLS certificates with auto-renewal
✅ MySQL database server with security hardening
✅ PHP 8.x with optimized configuration
✅ phpMyAdmin for database management
✅ Comprehensive security measures
✅ Performance optimizations
✅ Monitoring and maintenance procedures
Next Steps
For Development:
- Install Composer (PHP package manager)
- Set up Git for version control
- Configure development tools (Xdebug, etc.)
For Production:
- Implement regular backups
- Set up monitoring (Nagios, Zabbix)
- Configure CDN (Cloudflare)
- Implement fail2ban for intrusion prevention
- Set up log aggregation (ELK stack)
Popular Applications to Deploy:
- WordPress (CMS)
- Laravel (PHP framework)
- Magento (E-commerce)
- Drupal (CMS)
- PrestaShop (E-commerce)
Learning Resources
- Apache Documentation
- MySQL Documentation
- PHP Manual
- Digital Ocean Community Tutorials
- Stack Overflow LAMP Tag
Quick Reference Commands
# Apache
sudo systemctl restart apache2
sudo apache2ctl configtest
sudo a2ensite example.com.conf
sudo a2dissite 000-default.conf
sudo a2enmod rewrite
# MySQL
sudo systemctl restart mysql
sudo mysql -u root -p
sudo mysqltuner
# PHP
php -v
php -i | grep opcache
# Logs
sudo tail -f /var/log/apache2/error.log
sudo tail -f /var/log/mysql/error.log
# Firewall
sudo ufw status verbose
sudo ufw allow 80/tcp
# SSL
sudo certbot certificates
sudo certbot renew --dry-run
Happy hosting! Your LAMP stack is ready to power your web applications.
Related Posts:
- Deploying WordPress on LAMP Stack
- Laravel Application Setup on LAMP
- LAMP vs LEMP: Which Stack is Right for You?
- Advanced Apache Security Configuration
- MySQL Replication Setup for High Availability