LAMP + LEMP — Step-by-Step Setup

Two complete web stacks, side-by-side on VM 105 LinuxServer · sharing one MariaDB. Each command block has a Copy button — click, paste, run, verify.

🟢 LEMP · NGINX + PHP-FPM · port 80 🟠 LAMP · Apache + mod_php · port 8080 🔵 Shared MariaDB
0

Get a paste-friendly shell

VM 105's noVNC console doesn't accept paste. Use SSH from the Proxmox web shell instead — it pastes cleanly.

0.1 — Open Proxmox web shell

In Safari: https://10.10.10.10:8006 → log in as root → click node tctmachine → top tab >_ Shell.

0.2 — SSH into VM 105

ssh tct_linuxserver@192.168.0.20

Replace tct_linuxserver with your VM 105 admin username if different. Type yes to accept the host key, then your password.

Stay in this SSH session for the entire walkthrough. All subsequent paste blocks go into the prompt that now reads something like tct_linuxserver@tctlinuxserver:~$.
1

Clean slate

Wipes any prior Apache, NGINX, MariaDB, or PHP installs so we start from zero. If nothing was installed before, this still runs cleanly.

sudo systemctl stop apache2 nginx mariadb 2>/dev/null
sudo apt purge -y 'apache2*' 'nginx*' 'mariadb-*' 'php*' libapache2-mod-php
sudo apt autoremove --purge -y
sudo rm -rf /etc/apache2 /etc/nginx /etc/mysql /var/lib/mysql /var/www/html /var/www/lamp
sudo apt update
Expected: ends with Reading package lists... Done. No errors.
Confirm clean:
apt list --installed 2>/dev/null | grep -E "(apache|nginx|php|mariadb)"

Output should be empty (no matching packages installed).

2

System update

sudo apt update && sudo apt upgrade -y
Expected: ~2 minutes. Returns to prompt when done. Some kernel/library updates may ask config questions — accept defaults.
3

MariaDB Shared

One database backend serves both stacks.

3.1 — Install + start

sudo apt install -y mariadb-server mariadb-client
sudo systemctl enable --now mariadb
sudo systemctl status mariadb --no-pager | head -5
Expected: Active: active (running)

3.2 — Run security wizard

sudo mysql_secure_installation

Answer the prompts:

PromptAnswer
Enter current password for root(press Enter)
Switch to unix_socket authentication?n
Change the root password?Y → set strong password (write it down)
Remove anonymous users?Y
Disallow root login remotely?Y
Remove test database?Y
Reload privilege tables?Y

3.3 — Create lab DB + user

sudo mariadb <<'EOF'
CREATE DATABASE capstone_db;
CREATE USER 'capuser'@'localhost' IDENTIFIED BY 'CapstonePass2026!';
GRANT ALL PRIVILEGES ON capstone_db.* TO 'capuser'@'localhost';
FLUSH PRIVILEGES;
SHOW DATABASES;
EOF
Expected: Output lists capstone_db + information_schema, mysql, performance_schema, sys.
4

LEMP — NGINX + PHP-FPM port 80

4.1 — Install + start NGINX

sudo apt install -y nginx
sudo systemctl enable --now nginx
curl -sI http://localhost/ | head -2
Expected: HTTP/1.1 200 OK + Server: nginx/1.x.x

4.2 — Install PHP-FPM + extensions

sudo apt install -y php-fpm php-mysql php-cli php-curl php-mbstring php-xml php-zip
php -v | head -1
ls /run/php/
Expected: PHP 8.x.x (cli) · ls shows a php8.x-fpm.sock file (note the version)

4.3 — Configure NGINX to render PHP

sudo tee /etc/nginx/sites-available/default > /dev/null <<'NGINX_CONF'
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;
    index index.php index.html index.htm;

    server_name _;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:PHP_FPM_SOCK_PLACEHOLDER;
    }

    location ~ /\.ht {
        deny all;
    }
}
NGINX_CONF

PHP_FPM_SOCK=$(ls /run/php/php*-fpm.sock 2>/dev/null | head -1)
echo "Using PHP-FPM socket: $PHP_FPM_SOCK"
sudo sed -i "s|PHP_FPM_SOCK_PLACEHOLDER|${PHP_FPM_SOCK}|" /etc/nginx/sites-available/default
sudo nginx -t
sudo systemctl reload nginx
Expected: nginx: configuration file /etc/nginx/nginx.conf test is successful

