Lesson 1 — Complete Walkthrough

Building a LEMP Server
from Scratch

A no-skip, step-by-step professional guide covering hardware selection through HTTPS server blocks — Ubuntu 24.04 · Nginx · MariaDB · PHP-FPM

Ubuntu 24.04 LTS Nginx MariaDB 10.x PHP 8.3-FPM Let's Encrypt / Certbot Server Blocks

Created: March 2026  ·  Written by Nicole M. Taylor

⚠️
Disclaimer — Software Changes Over Time This guide was written in March 2026 for Ubuntu 24.04 LTS, Nginx, MariaDB 10.x, and PHP 8.3-FPM. Package names, version numbers, configuration file locations, and installer screens may differ from what is shown here. Always verify commands against the official documentation for the version you are installing. Written by Nicole M. Taylor   Contact
01

Hardware Recommendations for a Basic LEMP Server

Before installing a single package, you need appropriate hardware. Below are the recommended minimums and comfortable targets for a personal or small-business LEMP server running Ubuntu 24.04 LTS.

🟢
LEMP vs LAMP — What's the Difference? LEMP stands for Linux, Engine-X (Nginx), MariaDB, and PHP. The key difference from a LAMP stack is Nginx instead of Apache. Nginx is an event-driven web server that handles high concurrency with much lower memory usage than Apache — making it an excellent choice for high-traffic sites, reverse proxies, and resource-constrained hardware like a Raspberry Pi. The configuration approach is also different: Nginx uses server blocks instead of Apache's virtual hosts, and PHP runs as a separate PHP-FPM process rather than an Apache module.

CPU

Minimum: 2 cores Recommended: 4+ cores

Any modern x86-64 processor. Intel Core i3/i5 or AMD Ryzen 3/5 are excellent choices. Nginx is particularly well-suited to ARM64 including Raspberry Pi.

RAM

Minimum: 1 GB Recommended: 2–4 GB

Nginx uses significantly less RAM than Apache. A LEMP stack can run comfortably on 1 GB — making it ideal for low-spec hardware and VPS instances.

Storage

Minimum: 20 GB SSD Recommended: 60–120 GB SSD

SSDs dramatically improve MariaDB I/O performance. Avoid spinning HDDs for the OS drive if possible.

Network

Minimum: 100 Mbps NIC Recommended: 1 Gbps NIC

A wired Ethernet connection is strongly preferred over Wi-Fi for server stability and consistent latency.

Power Supply

UPS recommended

An Uninterruptible Power Supply (UPS) protects against sudden power loss that can corrupt MariaDB databases and the filesystem.

Popular Options

PC / Mini-PC / NUC / Pi

Intel NUC, Beelink Mini-PC, or any repurposed desktop PC are all practical. Raspberry Pi 5 (4 GB+) is recommended for ARM-based builds — Nginx's low footprint makes it an excellent match for the Pi. The Pi 4 can work but requires an extra bootloader step for USB boot.

ℹ️
Raspberry Pi Users — Read This First If you choose a Raspberry Pi, use the 64-bit (arm64) Ubuntu Server 24.04 image. Ubuntu on the Raspberry Pi uses cloud-init for its initial networking configuration — this causes problems with setting a local static IP in the traditional way. See Section 3 for the correct approach.

02

Installing Ubuntu 24.04 Server from Scratch

This section covers the complete bare-metal installation of Ubuntu 24.04 LTS Server for both standard x86-64 PCs and ARM64 Raspberry Pi hardware. Follow the path that matches your hardware.

ℹ️
Server Edition vs. Desktop Edition Always use the Server edition of Ubuntu for a LEMP server — not the Desktop edition. Server has no graphical interface, uses fewer resources, and gives you full control over exactly what is installed. Everything in this guide is done through the command line (CLI).

Part A — x86-64 PC Installation

What You Will Need

Step 1 — Write the ISO to USB

  1. Download the Ubuntu 24.04 LTS Server ISO from ubuntu.com. The filename will look like ubuntu-24.04-live-server-amd64.iso.
  2. Open Balena Etcher (or Rufus). Select the ISO file, select your USB drive as the target, and click Flash. This will erase everything on the USB drive.
  3. When flashing is complete, safely eject the USB drive.

