Ubuntu 22.04 64-bit LAMP Stack
System Preparation
Update Ubuntu and verify that the server is running the expected 64-bit architecture.
sudo apt update
sudo apt upgrade -y
sudo apt full-upgrade -y
sudo apt autoremove -y
Check the operating system and CPU architecture:
uname -m
dpkg --print-architecture
lsb_release -a
cat /etc/os-release
Check server resources:
free -h
df -h
lsblk
lscpu
Basic Server Security
Create a dedicated administrative user:
sudo adduser adminuser
sudo usermod -aG sudo adminuser
Install useful administration tools:
sudo apt install -y curl wget vim nano unzip zip git htop tree net-tools lsof ca-certificates gnupg software-properties-common apt-transport-https
Install and configure UFW:
sudo apt install -y ufw
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Do not expose MySQL port 3306 publicly unless it is specifically required and protected by network restrictions.
Install Apache
sudo apt update
sudo apt install -y apache2
Enable and start Apache:
sudo systemctl enable --now apache2
Check Apache:
systemctl status apache2
apache2 -v
Test configuration:
sudo apache2ctl configtest
Default web root:
/var/www/html
Test Apache locally:
curl http://localhost
Apache service commands:
sudo systemctl start apache2
sudo systemctl stop apache2
sudo systemctl restart apache2
sudo systemctl reload apache2
sudo systemctl enable apache2
sudo systemctl disable apache2
Apache logs:
sudo tail -f /var/log/apache2/access.log
sudo tail -f /var/log/apache2/error.log
Apache Modules
List loaded modules:
apache2ctl -M
Enable common modules:
sudo a2enmod rewrite
sudo a2enmod headers
sudo a2enmod ssl
sudo a2enmod expires
sudo a2enmod http2
sudo systemctl reload apache2
Disable a module:
sudo a2dismod MODULE_NAME
Install PHP
Ubuntu 22.04 LTS provides PHP 8.1 as its standard PHP release.
sudo apt update
sudo apt install -y php libapache2-mod-php php-cli php-common php-mysql php-curl php-gd php-mbstring php-xml php-zip php-intl php-bcmath php-soap php-opcache
Verify PHP:
php -v
List PHP modules:
php -m
Check PHP configuration:
php --ini
Create a temporary PHP information page:
sudo nano /var/www/html/info.php
<?php
phpinfo();
?>
Open /info.php in a browser, then remove it immediately
after testing.
sudo rm /var/www/html/info.php
PHP Configuration
Edit the Apache PHP configuration:
sudo nano /etc/php/8.1/apache2/php.ini
Important settings include:
memory_limit
upload_max_filesize
post_max_size
max_execution_time
max_input_time
max_input_vars
max_file_uploads
date.timezone
display_errors
log_errors
error_log
Production example:
display_errors = Off
log_errors = On
date.timezone = Asia/Manila
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 60
Restart Apache after changes:
sudo systemctl restart apache2
PHP OPcache
OPcache improves PHP performance by caching compiled PHP bytecode.
sudo apt install -y php8.1-opcache
php -i | grep -i opcache
Tune OPcache according to the application's workload and available memory rather than blindly copying settings from another server.
Install MySQL
sudo apt update
sudo apt install -y mysql-server
sudo systemctl enable --now mysql
Check MySQL:
sudo systemctl status mysql
mysql --version
Run the security configuration:
sudo mysql_secure_installation
Open the MySQL administration shell:
sudo mysql
MySQL Database Administration
Create a database:
CREATE DATABASE appdb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Create a restricted application user:
CREATE USER 'appuser'@'localhost'
IDENTIFIED BY 'CHANGE_THIS_TO_A_STRONG_PASSWORD';
GRANT ALL PRIVILEGES ON appdb.*
TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
Test the application account:
mysql -u appuser -p appdb
Useful SQL commands:
SHOW DATABASES;
USE appdb;
SHOW TABLES;
SELECT USER, HOST FROM mysql.user;
SHOW GRANTS FOR 'appuser'@'localhost';
EXIT;
Example table:
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
MySQL Backup and Restore
Back up one database:
mysqldump -u appuser -p appdb > appdb.sql
Back up all databases:
sudo mysqldump --all-databases > all-databases.sql
Compressed backup:
mysqldump -u appuser -p appdb | gzip > appdb.sql.gz
Restore:
mysql -u appuser -p appdb < appdb.sql
Restore compressed backup:
gunzip -c appdb.sql.gz | mysql -u appuser -p appdb
A backup is not proven until it has been successfully restored. Maintain multiple copies, preferably including off-server storage.
Apache Virtual Hosts
Create the website directory:
sudo mkdir -p /var/www/example.com/public_html
Set ownership:
sudo chown -R $USER:www-data /var/www/example.com
Set basic permissions:
sudo chmod -R 755 /var/www/example.com
Create the virtual host:
sudo nano /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
Options 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>
Enable the website:
sudo a2ensite example.com.conf
Optionally disable the default site:
sudo a2dissite 000-default.conf
Test and reload:
sudo apache2ctl configtest
sudo systemctl reload apache2
.htaccess and mod_rewrite
Enable rewrite:
sudo a2enmod rewrite
sudo systemctl reload apache2
Example front-controller .htaccess:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
HTTPS with Let's Encrypt
Install Certbot:
sudo apt update
sudo apt install -y certbot python3-certbot-apache
Request and configure the certificate:
sudo certbot --apache -d example.com -d www.example.com
Test automatic renewal:
sudo certbot renew --dry-run
List certificates:
sudo certbot certificates
SSL Verification
Enable SSL:
sudo a2enmod ssl
sudo systemctl reload apache2
Check listening ports:
sudo ss -lntp | grep -E ':80|:443'
Inspect Apache virtual hosts:
sudo apache2ctl -S
File and Directory Permissions
Inspect files:
ls -lah /var/www/example.com/
Set ownership:
sudo chown -R USER:www-data /var/www/example.com
Set directory permissions:
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
Set file permissions:
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
Avoid using chmod 777 as a general solution.
Application-specific writable directories should be configured
individually.
Apache Security Headers
Enable the headers module:
sudo a2enmod headers
sudo systemctl reload apache2
Example headers:
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy and HSTS should be designed and tested specifically for the application.
PHP Application Deployment
Example application structure:
/var/www/example.com/
app/
config/
storage/
vendor/
public/
When a framework provides a dedicated public directory, use that directory as Apache's DocumentRoot.
Example Composer deployment:
cd /var/www/example.com
composer install --no-dev --optimize-autoloader
Git Deployment
Install Git:
sudo apt install -y git
Example clone:
cd /var/www
sudo git clone REPOSITORY_URL example.com
Production deployments should use controlled credentials, protected secrets, reproducible builds, dependency pinning, and a rollback procedure.
MySQL Remote Access
Avoid remote MySQL access unless it is required.
If remote access is required:
- Restrict the source IP/network.
- Use encrypted connections.
- Use a dedicated database account.
- Grant only required privileges.
- Restrict firewall access.
- Monitor authentication and network activity.
Never expose port 3306 to the entire Internet without a strong, deliberate security architecture.
Service and Process Troubleshooting
Check services:
systemctl status apache2
systemctl status mysql
Check failed services:
systemctl --failed
View logs:
journalctl -u apache2 -n 100 --no-pager
journalctl -u mysql -n 100 --no-pager
Check processes:
ps aux
top
htop
Check ports:
sudo ss -lntup
Apache Troubleshooting
Configuration error:
sudo apache2ctl configtest
Recent Apache logs:
sudo journalctl -u apache2 -n 100 --no-pager
sudo tail -n 100 /var/log/apache2/error.log
Virtual hosts:
sudo apache2ctl -S
Enabled sites:
ls -lah /etc/apache2/sites-enabled/
Enabled modules:
apache2ctl -M
For HTTP 403 errors, inspect directory permissions and Apache access rules.
namei -l /var/www/example.com/public_html/index.php
PHP Troubleshooting
php -v
php -m
php --ini
Check common database extensions:
php -m | grep mysqli
php -m | grep pdo_mysql
Inspect PHP configuration:
php -i | less
Restart Apache after configuration changes:
sudo systemctl restart apache2
MySQL Troubleshooting
sudo systemctl status mysql
sudo journalctl -u mysql -n 100 --no-pager
mysql -u appuser -p
sudo mysql -e "SHOW DATABASES;"
sudo mysql -e "SELECT User,Host FROM mysql.user;"
sudo mysql -e "SHOW GRANTS FOR 'appuser'@'localhost';"
Performance Monitoring
CPU and memory:
htop
free -h
uptime
Disk:
df -h
df -i
Network:
ss -s
ip -s link
MySQL processes:
sudo mysql -e "SHOW PROCESSLIST;"
Tune production systems from measured workload data instead of arbitrary configuration values.
Log Rotation
Inspect logrotate configuration:
ls /etc/logrotate.d/
cat /etc/logrotate.d/apache2
Test the configuration:
sudo logrotate -d /etc/logrotate.conf
Force rotation only when required:
sudo logrotate -f /etc/logrotate.conf
Automatic Security Updates
Install unattended-upgrades:
sudo apt install -y unattended-upgrades
Check service status:
sudo systemctl status unattended-upgrades
Production systems should have a tested patch-management process, maintenance windows where appropriate, and monitoring for failed updates.
Web Application Backup
Example site backup:
sudo tar -czf example-site-$(date +%F).tar.gz \
/var/www/example.com
A production backup strategy should consider:
- Application files
- Database dumps
- User uploads
- Configuration
- Secrets and certificates where appropriate
- Deployment configuration
- Off-server/off-site copies
- Encryption
- Retention policies
- Regular restore tests
Cron Jobs
Edit the current user's crontab:
crontab -e
List cron jobs:
crontab -l
System cron locations:
/etc/crontab
/etc/cron.d/
/etc/cron.daily/
/etc/cron.weekly/
/etc/cron.monthly/
Example PHP scheduled task:
*/5 * * * * /usr/bin/php /var/www/example.com/artisan schedule:run >> /var/log/app-cron.log 2>&1
Environment Variables and Secrets
Never hard-code production passwords, API keys, private keys or other secrets into PHP source code or Git repositories.
For a protected .env file:
chmod 600 .env
Prefer keeping sensitive files outside the public web root when the application architecture allows it.
Common Apache Commands
sudo a2ensite example.com.conf
sudo a2dissite example.com.conf
sudo a2enmod rewrite
sudo a2dismod rewrite
sudo apache2ctl configtest
sudo apache2ctl -S
sudo systemctl reload apache2
sudo systemctl restart apache2
Useful Ubuntu Network Commands
ip addr
ip route
resolvectl status
dig example.com
curl -I http://example.com
curl -I https://example.com
sudo ss -lntup
ping -c 4 example.com
MySQL Database Maintenance
Check database sizes:
sudo mysql -e "
SELECT table_schema AS db,
ROUND(SUM(data_length+index_length)/1024/1024,2) AS MB
FROM information_schema.tables
GROUP BY table_schema
ORDER BY MB DESC;"
Connect to a database:
mysql -u appuser -p appdb
Inspect tables:
SHOW TABLES;
DESCRIBE users;
Common HTTP Status Codes
200 OK
301 Permanent redirect
302 Temporary redirect
304 Not modified
400 Bad request
401 Authentication required
403 Forbidden
404 Not found
405 Method not allowed
408 Request timeout
429 Too many requests
500 Internal server error
502 Bad gateway
503 Service unavailable
504 Gateway timeout
Security Checklist
- Ubuntu packages are current.
- SSH keys are configured and tested.
- Direct root SSH login is disabled where appropriate.
- Firewall is enabled.
- Only required ports are exposed.
- Apache is current.
- PHP is current for the supported application/runtime.
- MySQL is current.
- Database users use least privilege.
- MySQL is not unnecessarily exposed to the Internet.
- HTTPS is enabled for production websites.
- Certificate renewal has been tested.
- Sensitive files are not web-accessible.
- File permissions are restrictive.
- Secrets are not stored in Git.
- Backups are automated.
- Backups are stored separately.
- Restore tests are performed.
- Logs are monitored.
- Disk usage is monitored.
- Security updates are managed.
- A rollback procedure exists.
Quick LAMP Installation
Basic installation sequence for Ubuntu 22.04:
sudo apt update
sudo apt upgrade -y
sudo apt install -y apache2
sudo systemctl enable --now apache2
sudo apt install -y php libapache2-mod-php php-mysql php-curl \
php-gd php-mbstring php-xml php-zip php-intl php-bcmath php-opcache
sudo apt install -y mysql-server
sudo systemctl enable --now mysql
sudo a2enmod rewrite headers ssl
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo apache2ctl configtest
sudo systemctl reload apache2
Then:
- Create a database and restricted application user.
- Create an Apache virtual host.
- Point DNS to the server.
- Configure HTTPS.
- Deploy the application.
- Configure backups.
- Configure monitoring.
- Remove test files.
- Verify permissions.
Recommended Production Architecture
Internet
|
| HTTPS :443
v
Apache
|
+--> PHP application
|
+--> Static files/uploads
|
v
MySQL
(local/private network)
Operational components:
- UFW firewall
- SSH keys
- Security updates
- Log rotation
- Backups
- Monitoring and alerts
- HTTPS certificate renewal
Disaster Recovery
Document:
- Server hostnames and IP addresses
- DNS records
- Domain names
- Application deployment procedure
- Database names and users
- Backup locations
- Backup recovery credentials
- SSL renewal process
- Required PHP extensions
- Required system packages
- Cron jobs
- Firewall rules
- External dependencies
Recovery sequence:
- Provision a clean supported Ubuntu server.
- Apply security updates.
- Configure administrative access.
- Configure firewall.
- Install Apache, PHP and database software.
- Restore application files.
- Restore database.
- Restore configuration and secrets securely.
- Configure DNS and HTTPS.
- Test the application.
- Verify backups.
- Monitor the recovered system.
Version Notes
Ubuntu package versions change through security and maintenance updates. Do not assume that the exact package revision on one server will match another.
Before production deployment verify:
- Ubuntu 22.04 security status
- PHP version required by the application
- MySQL/MariaDB support requirements
- Apache package status
- Application/framework compatibility
- Third-party repository trust and maintenance
Final Verification Commands
Operating system:
lsb_release -a
uname -m
Apache:
apache2 -v
sudo apache2ctl configtest
sudo apache2ctl -S
systemctl is-active apache2
PHP:
php -v
php -m
MySQL:
mysql --version
systemctl is-active mysql
Network:
sudo ss -lntup
sudo ufw status verbose
Disk and memory:
df -h
free -h
Logs:
sudo tail -n 50 /var/log/apache2/error.log
sudo journalctl -u mysql -n 50 --no-pager
HTTPS:
curl -I https://example.com
Production Best Practices
- Keep Ubuntu and all packages patched.
- Use least privilege.
- Protect SSH.
- Do not expose MySQL unnecessarily.
- Use HTTPS.
- Do not use chmod 777 as a general fix.
- Keep secrets out of source control.
- Back up both files and databases.
- Test backup restoration.
- Monitor logs, disk, CPU and memory.
- Test configuration changes before reloading services.
- Maintain a documented rollback plan.
- Use application-compatible PHP extensions.
- Keep production and development credentials separate.
- Use reproducible deployment procedures.
- Remove diagnostic files such as phpinfo() from production.
- Do not blindly copy tuning settings from unrelated servers.
- Maintain a recovery path before making access-related changes.