In this tutorial, you will deploy Laravel on Ubuntu 24.04 with Nginx, PHP-FPM, Composer, correct Laravel permissions, HTTP verification, and an optional domain-backed HTTPS step.
Deploy Laravel on VPS means running a Laravel PHP application on your own Linux virtual machine, with Nginx handling public web traffic and PHP-FPM executing Laravel through the public/index.php front controller. This tutorial uses a Raff Ubuntu 24.04 VM and a fresh Laravel app so every command has a clean, verifiable result.
Raff Technologies operates 10,000+ VMs from Vint Hill, Virginia, with Raff VM plans built for production Linux workloads. Raff's General Purpose 2 vCPU / 4 GB VM costs $9.99/month with 80 GB NVMe SSD and unmetered VM traffic.
📌 Testing note: The HTTP deployment path in this tutorial was verified on a Raff Ubuntu 24.04 VM. The HTTPS section is included for production use, but it requires a real domain or subdomain pointed to the VM. Certbot was not executed during this test run because no domain was available.
Prerequisites:
- A Raff Ubuntu 24.04 VM
- SSH access with sudo privileges
- A domain or subdomain if you want to complete HTTPS
- A local terminal with
ssh - A Laravel project, or the sample Laravel app created in this tutorial
This tutorial was tested on a Raff General Purpose VM with 2 vCPU, 4 GB DDR5 RAM, 80 GB NVMe storage, running Ubuntu 24.04 LTS.
Use these placeholders throughout the tutorial:
| Placeholder | Replace with |
|---|---|
your_server_ip | Your Raff VM public IPv4 address |
laravel.example.com | Your domain or subdomain for HTTPS |
/var/www/laravel | Laravel application directory |


Step 1 — Prepare the Ubuntu VM
Start with a clean Ubuntu 24.04 VM so Nginx, PHP-FPM, Composer, and Laravel are installed from a known state.
Connect to your server:
ssh root@your_server_ip
When SSH asks whether you want to continue connecting, type:
yes
Update the package index and installed packages:
sudo apt update sudo apt upgrade -y
Install basic tools used later in the deployment:
sudo apt install -y curl unzip git software-properties-common ca-certificates
Verify the server is running Ubuntu 24.04:
clear lsb_release -a
Expected output includes:
Description: Ubuntu 24.04 LTS

Step 2 — Install Nginx and PHP-FPM
Install Nginx, PHP 8.3-FPM, Composer, and the PHP extensions Laravel needs on Ubuntu 24.04.
sudo apt install -y nginx \ php8.3-fpm php8.3-cli php8.3-common \ php8.3-curl php8.3-mbstring php8.3-xml \ php8.3-bcmath php8.3-zip php8.3-sqlite3 \ php8.3-intl composer
Enable Nginx and PHP-FPM:
sudo systemctl enable nginx sudo systemctl enable php8.3-fpm sudo systemctl start nginx sudo systemctl start php8.3-fpm
Verify both services are running:
sudo systemctl status nginx --no-pager sudo systemctl status php8.3-fpm --no-pager
Expected output includes:
Active: active (running)
Check the installed versions:
clear nginx -v php -v composer --version
Expected output includes Nginx, PHP 8.3, and Composer.

Verify Laravel's required PHP extensions are loaded:
php -m | grep -Ei 'ctype|curl|dom|fileinfo|filter|hash|mbstring|openssl|pcre|pdo|session|tokenizer|xml|bcmath'
Expected output includes these modules:
bcmath ctype curl dom fileinfo filter hash mbstring openssl pcre PDO session tokenizer xml
Step 3 — Configure firewall rules
Open SSH, HTTP, and HTTPS before sending public traffic to the server.
Allow SSH first:
sudo ufw allow OpenSSH
Allow Nginx web traffic:
sudo ufw allow 'Nginx Full'
Enable UFW:
sudo ufw --force enable
Verify the firewall rules:
sudo ufw status
Expected output:
Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere Nginx Full ALLOW Anywhere
Open the server IP in your browser:
http://your_server_ip
Expected result: the default Nginx welcome page loads.
Step 4 — Create the Laravel application
Create a fresh Laravel application in /var/www/laravel. For a real project, replace the composer create-project command with your Git clone and run the same deployment steps after it.
Create the application directory:
sudo mkdir -p /var/www/laravel sudo chown -R "$USER":www-data /var/www/laravel
Move into the directory:
cd /var/www/laravel
Create a new Laravel project:
composer create-project laravel/laravel .
Verify Laravel is installed:
php artisan --version
Expected output:
Laravel Framework 13.x.x
This tutorial was verified with:
Laravel Framework 13.17.0
Confirm the public entry point exists:
ls -la public/index.php artisan .env
Expected output includes:
artisan .env public/index.php
Step 5 — Configure environment and permissions
Set production environment values, create the SQLite database file, and give Nginx/PHP-FPM write access only where Laravel needs it.
Move into the app directory:
cd /var/www/laravel
Set production environment values. For HTTP testing by IP, use your server IP:
sed -i 's/^APP_ENV=.*/APP_ENV=production/' .env sed -i 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env sed -i 's#^APP_URL=.*#APP_URL=http://your_server_ip#' .env
Use SQLite for this first deployment:
sed -i 's/^DB_CONNECTION=.*/DB_CONNECTION=sqlite/' .env sed -i '/^DB_HOST=/d;/^DB_PORT=/d;/^DB_DATABASE=/d;/^DB_USERNAME=/d;/^DB_PASSWORD=/d' .env touch database/database.sqlite
Run the default migrations:
php artisan migrate --force
Expected output includes either migrations running or:
Nothing to migrate.
Set Laravel permissions without using chmod 777:
sudo chown -R "$USER":www-data /var/www/laravel sudo chmod -R ug+rwX storage bootstrap/cache database sudo find storage bootstrap/cache database -type d -exec chmod g+s {} \;
Clear and rebuild Laravel caches:
php artisan optimize:clear php artisan optimize
Verify the environment:
clear grep -E '^APP_ENV=|^APP_DEBUG=|^APP_URL=|^DB_CONNECTION=' .env
Expected output:
APP_ENV=production APP_DEBUG=false APP_URL=http://your_server_ip DB_CONNECTION=sqlite
Step 6 — Configure Nginx for Laravel
Configure Nginx to serve Laravel from the public directory and pass PHP requests to PHP-FPM.