Step 2 — Boot From USB

  1. Insert the USB drive into the target PC and power it on.
  2. Enter the BIOS/UEFI boot menu — typically by pressing F2, F10, F12, or Delete immediately on startup.
  3. Select your USB drive from the boot device list and press Enter.
  4. The Ubuntu installer will load. Select Try or Install Ubuntu Server and press Enter.

Step 3 — Walk Through the Ubuntu Server Installer

  1. Language — Select your language and press Enter.
  2. Keyboard Layout — Select your keyboard layout.
  3. Installation Type — Select Ubuntu Server (not the minimized version).
  4. Network — Leave DHCP as-is for now — you will set the static IP after installation (Section 3). Press Done.
  5. Proxy — Leave blank. Press Done.
  6. Ubuntu Archive Mirror — Accept the default. Press Done when the test passes.
  7. Storage Configuration — Select Use an entire disk. Press Done, then confirm the destructive action warning.
  8. Profile Setup — Enter your name, a server hostname (e.g., lempserver), a username, and a strong password. Write the password down.
  9. Ubuntu Pro — Select Skip for now.
  10. SSH Setup — Select Install OpenSSH server and press Space to check it, then Enter.
  11. Featured Server Snaps — Do not select anything. Press Done.
  12. Installation — Wait 5–15 minutes for the install to complete.
  13. When complete, select Reboot Now. Remove the USB drive when prompted.

Step 4 — First Login and Update

First Boot — Update the SystemBASH
sudo apt update && sudo apt upgrade -y
sudo reboot
x86-64 Install Complete Your server is installed and updated. Next: go to Section 3 and set your static IP first.

Part B — Raspberry Pi ARM64 Installation

What You Will Need

Step 1 — Write the Image Using Raspberry Pi Imager

  1. Download and install Raspberry Pi Imager on your desktop or laptop.
  2. Open Raspberry Pi Imager. Under Choose Device, select your Pi model.
  3. Under Choose OS, navigate to: Other general-purpose OS → Ubuntu → Ubuntu Server 24.04 LTS (64-bit).
  4. Under Choose Storage, select your microSD card.
  5. Click Next → Edit Settings and configure:
    • Hostname: lempserver
    • Username and Password — create your admin user
    • Enable SSH — check this box, select Use password authentication
    • Leave Wi-Fi blank — you will use Ethernet
  6. Click Save, then Yes twice to confirm. This erases the card.
  7. When writing is complete, eject the microSD card safely.

Step 2 — Boot and Connect

Connect to Pi via SSHBASH
# Find the Pi's IP from your router's DHCP client list
ssh [email protected]

# Accept the SSH fingerprint prompt: yes
# Then update immediately
sudo apt update && sudo apt upgrade -y
sudo reboot
⚠️
Raspberry Pi — Disable cloud-init Before Setting Static IP Ubuntu on the Raspberry Pi uses cloud-init to manage networking. Before proceeding to Section 3, you must disable cloud-init's network control first — otherwise your static IP will be overwritten on every reboot.
ARM64 / Raspberry Pi Install Complete Next: go to Section 3 and set your static IP first (disabling cloud-init networking before you do).

03

Setting Up a Static IP Address & SSH

A server must have a static local IP address so your router's port forwarding rules always point to the right machine. Ubuntu 24.04 uses Netplan for network configuration.

Step 1 — Identify Your Network Interface

Find Your InterfaceBASH
ip link show
# Look for eth0, ens3, enp3s0, or similar — ignore lo

ip route show
# The "default via" line shows your gateway — typically 192.168.1.1

Step 2 — Back Up and Edit Netplan

BackupBASH
sudo cp /etc/netplan/00-installer-config.yaml \
         /etc/netplan/00-installer-config.yaml.bak
/etc/netplan/00-installer-config.yamlYAML
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:             # Replace with your interface name
      dhcp4: no
      addresses:
        - 192.168.1.50/24
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses:
          - 1.1.1.1
          - 8.8.8.8
⚠️
YAML is Indentation-Sensitive Use spaces — never tabs. Use exactly 2 spaces per indentation level as shown above.

Step 3 — Apply and Verify

Apply Network ConfigBASH
sudo netplan try
sudo netplan apply
ip addr show eth0
ping -c 4 google.com