4.4 — Verify NGINX renders PHP

sudo mkdir -p /var/www/html
echo "" | sudo tee /var/www/html/info.php
curl -s http://localhost/info.php | head -3
Expected: Output starts with <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional... — that's PHP rendering.
If you see raw <?php phpinfo(); ?> in the output, FastCGI isn't connected. Re-run the socket sed in 4.3, then sudo systemctl reload nginx.
5

LAMP — Apache + mod_php port 8080

5.1 — Install Apache + mod_php

sudo apt install -y apache2 libapache2-mod-php
Expected during install: Apache will fail to start with "Job for apache2.service failed because the control process exited with error code" — that's because NGINX already owns port 80. We move Apache to 8080 next. Don't panic.

5.2 — Move Apache to port 8080 + new docroot

Single command: rewrites ports.conf for port 8080 and replaces the default vhost so the <Directory> block lives inside the <VirtualHost> (canonical Apache 2.4 placement — avoids "AllowOverride not allowed here" errors).

sudo sed -i 's/^Listen 80$/Listen 8080/' /etc/apache2/ports.conf

sudo mkdir -p /var/www/lamp
sudo chown -R www-data:www-data /var/www/lamp

sudo tee /etc/apache2/sites-available/000-default.conf > /dev/null <<'EOF'

    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/lamp

    
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
    

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

EOF

5.3 — Restart Apache + verify both servers

sudo apache2ctl configtest
sudo systemctl restart apache2
sudo systemctl status apache2 --no-pager | head -5
sudo ss -tlnp | grep -E ':(80|8080) '
Expected: Syntax OK · Active: active (running) · ss shows two lines — nginx on 0.0.0.0:80 and apache2 on 0.0.0.0:8080.
If only nginx shows up: paste sudo journalctl -u apache2 -n 20 — Apache didn't start. Common cause: the sed in 5.2 didn't match (already-modified file). Inspect /etc/apache2/ports.conf — make sure it says Listen 8080, not Listen 80.
6

Build the test pages

Distinct welcome pages + identical DB-test pages, so you can demo "same database, different web servers".

6.1 — LEMP welcome page (port 80)

sudo tee /var/www/html/index.html > /dev/null <<'EOF'


LEMP — NGINX




Capstone — LEMP Stack

NGINX + PHP-FPM + MariaDB on Ubuntu Server 26.04 · 192.168.0.20

Test pages

Sister stack: LAMP on port 8080 →

EOF

6.2 — LEMP database-test page

sudo tee /var/www/html/dbtest.php > /dev/null <<'EOF'
connect_error) { die("❌ " . $mysqli->connect_error); }
echo "

✅ LEMP — connected via PHP-FPM

"; echo "

MariaDB version: " . $mysqli->server_info . "

"; echo "

Web server: " . $_SERVER['SERVER_SOFTWARE'] . "

"; echo "

Port: " . $_SERVER['SERVER_PORT'] . "

"; $mysqli->close(); ?> EOF

6.3 — LAMP welcome page (port 8080)

sudo tee /var/www/lamp/index.html > /dev/null <<'EOF'


LAMP — Apache




Capstone — LAMP Stack

Apache 2 + mod_php + MariaDB on Ubuntu Server 26.04 · 192.168.0.20:8080

Test pages

Sister stack: LEMP on port 80 →

EOF

6.4 — LAMP info + database-test

echo "" | sudo tee /var/www/lamp/info.php > /dev/null

sudo tee /var/www/lamp/dbtest.php > /dev/null <<'EOF'
connect_error) { die("❌ " . $mysqli->connect_error); }
echo "

✅ LAMP — connected via mod_php

"; echo "

MariaDB version: " . $mysqli->server_info . "

"; echo "

Web server: " . $_SERVER['SERVER_SOFTWARE'] . "

"; echo "

Port: " . $_SERVER['SERVER_PORT'] . "

"; $mysqli->close(); ?> EOF sudo chown -R www-data:www-data /var/www/lamp /var/www/html
7

UFW firewall

