How to Deploy Laravel on an Ubuntu VPS: From Zero to Live
Deploying Laravel to a VPS (Virtual Private Server) is a major milestone when your project is ready for real users: you get more stable performance, full control over the server configuration, and a clearer path to scaling compared to shared hosting. For beginners, though, the process can feel overwhelming—installing a web server, PHP, a database, setting file permissions, and enabling SSL.
In this tutorial, you’ll deploy Laravel on an Ubuntu VPS from absolute zero until your application is reachable via domain/HTTPS. The focus is a production-ready setup (not just “it loads in the browser”), using safer practices: a non-root user, firewall rules, correct Nginx configuration, clean permissions, and Laravel production optimization.
End goal: your Laravel app is online using Nginx + PHP-FPM, connected to a database, and served over HTTPS.
Prerequisites
Before you start, prepare:
- Ubuntu VPS 22.04 LTS / 24.04 LTS (recommended for 2026)
- SSH access to the server (IP + username + password or SSH key)
- A domain (optional, but strongly recommended for SSL). Example:
example.com - A Laravel project ready to deploy (in a Git repo or on your local machine)
- Basic Linux command knowledge (cd, ls, nano, systemctl)
Minimum specs (typical):
- 1 vCPU
- 1–2 GB RAM (2 GB is more comfortable if you use queue/redis)
- 20 GB storage
Deployment architecture used
In this tutorial we’ll use a common and maintainable stack:
- Nginx as the web server and reverse proxy
- PHP-FPM to execute PHP (Laravel)
- MySQL/MariaDB as the database (you can replace it with PostgreSQL; the concept is similar)
- Certbot (Let’s Encrypt) for HTTPS SSL
- (Optional) Redis for cache/queue
- (Optional) Supervisor for queue workers
Simple request flow:
- A user visits
https://example.com - Nginx receives the request on port 443
- Nginx serves from Laravel’s
public/directory - For PHP requests, Nginx forwards to PHP-FPM
- Laravel reads
.envand connects to the database
Important Laravel folders:
public/→ the document root for your web server (required)storage/andbootstrap/cache/→ must be writable by the web server user.env→ production environment configuration
Step 1 — Update the server + basic security
Log in to your VPS:
ssh root@YOUR_SERVER_IP
1A) Update packages
apt update
apt -y upgrade
apt -y autoremove
If the kernel/core components were updated, reboot:
reboot
1B) Create a non-root user + sudo
It’s best not to work daily as root.
adduser deploy
usermod -aG sudo deploy
Try logging in as the new user:
ssh deploy@YOUR_SERVER_IP
1C) Enable the UFW firewall
Install UFW and set minimal rules:
sudo apt -y install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Step 2 — Install core components (Nginx, PHP, Composer, DB)
2A) Install Nginx
sudo apt -y install nginx
sudo systemctl enable --now nginx
Check status:
sudo systemctl status nginx
2B) Install PHP-FPM + extensions required by Laravel
In 2026, common Laravel PHP versions are 8.2 or 8.3. On Ubuntu, the exact version may vary. Install these core packages:
sudo apt -y install \
php-fpm php-cli php-common \
php-mbstring php-xml php-curl php-zip php-bcmath php-intl \
php-mysql
Check PHP version:
php -v
List PHP-FPM services (name may vary, e.g. php8.3-fpm):
systemctl list-units --type=service | grep php
2C) Install Composer
A safe option on Ubuntu is the package manager (it may not be the newest release, but it’s stable). For this tutorial:
sudo apt -y install composer
composer --version
2D) Install MariaDB (MySQL-compatible)
sudo apt -y install mariadb-server
sudo systemctl enable --now mariadb
Run basic hardening:
sudo mysql_secure_installation
Create a database + dedicated app user:
sudo mariadb
In the MariaDB prompt:
CREATE DATABASE laravel_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'STRONG_PASSWORD_HERE';
GRANT ALL PRIVILEGES ON laravel_app.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Step 3 — Prepare the app directory and deploy your code
We’ll place the project under /var/www.
3A) Create the project directory
sudo mkdir -p /var/www/laravel-app
sudo chown -R deploy:deploy /var/www/laravel-app
3B) Deploy via Git (recommended)
Install git:
sudo apt -y install git
Clone the repo (example):
cd /var/www
git clone <https://github.com/username/your-laravel-repo.git> laravel-app
cd /var/www/laravel-app
If the repo is private, use a deploy key or a secure token approach (don’t hardcode secrets on the server).
Alternative: upload files (scp/rsync)
If you don’t use Git, you can upload from your laptop:
rsync -avz --exclude node_modules --exclude vendor ./ deploy@YOUR_SERVER_IP:/var/www/laravel-app/
Step 4 — Configure Laravel for production
Enter the project directory:
cd /var/www/laravel-app
4A) Install production dependencies
composer install --no-dev --optimize-autoloader
4B) Set up environment variables (.env)
Create .env from the example:
cp .env.example .env
Edit .env:
nano .env
At minimum, set:
APP_NAME="Laravel App"APP_ENV=productionAPP_DEBUG=falseAPP_URL=https://example.comDB_CONNECTION=mysqlDB_DATABASE=laravel_appDB_USERNAME=laravel_userDB_PASSWORD=STRONG_PASSWORD_HERE
Generate the app key:
php artisan key:generate
4C) Run database migrations
In production, use --force so it doesn’t prompt/block.
php artisan migrate --force
4D) Correct permissions (most common source of errors)
Nginx/PHP-FPM typically runs as www-data. Laravel needs write access to:
storage/bootstrap/cache/
Set ownership and permissions safely:
sudo chown -R www-data:www-data /var/www/laravel-app/storage /var/www/laravel-app/bootstrap/cache
sudo find /var/www/laravel-app/storage -type d -exec chmod 775 {} \;
sudo find /var/www/laravel-app/bootstrap/cache -type d -exec chmod 775 {} \;
Note: avoid chmod -R 777 because it’s a security risk.
Step 5 — Configure Nginx for Laravel
5A) Create an Nginx server block
Create a config file:
sudo nano /etc/nginx/sites-available/laravel-app
Example configuration (adjust the domain and PHP-FPM socket version):
server {
listen 80;
server_name example.com www.example.com;
root /var/www/laravel-app/public;
index index.php index.html;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
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 ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Enable the site and (optionally) disable the default site:
sudo ln -s /etc/nginx/sites-available/laravel-app /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
Test the config:
sudo nginx -t
Reload Nginx:
sudo systemctl reload nginx
At this point, your app should be reachable via HTTP if your DNS points to the VPS.
Step 6 — Enable HTTPS SSL (Let’s Encrypt)
6A) Install Certbot
sudo apt -y install certbot python3-certbot-nginx
6B) Request a certificate
Make sure your domain already points to the VPS IP, then run:
sudo certbot --nginx -d example.com -d www.example.com
Choose the HTTP → HTTPS redirect option if prompted.
6C) Verify auto-renewal
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
Step 7 — Optimize Laravel for production
Once online, enable caches for better performance:
php artisan config:cache
php artisan route:cache
php artisan view:cache
Important notes:
route:cacheis safe if your routes don’t use closures that can’t be serialized.- After changing config/routes, rebuild the caches.
For safer permissions after deploy:
- don’t make the entire project owned by
www-data - only make writable directories (
storage/,bootstrap/cache/) owned/writable as needed
Step 8 — Scheduler and queue (optional but recommended)
8A) Laravel Scheduler (cron)
Run as an appropriate user (often www-data or your deploy user). Add a cron entry:
sudo crontab -u www-data -e
Add:
* * * * * cd /var/www/laravel-app && php artisan schedule:run >> /dev/null 2>&1
8B) Queue worker (Supervisor)
If your app uses queues, use Supervisor:
sudo apt -y install supervisor
Create the config:
sudo nano /etc/supervisor/conf.d/laravel-worker.conf
Example:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/laravel-app/artisan queue:work --sleep=3 --tries=3 --timeout=90
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/laravel-app/storage/logs/worker.log
stopwaitsecs=3600
Reload Supervisor:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status
Common troubleshooting (frequent errors)
1) 403 Forbidden / weird 404 behavior
Common causes:
- Nginx
rootis not pointing topublic/ - incorrect directory permissions
Verify your Nginx config contains:
root /var/www/laravel-app/public;
2) 502 Bad Gateway
Usually PHP-FPM is down or the socket is wrong.
Check PHP-FPM:
sudo systemctl status php8.3-fpm
Check available sockets:
ls -lah /run/php/
Make sure fastcgi_pass matches your PHP-FPM socket.
3) Database connection errors
Confirm .env values and that the DB user has access.
Test DB login:
sudo mariadb -u laravel_user -p laravel_app
4) Storage/log not writable
Ensure writable directories are owned correctly:
sudo chown -R www-data:www-data /var/www/laravel-app/storage /var/www/laravel-app/bootstrap/cache
5) Check logs
- Nginx error log:
sudo tail -n 100 /var/log/nginx/error.log
- Laravel log:
tail -n 100 /var/www/laravel-app/storage/logs/laravel.log
- systemd logs:
sudo journalctl -u nginx -n 100 --no-pager
Final checklist: Laravel is online
- [ ] Your domain points to
YOUR_SERVER_IP - [ ] Nginx is active and config is valid (
nginx -tis OK) - [ ] Nginx root points to
public/ - [ ]
.envis production-ready (APP_DEBUG=false) - [ ] Migrations have run (
migrate --force) - [ ]
storage/andbootstrap/cache/permissions are correct - [ ] HTTPS is enabled and certbot auto-renewal works
- [ ] Laravel caches are built (config/route/view caches)
FAQ (for SEO)
1) What’s the difference between deploying Laravel on a VPS vs shared hosting?
A VPS gives you full control (Nginx, PHP-FPM, Supervisor, Redis) and is typically more stable and scalable.
2) Why must the document root point to public?
Only public/ should be exposed to the internet. Other folders contain sensitive files.
3) Do I need a domain to deploy Laravel?
Not strictly for testing via IP, but Let’s Encrypt SSL generally requires a domain.
4) Why do we run php artisan migrate with --force in production?
Laravel blocks migrations in production unless you use --force, to prevent accidental destructive actions.
5) Nginx looks correct but I still get 502. What’s the most common cause?
PHP-FPM is not running, or the fastcgi_pass socket doesn’t match your installed PHP-FPM version.
6) How do I update Laravel code after it’s online?
Typically: git pull, composer install --no-dev, then rebuild caches and run migrations if needed.
7) Do I need Redis for Laravel?
Not required, but very helpful for caching and queues on larger apps.
8) What’s the minimum RAM for Laravel on a VPS?
A lightweight app can run on 1 GB, but 2 GB is safer, especially with a database and queue workers.

Komentar
Posting Komentar