Raspberry Pi — Disable cloud-init Networking First

Disable cloud-init (Raspberry Pi Only)BASH
sudo bash -c \
  'echo "network: {config: disabled}" > \
  /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg'
sudo netplan apply
sudo reboot

Connect via SSH — Ditch the Server Keyboard

With a confirmed static IP you can now SSH in from your Windows or Linux desktop and copy every command from this webpage and paste it directly into your terminal.

Install SSH if NeededBASH
sudo apt install -y openssh-server
sudo systemctl enable ssh
sudo systemctl start ssh
Connect — Windows PowerShell or Linux/macOS TerminalBASH
# Replace yourusername and IP with your actual values
ssh [email protected]

# Type yes to accept fingerprint on first connection
# PowerShell paste: right-click | Linux paste: Ctrl+Shift+V | macOS: Cmd+V

If you prefer a graphical SSH client on Windows, PuTTY (free at putty.org) is the most popular option.

You Are Now Connected via SSH Your server keyboard and monitor can be unplugged. Continue to Section 4 (Router & Port Forwarding) then Section 5 for the full LEMP stack installation.

04

Router Setup & Port Forwarding

For your server to be reachable from the internet, your router must forward incoming traffic on the correct ports to your server's local IP address.

Required Port Forwarding RulesTABLE
Rule         Protocol  Ext Port  Int Port  Destination
─────────────────────────────────────────────────────
HTTP Web     TCP       80        80        192.168.1.50
HTTPS Web    TCP       443       443       192.168.1.50
🔐
Optional — SSH Port Forwarding (Port 22) Forwarding port 22 allows SSH from the internet but is a significant security risk on the default port. If you enable it, consider changing SSH to a non-standard port, disabling password login in favour of SSH key pairs, and enabling fail2ban. If you only need SSH from within your home network, do not forward port 22 at all.

Step-by-Step Port Forwarding

  1. Log into your router admin panel — typically at http://192.168.1.1 or http://192.168.0.1.
  2. Navigate to Port Forwarding, Virtual Servers, or NAT.
  3. Create a rule for HTTP: TCP, external port 80, internal port 80, destination = your server's static IP.
  4. Create a rule for HTTPS: TCP, external port 443, internal port 443, same destination IP.
  5. Save and apply. Some routers require a reboot.
  6. Verify your public IP at whatismyip.com — this is the IP your domain name will point to.
⚠️
Dynamic IP Addresses Most residential ISPs assign a dynamic public IP that can change without notice. Use a Dynamic DNS (DDNS) service such as DuckDNS or Cloudflare to automatically update your DNS record whenever your IP changes, or pay your ISP for a static IP.

05

Full LEMP Stack Installation

With networking configured, it's time to install the full stack — Nginx, MariaDB, and PHP-FPM along with all the modules needed to make them work together correctly.

🟢
PHP-FPM — Not a Module Unlike Apache's mod_php, Nginx does not embed PHP inside the web server process. Instead, Nginx passes PHP requests to a separate PHP-FPM (FastCGI Process Manager) service over a Unix socket. This means PHP-FPM must be running as its own service, and your Nginx server block configuration must tell Nginx where to find it. This is covered in detail in Section 7.

Step 1 — Update the System

System UpdateBASH
sudo apt update && sudo apt upgrade -y

Step 2 — Install Nginx

Install NginxBASH
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx
ℹ️
Quick Test Open a browser on another device on your network and navigate to your server's local IP (e.g., http://192.168.1.50). You should see the Welcome to nginx! default page. This confirms Nginx is running and reachable.

Step 3 — Configure UFW Firewall

UFW must be configured before you enable it — enabling it with no rules will lock you out of SSH immediately.

🚫
Add Rules First — Then Enable Enabling UFW with an empty ruleset blocks all incoming connections including your current SSH session. Add all rules first, then enable as the final step.
Configure UFW RulesBASH
# Allow HTTP — port 80
sudo ufw allow 80/tcp

# Allow HTTPS — port 443
sudo ufw allow 443/tcp

# Allow SSH — port 22 (do not skip this)
sudo ufw allow 22/tcp

# Alternatively use the Nginx profile shortcut for 80 and 443:
# sudo ufw allow 'Nginx Full'

