Skip to content
Products
AI Website Builder New VPS Hosting Cloud Servers Web Hosting Dedicated Servers Domains
Company
About Documentation Support Center Contact Get Started Login Client Area
ALL SYSTEMS OPERATIONAL
VPS

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

Getwebup 6 min read

You've secured a fresh VPS - SSH keys are in, root login is off, the firewall is up - and now you actually need it to serve a website. That means installing a web server, a database, and a language runtime yourself, since a bare Ubuntu droplet doesn't ship with any of that. This is the LEMP stack: Linux, Nginx, MySQL (or MariaDB), and PHP. Here's how to put it together correctly the first time, including the mistakes that trip people up.

Before You Start

This assumes you already have a non-root sudo user and SSH key access working - if you haven't done that yet, lock down SSH and the firewall first. Installing a web stack on a server that's still wide open to the internet just gives attackers something worth attacking.

Update the system before installing anything else:

sudo apt update && sudo apt upgrade -y

Step 1: Install Nginx

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

Confirm it's running and allow it through the firewall if you're using UFW:

sudo ufw allow 'Nginx Full'
systemctl status nginx

Visit your server's IP in a browser. You should see the default "Welcome to nginx!" page. If the connection just hangs instead of loading, double check the firewall rule went through and that your VPS provider's own network firewall (a separate thing from UFW, common on cloud panels) also allows ports 80 and 443.

Step 2: Install MySQL or MariaDB

MariaDB is the more common default on Ubuntu now and it's a drop-in replacement for MySQL:

sudo apt install mariadb-server -y
sudo mysql_secure_installation

Answer yes to removing anonymous users, disabling remote root login, removing the test database, and reloading privilege tables. Skip this step and you're leaving a database with no root password and a public test database sitting on the server - not something you want indexed by a scanner.

Create the database and a dedicated user for your app now, rather than using root for everything 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-FPM

Nginx doesn't process PHP itself - it hands PHP requests off to PHP-FPM over a socket. Install the version your app needs plus the common extensions:

sudo apt install php-fpm php-mysql php-curl php-xml php-mbstring php-zip -y
sudo systemctl enable --now php8.3-fpm

Check the exact service name with systemctl list-units | grep php - the version number in the service name (php8.3-fpm, php8.1-fpm, etc.) depends on what Ubuntu shipped, and you'll need it in the next step.

Step 4: Connect Nginx to PHP-FPM

This is where most first-time setups break. Nginx needs an explicit server block telling it to pass .php requests to the FPM socket. Create a new site config:

sudo nano /etc/nginx/sites-available/yourdomain.com
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    root /var/www/yourdomain.com;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

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

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

Match the socket path in fastcgi_pass to your actual PHP-FPM version - a mismatch here is the single most common cause of a fresh LEMP install returning 502 Bad Gateway the moment you try to load a PHP file. Enable the site and test the config before reloading:

sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Create the web root and a test file to confirm PHP is actually executing, not just being served as plain text:

sudo mkdir -p /var/www/yourdomain.com
echo '<?php phpinfo(); ?>' | sudo tee /var/www/yourdomain.com/index.php

Load the domain in a browser. If you see the raw PHP code instead of a rendered info page, Nginx isn't passing the request to PHP-FPM at all - re-check the location ~ \.php$ block and the socket path.

Step 5: File Ownership and Permissions

PHP-FPM runs as its own user (usually www-data), and that user needs to own the files it serves - otherwise you'll get 403 errors or PHP "permission denied" warnings the moment your app tries to write a cache file or handle an upload:

sudo chown -R www-data:www-data /var/www/yourdomain.com
sudo find /var/www/yourdomain.com -type d -exec chmod 755 {} \;
sudo find /var/www/yourdomain.com -type f -exec chmod 644 {} \;

Delete the phpinfo() test file once you've confirmed PHP works - leaving it live exposes your full PHP configuration and installed module versions to anyone who requests the URL.

Step 6: Free SSL with Certbot

Point the domain's A record at the server IP first, then wait for DNS to actually resolve before running Certbot - it fails outright if the domain doesn't point at this server yet.

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

Certbot edits your Nginx config automatically to add the certificate and a redirect from HTTP to HTTPS. Confirm auto-renewal is scheduled - modern Certbot installs a systemd timer by default:

sudo systemctl list-timers | grep certbot

Common First-Setup Errors

SymptomLikely causeFix
502 Bad GatewayPHP-FPM socket path wrong or service not runningCheck the version in fastcgi_pass matches systemctl status phpX.X-fpm
Browser shows raw PHP codeNginx not passing .php requests to FPMRe-check the location ~ \.php$ block exists and is enabled
403 ForbiddenWrong file ownership or missing index filechown -R www-data:www-data on the web root
Certbot fails to obtain a certDNS A record not pointing at this server yetConfirm with dig +short yourdomain.com before retrying
Site loads on IP but not domainserver_name mismatch in the Nginx blockMatch it exactly to the domain, including www variant

Prevention: What to Set Up Before You Deploy Real Traffic

  • Set a PHP memory limit and upload size in /etc/php/8.3/fpm/php.ini before your app hits the default limits under real use.
  • Take a snapshot of the server once the stack is confirmed working - it's a much faster recovery point than rebuilding from scratch if a later change breaks something.
  • Enable UFW or your provider's firewall for anything beyond 22, 80, and 443 - a freshly opened MySQL port bound to 0.0.0.0 instead of localhost is a common and avoidable mistake.
  • Set up basic uptime monitoring (UptimeRobot or similar) so a crashed PHP-FPM pool gets caught in minutes, not when a visitor complains.

Once these pieces are wired together correctly, they rarely need touching again - most of the pain is in this first setup, not in running it day to day.

Frequently asked questions

Should I use MySQL or MariaDB on a new Ubuntu VPS?

MariaDB is the default in Ubuntu's repositories and is a drop-in replacement for MySQL with the same commands and syntax. Use MySQL specifically only if an application requires it by name - otherwise MariaDB is the simpler install.

Why do I get a 502 Bad Gateway right after installing PHP-FPM?

This almost always means the socket path in your Nginx server block doesn't match the actual running PHP-FPM version. Run systemctl status php8.3-fpm (adjusting the version) and make sure fastcgi_pass in your config points to that exact socket file.

Can I run this stack without a domain, just on the server's IP address?

Yes for testing, but you can't get a Let's Encrypt SSL certificate for a bare IP address - Certbot requires a domain name pointing at the server. Point a domain or subdomain at the IP first if you'll need HTTPS.

Do I need to install phpMyAdmin as part of this setup?

No, it's optional. You can manage MySQL/MariaDB entirely from the command line. If you want a GUI, install phpMyAdmin separately afterward and put it behind HTTP auth or an IP allowlist since it's a common attack target when left open.

What's the difference between this and just using cPanel on a VPS?

cPanel/WHM automates and manages this entire stack (and much more) through a web interface, at the cost of a license fee and some system resources. Installing LEMP manually gives you full control and no extra cost, but you're responsible for every configuration change and update yourself.

#lemp-stack #ubuntu #nginx #php-fpm #mysql #vps

Keep reading

Chat with Support