Install a LAMP Stack on a Fresh Ubuntu VPS: Full Guide

· 6 min read · 1 view · Getwebup

If your VPS came bare - no control panel, no web server, nothing - and you'd rather run Apache than Nginx, this is the setup. LAMP (Linux, Apache, MySQL/MariaDB, PHP) is still the stack most WordPress installs, cPanel-style hosts, and .htaccess-dependent apps expect out of the box, and it's a fair bit more forgiving to configure than Nginx if you're used to shared hosting.

Before You Start

This walkthrough assumes Ubuntu 22.04 or 24.04, a non-root sudo user, and SSH key access already working. If your firewall is still wide open, close it down first - installing a web stack before locking down SSH just hands attackers something worth breaking into.

sudo apt update && sudo apt upgrade -y

Step 1: Install Apache

sudo apt install apache2 -y
sudo systemctl enable --now apache2

Open the firewall for HTTP and HTTPS:

sudo ufw allow 'Apache Full'
sudo systemctl status apache2

Load your VPS's IP in a browser. You should see the default "Apache2 Ubuntu Default Page." If nothing loads, check that port 80/443 are open both in UFW and in your VPS provider's separate network firewall (common on cloud dashboards - it's not the same thing as UFW).

Step 2: Install MySQL or MariaDB

MariaDB is the default on most current Ubuntu releases and is a drop-in replacement for MySQL:

sudo apt install mariadb-server -y
sudo mysql_secure_installation

Say yes to removing anonymous users, disabling remote root login, dropping the test database, and reloading privileges. Skipping this leaves a root account with no password sitting on the box - not something you want a scanner to find.

Create a dedicated database and user for your app rather than reusing root later:

sudo mysql -u root -p

CREATE DATABASE appdb;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-strong-password-here';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Step 3: Install PHP and Connect It to Apache

This is the one real fork in the road versus Nginx. Apache can run PHP two ways:

  • mod_php - PHP runs embedded inside the Apache process. Simple to set up, but every Apache worker carries the PHP interpreter's memory overhead even when serving static files.
  • PHP-FPM via mod_proxy_fcgi - PHP runs as a separate pool of workers, same as it does under Nginx. More setup, noticeably better memory use and concurrency under load.

For a small site, mod_php is fine and this is the fastest path:

sudo apt install php libapache2-mod-php php-mysql php-curl php-xml php-mbstring php-zip -y
sudo systemctl restart apache2

If you want PHP-FPM instead (recommended once you're running anything with real traffic, or migrating a site that already expects FPM):

sudo apt install php-fpm php-mysql php-curl php-xml php-mbstring php-zip -y
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

Check the exact FPM service and conf name with ls /etc/apache2/conf-available/ | grep php - the version number depends on what Ubuntu shipped.

Test PHP is actually being processed, not downloaded as a raw file:

echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php

Visit http://your-server-ip/info.php. A rendered PHP info page means it's wired up correctly; a file download prompt means Apache still isn't handing .php files to the interpreter. Delete this file once you've confirmed it works - a public phpinfo page leaks server paths and module versions to anyone who finds it.

Step 4: Set Up a Virtual Host

Don't build your site into /var/www/html directly - give it its own document root and virtual host so you can add more sites later without a rebuild.

sudo mkdir -p /var/www/yourdomain.com/public_html
sudo chown -R $USER:$USER /var/www/yourdomain.com/public_html
sudo chmod -R 755 /var/www/yourdomain.com

Create the virtual host config:

sudo nano /etc/apache2/sites-available/yourdomain.com.conf
<VirtualHost *:80>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com
    DocumentRoot /var/www/yourdomain.com/public_html

    <Directory /var/www/yourdomain.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-error.log
    CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log combined
</VirtualHost>

The AllowOverride All line matters more than it looks - without it, .htaccess rules (permalinks, redirects, most WordPress plugin behavior) get silently ignored, no error, they just don't apply.

Enable the site, disable the default one, and reload:

sudo a2ensite yourdomain.com.conf
sudo a2dissite 000-default.conf
sudo a2enmod rewrite
sudo apache2ctl configtest
sudo systemctl reload apache2

Always run apache2ctl configtest before reloading. It catches a stray brace or typo in the vhost file before Apache refuses to start over it.

Step 5: Add Free SSL

sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com

Certbot edits your Apache vhost automatically to add the 443 block and redirect HTTP to HTTPS. Confirm auto-renewal is scheduled:

sudo certbot renew --dry-run

Common Errors and Fixes

"Could not reliably determine the server's fully qualified domain name" (AH00558)

Harmless warning, but easy to silence. Set the global server name:

echo "ServerName localhost" | sudo tee /etc/apache2/conf-available/servername.conf
sudo a2enconf servername
sudo systemctl reload apache2

403 Forbidden on a Working Directory

Usually one of two things: the <Directory> block is missing Require all granted (older tutorials still show Order allow,deny, which is Apache 2.2 syntax and does nothing on 2.4+), or the file permissions on public_html are too tight for the www-data user to traverse into. Check both:

sudo apachectl -S
namei -l /var/www/yourdomain.com/public_html/index.php

.htaccess Rules Not Applying

Almost always AllowOverride None left over from the default vhost, or mod_rewrite not enabled. Confirm both:

sudo a2enmod rewrite
apache2ctl -M | grep rewrite

PHP Files Downloading Instead of Running

If you're on PHP-FPM, this means the proxy_fcgi handler isn't picking up .php requests - check that a2enconf php8.3-fpm (with your actual version) was run and that the FPM socket path in that conf file matches what's listed in systemctl status php8.3-fpm.

LAMP vs LEMP: Which One Should You Actually Pick

FactorLAMP (Apache)LEMP (Nginx)
.htaccess supportNative, per-directoryNot supported - all rules live in one config file
Config style familiar fromShared hosting, cPanelCloud-native, containerized deploys
Memory per connectionHigher with mod_php, comparable with FPMLower - event-driven by design
Best fitWordPress/plugin-heavy sites expecting .htaccessHigh-concurrency, static-heavy, or API-style apps

If you're moving a site off shared cPanel hosting and it leans on .htaccess for redirects, security rules, or plugin behavior, LAMP will save you from rewriting all of that as Nginx directives. If you're starting fresh with no legacy .htaccess dependency, LEMP is usually the leaner choice.

Prevention: Keep It From Breaking Later

  • Run apache2ctl configtest before every reload - not after something breaks.
  • Keep PHP and Apache module versions in sync across staging and production so a working .htaccess rule doesn't quietly stop working after a server migration.
  • Set up unattended security updates so Apache and PHP patches land automatically without you having to chase CVEs.
  • Log rotation is on by default via logrotate, but check /var/log/apache2/ disk usage periodically on a busy site - access logs grow fast.

Questions people actually ask