# Now enable the firewall — rules are already in place
sudo ufw enable

# Verify
sudo ufw status verbose
Expected UFW StatusTEXT
Status: active
Default: deny (incoming), allow (outgoing)

To          Action    From
--          ------    ----
80/tcp      ALLOW IN  Anywhere
443/tcp     ALLOW IN  Anywhere
22/tcp      ALLOW IN  Anywhere
⚠️
Non-Standard Ports Need Their Own Rules UFW blocks every port not explicitly allowed. Examples:
  • SSH on custom port: sudo ufw allow 2222/tcp
  • App on port 8080: sudo ufw allow 8080/tcp
  • MariaDB from LAN only: sudo ufw allow from 192.168.1.0/24 to any port 3306

Step 4 — Install MariaDB

ℹ️
Why MariaDB? MariaDB is in Ubuntu's default repositories — no extra APT sources needed. It is a fully open-source drop-in replacement for MySQL, maintained by the original MySQL developers, and fully compatible with MySQL syntax and the PHP mysqli and pdo_mysql extensions.
Install MariaDBBASH
sudo apt install -y mariadb-server
sudo systemctl enable mariadb
sudo systemctl start mariadb
sudo systemctl status mariadb

Step 5 — Secure MariaDB

Secure MariaDBBASH
sudo mysql_secure_installation

Step 6 — Create a MariaDB Database and User

Create Database and UserSQL
-- Log into MariaDB as root
sudo mariadb -u root -p

CREATE DATABASE mywebsite_db;
CREATE USER 'webuser'@'localhost'
  IDENTIFIED BY 'YourStrongPassword123!';
GRANT ALL PRIVILEGES ON mywebsite_db.*
  TO 'webuser'@'localhost';
FLUSH PRIVILEGES;
SELECT user, host FROM mysql.user;
EXIT;

Step 7 — Verify MariaDB Login

Test User LoginBASH
mariadb -u webuser -p
SHOW DATABASES;
# You should see mywebsite_db listed
EXIT;

Step 8 — Install PHP-FPM and All Recommended Modules

Install PHP 8.3-FPM and ModulesBASH
sudo apt install -y \
  php8.3-fpm \
  php8.3-mysql \
  php8.3-cli \
  php8.3-common \
  php8.3-curl \
  php8.3-gd \
  php8.3-intl \
  php8.3-mbstring \
  php8.3-xml \
  php8.3-zip \
  php8.3-bcmath \
  php8.3-opcache

# Enable and start PHP-FPM
sudo systemctl enable php8.3-fpm
sudo systemctl start php8.3-fpm
sudo systemctl status php8.3-fpm

# Verify PHP version
php --version
🟢
No libapache2-mod-php — That's Correct Notice there is no Apache module in this list. Nginx communicates with PHP via a Unix socket file at /run/php/php8.3-fpm.sock. You will reference this socket path in every Nginx server block that needs to process PHP files. This is configured in Section 7.

Step 9 — Set Correct File Ownership

File Ownership & PermissionsBASH
# Nginx runs as www-data — set ownership of the web root
sudo chown -R www-data:www-data /var/www/html

# Add your own user to the www-data group
sudo usermod -aG www-data $USER

# Directories: 755 | Files: 644
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
newgrp www-data

06

Verifying PHP with phpinfo

The phpinfo() function produces a detailed page confirming your LEMP stack is wired together correctly — especially that Nginx is handing PHP requests to PHP-FPM successfully.

🚫
Security Warning — Delete This File After Testing The phpinfo.php file exposes detailed server configuration to anyone who visits it. Create it, verify your install, then delete it immediately. Never leave this file on a production server.

Step 1 — Create the File

Create phpinfo.phpBASH
sudo bash -c \
  'echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php'
sudo chown www-data:www-data /var/www/html/phpinfo.php
sudo chmod 644 /var/www/html/phpinfo.php
⚠️
phpinfo Won't Work Yet If You Haven't Configured PHP-FPM in Nginx The default Nginx config does not process PHP files out of the box. If you navigate to http://your-server-ip/phpinfo.php right now and see a blank page or a download prompt instead of the PHP info page, it means Nginx is not yet passing .php requests to PHP-FPM. Complete Section 7 first to configure your server block — then come back and test this page. Once Section 7 is done the test will work correctly.

