How to Install a LAMP Stack and Host Your Own Website on Linux
LAMP is Linux, Apache, MySQL (or MariaDB) and PHP. It still runs a large share of the web, from WordPress to custom PHP apps, and it is the simplest way to host your own sites on a server you control. This post goes from a bare server to a site with its own domain, HTTPS, a database and a deploy routine.
Where to run it
A VPS from any cloud or hosting provider is the easy option: a public IPv4 address, a reliable network and console access when something breaks. One vCPU and 1 to 2 GB of RAM is enough for a few small sites.
Hardware at home (an old PC, a mini PC, a Raspberry Pi) works for personal projects, with caveats:
- You need a reachable public IP. Many ISPs put customers behind CGNAT or block inbound port 80. Check before you buy anything.
- If your IP changes, you need dynamic DNS to keep the domain pointing at it.
- Forward ports 80 and 443 on the router to the server, and nothing else.
- Power cuts and home internet outages take the site down.
For the OS, use a current LTS or enterprise release: Ubuntu Server 24.04 or 26.04, Debian, or AlmaLinux/Rocky Linux 9 or 10. Install the minimal server variant with OpenSSH, and log in with an SSH key.
Install on Ubuntu or Debian
sudo apt update
sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql
On Debian, use mariadb-server instead of mysql-server, which is not in the Debian archive. Add the PHP extensions your application needs, for example for WordPress:
sudo apt install php-curl php-gd php-intl php-mbstring php-xml php-zip
All services start automatically. Open the firewall, SSH first:
sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable
libapache2-mod-php runs PHP inside Apache and is the simplest setup. On busier servers, PHP-FPM with Apache's event MPM uses less memory. To switch (replace 8.3 with the version php -v reports):
sudo apt install php-fpm
sudo a2dismod php8.3 mpm_prefork
sudo a2enmod mpm_event proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2
Install on AlmaLinux, Rocky Linux or RHEL
sudo dnf install httpd mariadb-server php php-mysqlnd
sudo systemctl enable --now httpd mariadb
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload
The RHEL family ships MariaDB as the usual "M"; MySQL itself is also available as mysql-server, but not both on one host. PHP runs as PHP-FPM, which is started together with httpd, so there is nothing extra to enable. After installing more PHP modules, run sudo systemctl restart httpd php-fpm.
Secure the database
sudo mysql_secure_installation
This removes anonymous users and the test database and disables remote root login. On Ubuntu with MySQL, root logs in through auth_socket (sudo mysql), so the script skips the root password step. With MariaDB it also offers socket authentication and a password for root.
Create a database and a user for the site. Never let PHP connect as root:
CREATE DATABASE example_com;
CREATE USER 'example_com'@'localhost' IDENTIFIED BY 'use-a-long-random-password';
GRANT ALL PRIVILEGES ON example_com.* TO 'example_com'@'localhost';
Run this in sudo mysql (or sudo mariadb). The database stays on localhost; there is no reason to open port 3306 on a single-server setup.
Check that PHP works
echo '<?php phpinfo();' | sudo tee /var/www/html/info.php
curl -s http://localhost/info.php | grep -m1 'PHP Version'
sudo rm /var/www/html/info.php
Delete the file afterwards. phpinfo() shows paths, versions and configuration that you do not want public.
Add a site
One virtual host per site, each with its own document root, owned by a deploy user rather than the web server:
sudo mkdir -p /var/www/example.com/public
sudo chown -R deploy: /var/www/example.com
With the default umask, files are world-readable, so Apache can serve them without owning them. On Ubuntu, create /etc/apache2/sites-available/example.com.conf:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/log/apache2/example.com-error.log
CustomLog /var/log/apache2/example.com-access.log combined
</VirtualHost>
AllowOverride All lets applications like WordPress use .htaccess for pretty URLs. Enable the site and the rewrite module:
sudo a2ensite example.com
sudo a2enmod rewrite
sudo apache2ctl configtest && sudo systemctl reload apache2
On RHEL, save the same block as /etc/httpd/conf.d/example.com.conf with log paths under /var/log/httpd/, then sudo apachectl configtest && sudo systemctl reload httpd. mod_rewrite is already loaded there.
Point the domain at the server
At your DNS provider, create an A record for example.com and www.example.com pointing at the server's public IPv4 address, plus AAAA records if the server has IPv6. Check it from your machine:
dig +short example.com
dig +short www.example.com
Both should return the server's address before you request a certificate.
Add HTTPS
# Ubuntu / Debian
sudo apt install certbot python3-certbot-apache
# AlmaLinux / Rocky (certbot comes from EPEL)
sudo dnf install epel-release
sudo dnf install certbot python3-certbot-apache mod_ssl
sudo certbot --apache -d example.com -d www.example.com
sudo certbot renew --dry-run
Certbot adds an HTTPS virtual host, can redirect HTTP to HTTPS, and installs a timer that renews certificates before they expire.
Deploy files
Use SSH-based tools. Plain FTP sends passwords in clear text and has no place on a public server.
rsync -avz --delete ./site/ deploy@203.0.113.10:/var/www/example.com/public/
--delete removes files on the server that no longer exist locally, so double-check the paths. Graphical SFTP clients work over the same SSH login. If the site lives in git, cloning it on the server and running git pull to deploy is also fine, as long as the .git directory sits outside the document root.
Only directories the application writes to (uploads, cache) should be writable by the web server, for example sudo chown www-data /var/www/example.com/public/uploads (apache on RHEL). Everything else stays owned by the deploy user, so a compromised PHP script cannot rewrite the code.
Keep it running
- Updates. Ubuntu Server installs security updates automatically through
unattended-upgrades. On the RHEL family installdnf-automatic, setapply_updates = yesin/etc/dnf/automatic.confand runsudo systemctl enable --now dnf-automatic.timer. - SSH. Key-only logins:
PasswordAuthentication noinsshd_config. - Backups. Dump each database nightly from root's crontab (
sudo crontab -e) and copy dumps and site files off the server:
A backup that never leaves the server dies with the server.0 3 * * * mysqldump --single-transaction example_com | gzip > /var/backups/example_com-$(date +\%F).sql.gz - Database admin. Skip a public phpMyAdmin. Connect a desktop client through an SSH tunnel:
ssh -L 3306:localhost:3306 deploy@203.0.113.10. - Disk and logs. Check
df -hand the Apache error logs now and then; a full disk takes down MySQL first.
Quick reference
sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql # Ubuntu
sudo dnf install httpd mariadb-server php php-mysqlnd # RHEL family
sudo systemctl enable --now httpd mariadb # RHEL family
sudo ufw allow 'Apache Full' # or firewall-cmd --add-service=http/https
sudo mysql_secure_installation
sudo a2ensite example.com && sudo a2enmod rewrite && sudo systemctl reload apache2
sudo certbot --apache -d example.com -d www.example.com
rsync -avz ./site/ deploy@203.0.113.10:/var/www/example.com/public/