MariaDB stays bound to localhost — no rule for port 3306. Production-shaped.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment "SSH"
sudo ufw allow 80/tcp comment "HTTP NGINX (LEMP)"
sudo ufw allow 8080/tcp comment "HTTP Apache (LAMP)"
sudo ufw allow 443/tcp comment "HTTPS (future)"
sudo ufw --force enable
sudo ufw status verbose
Expected: Status: active. ALLOW rules for 22, 80, 443, 8080 from anywhere.
8

Final verification — the screenshot moment

Run this single block. The output is your evidence-of-build for the lab report.

echo "=== LAMP + LEMP COEXISTENCE on VM 105 ==="
echo
echo "--- Hostname / OS ---"
hostnamectl | head -3
echo
echo "--- NGINX (LEMP, port 80) ---"
systemctl is-active nginx
nginx -v 2>&1
curl -sI http://localhost/ | head -2
echo
echo "--- Apache (LAMP, port 8080) ---"
systemctl is-active apache2
apache2 -v | head -1
curl -sI http://localhost:8080/ | head -2
echo
echo "--- MariaDB (shared) ---"
systemctl is-active mariadb
mariadb -V
echo
echo "--- PHP ---"
php -v | head -1
echo "PHP-FPM (LEMP):  $(systemctl is-active php*-fpm)"
echo "mod_php (LAMP):  $(apache2ctl -M 2>/dev/null | grep -c php_module) modules loaded"
echo
echo "--- Listening ports ---"
sudo ss -tlnp | grep -E ':(80|8080|3306) '
echo
echo "--- UFW ---"
sudo ufw status verbose | head -10
Screenshot the output as 27-linuxserver-lamp-lemp.png. We'll wire it into the verification page as the LAMP+LEMP milestone.
9

Cross-VM tests

Prove the stacks are reachable from your other VMs. Pick whichever method matches the VM you're on.

From WinSrv (Edge browser)

URLExpected
http://192.168.0.20/Green-banner LEMP welcome page
http://192.168.0.20/dbtest.php"✅ LEMP — connected via PHP-FPM" + MariaDB version
http://192.168.0.20:8080/Orange-banner LAMP welcome page
http://192.168.0.20:8080/dbtest.php"✅ LAMP — connected via mod_php" + MariaDB version

From jumpbox SSH session

curl -s http://192.168.0.20/ | head -10
echo "---"
curl -s http://192.168.0.20:8080/ | head -10

From your Mac (via SSH tunnel)

From Mac Terminal:

ssh -L 8000:192.168.0.20:80 -L 8081:192.168.0.20:8080 root@10.10.10.10

Leave that open. In Safari, browse:

URLStack
http://localhost:8000/LEMP (NGINX → port 80 inside)
http://localhost:8081/LAMP (Apache → port 8080 inside)
If WinSrv → LinuxServer fails: same-subnet L2 traffic, no firewall in path. Check sudo ss -tlnp | grep nginx shows 0.0.0.0:80 not 127.0.0.1:80.
If jumpbox → LinuxServer fails: pfSense's DMZ-side firewall is blocking. In pfSense → Firewall → Rules → DMZ → add a rule allowing DMZ netLAN_NET ports 80, 8080.
10

Pre-demo cleanup

info.php leaks PHP version + module list. Remove before the lab demo. Keep dbtest.php (no secrets exposed).

sudo rm /var/www/html/info.php /var/www/lamp/info.php

✅ When all phases are complete

  • LinuxServer (VM 105) running both stacks side-by-side
  • NGINX + PHP-FPM on :80 → green-banner LEMP welcome
  • Apache + mod_php on :8080 → orange-banner LAMP welcome
  • Both stacks query the same capstone_db in MariaDB
  • UFW configured · ss -tlnp shows nginx:80, apache2:8080, mariadb:3306
  • Distinct test pages prove PHP and DB work on each stack independently

Lab-report talking points

  1. Same protocol, different implementations. Both serve HTTP/1.1; architecturally distinct (mod_php = embedded interpreter; PHP-FPM = separate process pool over Unix socket).
  2. Port multiplexing. Single IP, multiple services on different ports — standard sysadmin concept.
  3. Shared backing store. Both stacks query the same MariaDB. Demonstrates the "database is the source of truth" pattern.
  4. Resource trade-offs. Apache spawns processes per request; PHP-FPM has a tunable pool. Apache uses more RAM under load; NGINX handles more connections per MB.
  5. Production reality. Most companies run NGINX in front of Apache (reverse proxy) to combine their strengths.
Copied to clipboard