Laravel must be served from the public directory. Do not point Nginx to /var/www/laravel, because the project root contains private files such as .env, config files, storage, logs, and application code.
Create the Nginx server block. For HTTP testing by IP, set APP_HOST to your VM public IP:
APP_HOST="your_server_ip" APP_DIR="/var/www/laravel" sudo tee /etc/nginx/sites-available/laravel > /dev/null <<NGINX server { listen 80; listen [::]:80; server_name ${APP_HOST}; root ${APP_DIR}/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; index index.php; charset utf-8; location / { try_files \$uri \$uri/ /index.php?\$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ ^/index\.php(/|$) { fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name; include fastcgi_params; fastcgi_hide_header X-Powered-By; } location ~ /\.(?!well-known).* { deny all; } } NGINX
Disable the default Nginx site and enable the Laravel site:
sudo rm -f /etc/nginx/sites-enabled/default sudo ln -sfn /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/laravel
Test the Nginx configuration:
sudo nginx -t
Expected output:
syntax is ok test is successful
Reload Nginx:
sudo systemctl reload nginx
Verify Nginx is serving the Laravel site over HTTP:
curl -I http://your_server_ip
Expected output includes:
HTTP/1.1 200 OK Server: nginx
Open this URL in your browser:
http://your_server_ip
Expected result: the Laravel welcome page loads through Nginx and PHP-FPM.

Step 7 — Enable HTTPS when a domain is ready
HTTPS requires a real domain or subdomain pointed to your VM. Let's Encrypt and Certbot cannot issue a normal browser-trusted certificate for only a raw IP address.
If you do not have a domain yet, skip this step for now and continue to Step 8. The Laravel deployment will still work over HTTP for testing. For production, return to this step after creating a DNS record.
Create this DNS record at your DNS provider:
| Type | Name | Value |
|---|---|---|
| A | laravel | your_server_ip |
That should produce a domain like:
laravel.example.com
After DNS resolves, update Laravel's APP_URL:
cd /var/www/laravel APP_HOST="laravel.example.com" sed -i "s#^APP_URL=.*#APP_URL=https://${APP_HOST}#" .env
Update the Nginx server_name to the domain:
sudo sed -i "s/server_name .*/server_name ${APP_HOST};/" /etc/nginx/sites-available/laravel
Test and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Install Certbot:
sudo apt install -y certbot python3-certbot-nginx
Request the certificate:
sudo certbot --nginx -d "$APP_HOST" --redirect
Follow the Certbot prompts for email, terms, and redirect behavior.
Verify HTTPS:
curl -I https://laravel.example.com
Expected output includes:
HTTP/2 200
Verify automatic renewal:
sudo certbot renew --dry-run
Expected output includes:
Congratulations, all simulated renewals succeeded
Step 8 — Optimize Laravel for production
Install production Composer dependencies, cache Laravel configuration, and verify the production environment.
Move into the app directory:
cd /var/www/laravel
Install production dependencies and optimize the autoloader:
composer install --no-dev --optimize-autoloader
Cache Laravel's production files:
php artisan optimize
Verify the environment:
php artisan about --only=environment
Expected output includes:
Environment Application Name Laravel Version PHP Version Environment Debug Mode
Confirm debug mode is off:
grep -E '^APP_ENV=|^APP_DEBUG=|^APP_URL=' .env
Expected output for HTTP testing:
APP_ENV=production APP_DEBUG=false APP_URL=http://your_server_ip
Expected output after the HTTPS step:
APP_ENV=production APP_DEBUG=false APP_URL=https://laravel.example.com
Step 9 — Verify the full Laravel deployment
Verify the browser endpoint, PHP-FPM handoff, Nginx logs, and Laravel logs before considering the deployment complete.
For HTTP testing, set:
APP_TEST_URL="http://your_server_ip"
If you completed HTTPS, use:
APP_TEST_URL="https://laravel.example.com"
Check the response headers:
curl -I "$APP_TEST_URL"
Expected output for HTTP testing:
HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu)
Expected output after HTTPS:
HTTP/2 200 Server: nginx
Check that the Laravel page loads:
curl -s "$APP_TEST_URL" | grep -i "Laravel" | head
Expected output includes:
<title>Laravel</title>
Check Nginx errors:
sudo tail -n 30 /var/log/nginx/error.log
Expected result: no new PHP-FPM, permission, or upstream errors. A line such as client closed keepalive connection is not a deployment failure.
Check Laravel logs:
sudo test -f /var/www/laravel/storage/logs/laravel.log \ && sudo tail -n 30 /var/www/laravel/storage/logs/laravel.log \ || echo "No Laravel log file yet."
Expected result: either no log file yet or no new production errors.