Step 2 — View the Page in a Browser

After completing Section 7, navigate to: http://your-server-ip/phpinfo.php

Verify the following on the PHP info page:

Step 3 — Delete the File After Verification

Delete phpinfo.phpBASH
sudo rm /var/www/html/phpinfo.php

07

Creating Your First Website (HTTP on Port 80)

Nginx uses server blocks — the equivalent of Apache's virtual hosts — to serve websites. Each site gets its own config file in /etc/nginx/sites-available/ and is activated by symlinking it into /etc/nginx/sites-enabled/. For this example, assume your domain is example.com. Replace it with your actual domain throughout.

Step 1 — Create the Website Document Root

Create Web Root DirectoryBASH
sudo mkdir -p /var/www/example.com/public_html
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com

Step 2 — Create an Index PHP Page

/var/www/example.com/public_html/index.phpPHP
<?php
echo '<h1>Success! Your LEMP Server is Running.</h1>';
echo '<p>Nginx, PHP-FPM, and MariaDB are installed.</p>';
?>
Write the Index FileBASH
sudo nano /var/www/example.com/public_html/index.php
# Paste the PHP above, then Ctrl+X, Y, Enter to save

Step 3 — Create the Nginx Server Block Configuration

🟢
The fastcgi_pass Line Is Essential The fastcgi_pass directive is what connects Nginx to PHP-FPM. It tells Nginx to forward all .php requests to the PHP-FPM Unix socket. Without this block, Nginx will either serve your PHP files as plain text or return a blank page. Make sure the socket path matches your PHP version — for PHP 8.3 it is /run/php/php8.3-fpm.sock.
/etc/nginx/sites-available/example.comNGINX
server {
    listen       80;
    listen       [::]:80;
    server_name  example.com www.example.com;
    root         /var/www/example.com/public_html;
    index        index.php index.html index.htm;

    access_log   /var/log/nginx/example.com-access.log;
    error_log    /var/log/nginx/example.com-error.log;

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

    # Pass all .php requests to PHP-FPM
    location ~ \.php$ {
        include         snippets/fastcgi-php.conf;
        fastcgi_pass    unix:/run/php/php8.3-fpm.sock;
    }

    # Block access to .htaccess files
    location ~ /\.ht {
        deny all;
    }
}

Step 4 — Enable the Site and Test

Enable SiteBASH
# Create the symlink to enable the site
sudo ln -s /etc/nginx/sites-available/example.com \
          /etc/nginx/sites-enabled/

# Remove the default Nginx placeholder site
sudo rm /etc/nginx/sites-enabled/default

# Test the Nginx config for syntax errors
sudo nginx -t

# If output shows "test is successful", reload Nginx
sudo systemctl reload nginx
ℹ️
nginx -t Is Your Best Friend Always run sudo nginx -t before reloading. It checks every config file for syntax errors without affecting the running server. If it reports an error, fix it before reloading — a bad config will prevent Nginx from restarting.
Success Check Navigate to http://example.com (or your server's IP). You should see your "Success! Your LEMP Server is Running." page rendered by PHP — not the default Nginx page. Now go back and complete the phpinfo test from Section 6.

08

Installing Let's Encrypt with Certbot (HTTPS / Port 443)

Let's Encrypt provides free, trusted SSL/TLS certificates. Before proceeding, your domain's DNS A record must already point to your server's public IP address, and port 80 must be reachable from the internet.

⚠️
DNS Must Resolve First Certbot verifies domain ownership by making an HTTP request to your server from the internet. If your domain's DNS is not yet pointing to your public IP — or if port 80 is blocked — certificate issuance will fail. Verify DNS propagation first using dnschecker.org.

Step 1 — Install Certbot for Nginx

Install CertbotBASH
sudo apt install -y certbot python3-certbot-nginx
certbot --version

Step 2 — Obtain a Certificate for Your Domain

Request SSL CertificateBASH
sudo certbot --nginx -d example.com -d www.example.com

When Certbot finishes, it automatically modifies your Nginx server block to add SSL configuration and sets up HTTP → HTTPS redirection.

Step 3 — Verify the Updated Server Block

