Kenapa Nginx + PHP-FPM Bukan Hanya Apache
Banyak shared hosting default ke Apache. Di VPS, kombinasi Nginx + PHP-FPM memberi performa lebih baik untuk aplikasi PHP modern seperti Laravel — terutama dalam menangani concurrent request dan static file serving.
Artikel ini memandu deploy Laravel dari nol di VPS Ubuntu: setup Nginx, PHP-FPM 8.3, MySQL 8, Composer, Git, dan konfigurasi production-ready.
Target Hasil
- Aplikasi Laravel bisa diakses via domain (HTTP + HTTPS dengan Certbot nanti)
- PHP-FPM berjalan dengan user terpisah (bukan www-data default)
- MySQL hanya bisa diakses dari localhost
- File permission aman: storage dan bootstrap/cache writable, lainnya readonly
.envtidak bisa diakses publik
Spesifikasi Server
| Komponen | Versi |
|---|---|
| OS | Ubuntu 24.04 LTS |
| PHP | 8.3 (FPM) |
| Nginx | latest stable |
| MySQL | 8.0 |
| Composer | 2.x |
| Node.js | 22 LTS |
1. Update System & Install Dependencies
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx mysql-server php8.3-fpm php8.3-mysql \
php8.3-cli php8.3-curl php8.3-mbstring php8.3-xml php8.3-zip \
php8.3-bcmath php8.3-gd php8.3-intl unzip git curl
2. Setup MySQL
sudo mysql_secure_installation
Pilih: VALIDATE PASSWORD (yes, strong), set root password, remove anonymous users, disallow root login remotely, remove test database, reload privileges.
Buat database dan user untuk aplikasi:
CREATE DATABASE laravel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'password_kuat';
GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
3. Clone Project & Setup Laravel
sudo mkdir -p /var/www
cd /var/www
sudo git clone https://github.com/username/repo.git laravel-app
sudo chown -R $USER:www-data /var/www/laravel-app
cd /var/www/laravel-app
Install dependencies:
composer install --no-dev --optimize-autoloader
npm ci && npm run build
Setup .env:
cp .env.example .env
php artisan key:generate
Edit .env:
APP_NAME="Nama Aplikasi"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://domainkamu.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=password_kuat
4. Permission — Yang Sering Salah
Hanya storage dan bootstrap/cache yang perlu writable oleh PHP:
sudo chown -R $USER:www-data /var/www/laravel-app
sudo find /var/www/laravel-app -type d -exec chmod 755 {} \;
sudo find /var/www/laravel-app -type f -exec chmod 644 {} \;
sudo chmod -R 775 /var/www/laravel-app/storage
sudo chmod -R 775 /var/www/laravel-app/bootstrap/cache
Kenapa ini penting:
- File PHP hanya perlu readable, bukan executable (nginx tidak mengeksekusi PHP langsung)
storagebutuh writable untuk log, cache, upload.envchmod 644 aman karena Nginx dikonfigurasi menolak akses ke file dot (lihat langkah 5)
5. Konfigurasi Nginx
sudo nano /etc/nginx/sites-available/laravel
server {
listen 80;
server_name domainkamu.com www.domainkamu.com;
root /var/www/laravel-app/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; }
location ~ /\.(?!well-known).* {
deny all;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(css|js|gif|ico|jpeg|jpg|png|svg|webp|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Aktifkan:
sudo ln -s /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
6. Optimasi Laravel untuk Production
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
Kenapa harus di-cache:
- Tanpa cache, Laravel membaca semua file config, route, dan view setiap request
- Dengan cache, semua sudah dikompilasi jadi satu file — lebih cepat
- CATATAN: Kalau kamu deploy ulang dengan perubahan config/route, jalankan ulang command cache ini
7. Queue Worker (Opsional — Kalau Pakai Job/Email)
Laravel queue butuh worker yang berjalan di background:
sudo nano /etc/systemd/system/laravel-queue.service
[Unit]
Description=Laravel Queue Worker
After=network.target
[Service]
User=www-data
WorkingDirectory=/var/www/laravel-app
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 --max-time=3600
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now laravel-queue
8. Scheduler — Cron untuk Task Scheduler Laravel
Laravel scheduler butuh satu cron entry:
sudo crontab -e
* * * * * php /var/www/laravel-app/artisan schedule:run >> /dev/null 2>&1
9. SSL dengan Certbot (Let's Encrypt)
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d domainkamu.com -d www.domainkamu.com
Auto-renew sudah terpasang via systemd timer. Cek:
sudo systemctl status certbot.timer
Troubleshooting
502 Bad Gateway
- Penyebab: PHP-FPM tidak berjalan atau socket salah
- Cek:
sudo systemctl status php8.3-fpm - Pastikan socket path di Nginx config benar:
/var/run/php/php8.3-fpm.sock
403 Forbidden
- Penyebab: permission folder tidak benar atau Nginx tidak bisa baca file
- Cek:
sudo -u www-data cat /var/www/laravel-app/public/index.php
Laravel error 500, tapi tidak ada pesan di browser
- Penyebab: APP_DEBUG=false menyembunyikan error
- Solusi:
sudo tail -100 /var/www/laravel-app/storage/logs/laravel.log
.env changes not taking effect
- Penyebab: config sudah di-cache (config:cache)
- Solusi:
php artisan config:clear && php artisan config:cache
Takeaway
Deploy Laravel ke VPS bukan proses yang rumit — tapi butuh perhatian ke detail permission, konfigurasi Nginx, dan caching.
Yang paling sering salah:
- Permission folder — hanya
storagedanbootstrap/cacheyang butuh write access - FastCGI socket path — pastikan versi PHP di Nginx config sama dengan yang terinstall
- Cache config — setiap deploy ulang, clear dan recache
- .env tidak diproteksi dari akses publik — Nginx harus blokir akses ke file dot