At this point, Laravel is verified through Nginx and PHP-FPM. If you used only the server IP, the deployment is verified over HTTP. If you completed the domain-backed Certbot step, the deployment is verified over HTTPS.
Cleanup
Use this section only if you want to remove the Laravel deployment from the VM.
⚠️ Warning: The following commands delete the Laravel application directory, Nginx site, and TLS certificate. Back up application code and data first.
If you issued a certificate, delete it first:
sudo certbot delete --cert-name laravel.example.com
Remove the Nginx site:
sudo rm -f /etc/nginx/sites-enabled/laravel sudo rm -f /etc/nginx/sites-available/laravel sudo nginx -t sudo systemctl reload nginx
Delete the Laravel application:
sudo rm -rf /var/www/laravel
Remove public web firewall rules if this VM will no longer serve websites:
sudo ufw delete allow 'Nginx Full' sudo ufw status
Troubleshooting
Nginx returns 403 or 404
Cause: Nginx is not pointing to Laravel's public directory, or the server block is not enabled.
Fix:
grep -R "root" /etc/nginx/sites-enabled/laravel ls -la /var/www/laravel/public/index.php sudo nginx -t sudo systemctl reload nginx
The Nginx root must be:
/var/www/laravel/public
Nginx returns 502 Bad Gateway
Cause: PHP-FPM is stopped, or the Nginx fastcgi_pass socket path is wrong.
Fix:
sudo systemctl status php8.3-fpm --no-pager ls -la /run/php/php8.3-fpm.sock grep fastcgi_pass /etc/nginx/sites-enabled/laravel
The server block should use:
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
Restart PHP-FPM and reload Nginx:
sudo systemctl restart php8.3-fpm sudo systemctl reload nginx
Laravel returns 500 Server Error
Cause: Missing app key, wrong permissions, failed migration, or an invalid .env value.
Fix:
cd /var/www/laravel php artisan key:generate --force php artisan migrate --force sudo chmod -R ug+rwX storage bootstrap/cache database php artisan optimize:clear php artisan optimize
Check the Laravel log:
sudo tail -n 50 /var/www/laravel/storage/logs/laravel.log
Certbot fails domain validation
Cause: The domain does not point to the VM public IP, or ports 80 and 443 are blocked.
Fix:
dig +short laravel.example.com curl -I http://laravel.example.com sudo ufw status
The domain must resolve to your Raff VM public IPv4 address, and UFW must allow Nginx Full.
The site works by IP but HTTPS is not available
Cause: HTTPS needs a domain. A raw IP address can load the Laravel app over HTTP, but Certbot needs a domain name for a normal browser-trusted certificate.
Fix: Create an A record pointing a subdomain to the VM IP, update APP_URL, update Nginx server_name, and run the Certbot commands in Step 7.
Uploaded files are not publicly visible
Cause: Laravel's public storage symlink has not been created.
Fix:
cd /var/www/laravel php artisan storage:link ls -la public/storage
Expected output shows public/storage pointing to storage/app/public.
Conclusion
You now have Laravel running on an Ubuntu 24.04 VM with Nginx, PHP-FPM, Composer, production environment values, correct writable directories, and terminal verification. In this test run, the application was verified over HTTP using the VM public IP. For production HTTPS, point a domain to the VM and complete the Certbot step.
If you have not deployed your Raff VM yet, you can launch one from the Raff VM product page.
Next steps:
- Set up a full LEMP foundation with
/learn/tutorials/set-up-lemp-stack-ubuntu-24-04 - Install MySQL with
/learn/tutorials/install-mysql-ubuntu-24-04 - Secure Nginx with
/learn/tutorials/secure-nginx-lets-encrypt-ubuntu-24-04 - Compare deployment patterns with
/learn/guides/shared-hosting-to-raff-saas-migration