View Updated ConfigBASH
sudo cat /etc/nginx/sites-available/example.com

Certbot will have added SSL certificate paths and an HTTPS server block. It looks similar to this:

/etc/nginx/sites-available/example.com — After CertbotNGINX
server {
    listen       80;
    server_name  example.com www.example.com;
    # Certbot adds redirect to HTTPS:
    return 301 https://$host$request_uri;
}

server {
    listen       443 ssl;
    listen       [::]:443 ssl;
    server_name  example.com www.example.com;
    root         /var/www/example.com/public_html;
    index        index.php index.html;

    ssl_certificate
      /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key
      /etc/letsencrypt/live/example.com/privkey.pem;
    include
      /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam
      /etc/letsencrypt/ssl-dhparams.pem;

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

    location ~ \.php$ {
        include      snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

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

Step 4 — Test Automatic Certificate Renewal

Test Auto-RenewalBASH
sudo certbot renew --dry-run
sudo systemctl status certbot.timer
sudo certbot certificates
Success Check The dry run completes without errors. Your site loads over HTTPS with a valid padlock. HTTP traffic automatically redirects to HTTPS.

09

Nginx Server Blocks — Multiple Domains on One Server

Nginx can serve any number of distinct websites from a single server using server blocks. Each domain gets its own config file, its own document root, and its own SSL certificate. In this example, we'll add a second domain: secondsite.com.

Step 1 — Create the Document Root

Create Second Site DirectoryBASH
sudo mkdir -p /var/www/secondsite.com/public_html
sudo chown -R www-data:www-data /var/www/secondsite.com
sudo chmod -R 755 /var/www/secondsite.com

sudo bash -c 'cat > /var/www/secondsite.com/public_html/index.php << EOF
<?php echo "<h1>Welcome to Second Site!</h1>"; ?>
EOF'

Step 2 — Create the Server Block Config

/etc/nginx/sites-available/secondsite.comNGINX
server {
    listen       80;
    listen       [::]:80;
    server_name  secondsite.com www.secondsite.com;
    root         /var/www/secondsite.com/public_html;
    index        index.php index.html;

    access_log   /var/log/nginx/secondsite.com-access.log;
    error_log    /var/log/nginx/secondsite.com-error.log;

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

    location ~ \.php$ {
        include      snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

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

Step 3 — Enable the Site

Enable Second SiteBASH
sudo ln -s /etc/nginx/sites-available/secondsite.com \
          /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 4 — Get SSL Certificate for the Second Domain

Issue Certificate for Second DomainBASH
sudo certbot --nginx -d secondsite.com -d www.secondsite.com
ℹ️
Useful Management Commands
sudo ln -s /etc/nginx/sites-available/name /etc/nginx/sites-enabled/ — Enable a site
sudo rm /etc/nginx/sites-enabled/name — Disable a site (removes symlink only)
ls /etc/nginx/sites-enabled/ — List all active sites
sudo nginx -T — Dump the full compiled Nginx config for debugging

10

Advanced: HTTPS Reverse Proxy Server Blocks

A reverse proxy server block allows your LEMP server to act as a secure front-door for other services running on your local network. External visitors connect to your LEMP server over HTTPS, and Nginx silently forwards their requests to a different machine or port inside your network — passing back the response as if it came from the LEMP server itself.

This is extremely useful for exposing internal services (media servers, dashboards, apps) through a single public IP with proper SSL certificates. Nginx is particularly well-suited to this role — it handles reverse proxying with less overhead than Apache.

Prerequisites

Step 1 — Obtain a Certificate for Your Subdomain

Issue Certificate for SubdomainBASH
sudo certbot --nginx -d app.example.com

Step 2 — Create the Reverse Proxy Server Block

In this example, we'll proxy app.example.com to an internal service running at 192.168.1.42 on port 4533. Replace these with your actual subdomain, internal IP, and port.

/etc/nginx/sites-available/app.example.comNGINX
# HTTP — redirect all traffic to HTTPS
server {
    listen      80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS — reverse proxy to internal service
server {
    listen      443 ssl;
    listen      [::]:443 ssl;
    server_name app.example.com;

    ssl_certificate
      /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key
      /etc/letsencrypt/live/app.example.com/privkey.pem;
    include
      /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam
      /etc/letsencrypt/ssl-dhparams.pem;

    # Security header
    add_header Strict-Transport-Security
      "max-age=63072000; includeSubDomains; preload" always;

    # Forward all requests to the internal service
    location / {
        proxy_pass         http://192.168.1.42:4533;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header
          X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header
          X-Forwarded-Proto $scheme;
    }
}

Step 3 — Enable and Test

Enable Reverse Proxy SiteBASH
sudo ln -s /etc/nginx/sites-available/app.example.com \
          /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl -I https://app.example.com
🟢
proxy_set_header Explained The four proxy_set_header lines pass important information to the backend service: the original Host header, the real client IP via X-Real-IP, the full forwarded chain via X-Forwarded-For, and whether the original request was HTTP or HTTPS via X-Forwarded-Proto. Many backend applications need these headers to generate correct links and handle authentication properly.

To expose additional internal services, repeat this process — new subdomain DNS record, new certificate, new server block config file. Each subdomain is completely independent.


11

Setting Up a Second Physical Server as a Subdomain

Now we'll bring a second physical computer online as a separate web server running its own Nginx instance, and expose it to the internet through your LEMP server as a subdomain — for example, server2.example.com.

Overview of the Architecture

Traffic FlowTEXT
Internet User
     │
     ▼  (HTTPS — server2.example.com)
[Your LEMP Server — Public IP]    ← Port 443
     │
     │  (HTTP — internal LAN)
     ▼
[Second Server — 192.168.1.51]    ← Port 80

On the Second Server — Install Nginx

Second Server — Install NginxBASH
sudo apt update && sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx

sudo mkdir -p /var/www/website2/public_html
sudo chown -R www-data:www-data /var/www/website2
sudo chmod -R 755 /var/www/website2

sudo bash -c 'echo "<?php echo \
  \"<h1>Website 2 — Running on the Second Server</h1>\"; ?>" \
  > /var/www/website2/public_html/index.php'

On the Second Server — Server Block Config

/etc/nginx/sites-available/website2 (Second Server)NGINX
server {
    listen       80;
    listen       [::]:80;
    server_name  server2.example.com;
    root         /var/www/website2/public_html;
    index        index.php index.html;

    access_log   /var/log/nginx/website2-access.log;
    error_log    /var/log/nginx/website2-error.log;

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

    location ~ \.php$ {
        include      snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    location ~ /\.ht {
        deny all;
    }
}
Enable on Second ServerBASH
sudo ln -s /etc/nginx/sites-available/website2 \
          /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

On the DNS — Create the Subdomain Record

DNS RecordTEXT
Type:    A
Name:    server2   (becomes server2.example.com)
Value:   YOUR.PUBLIC.IP.ADDRESS
TTL:     Auto (or 3600)

On the LEMP Server — Reverse Proxy for the Subdomain

Issue Certificate (LEMP Server)BASH
sudo certbot --nginx -d server2.example.com
/etc/nginx/sites-available/server2.example.com (LEMP Server)NGINX
server {
    listen      80;
    server_name server2.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen      443 ssl;
    listen      [::]:443 ssl;
    server_name server2.example.com;

    ssl_certificate
      /etc/letsencrypt/live/server2.example.com/fullchain.pem;
    ssl_certificate_key
      /etc/letsencrypt/live/server2.example.com/privkey.pem;
    include
      /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam
      /etc/letsencrypt/ssl-dhparams.pem;

    add_header Strict-Transport-Security
      "max-age=63072000; includeSubDomains; preload" always;

    location / {
        proxy_pass
          http://192.168.1.51:80;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header
          X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header
          X-Forwarded-Proto $scheme;
    }
}
Enable and Reload on LEMP ServerBASH
sudo ln -s /etc/nginx/sites-available/server2.example.com \
          /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Final Verification Visit https://server2.example.com in a browser. You should see the "Website 2" page with a valid padlock — confirming HTTPS is handled by your LEMP server and transparently proxied to the second physical server on your LAN.
💡
What's Coming Next — File Permissions Deep Dive A dedicated Lesson 2 will cover Linux file permission levels in depth: octal notation, setuid/setgid bits, sticky bits, ACLs, and best practices for securing your web directories beyond the basics covered here.