Deploying Laravel on a $4 VPS: Production Nginx, Redis, Queue Workers & SSL
How to configure a low-memory Ubuntu VPS for production Laravel: PHP-FPM ondemand tuning, Redis cache and queues, Supervisor workers, Let's Encrypt SSL, and UFW firewall hardening.
You don't need expensive serverless subscriptions or high-tier cloud instances to run a fast, production-ready Laravel application. With proper resource tuning, a $4–$5/month VPS (2 vCPU, 2GB–4GB RAM on Hetzner, RackNerd, or Contabo) can comfortably handle hundreds of thousands of monthly requests with sub-100ms response times.
Here is the exact server configuration recipe.
1. Swap Space (Preventing OOM Kills)
On low-memory servers, memory spikes during composer install or asset builds can trigger the Linux Out-Of-Memory (OOM) killer. Always enable a swap file:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
2. Tuning PHP-FPM for Low RAM
Edit /etc/php/8.4/fpm/pool.d/www.conf:
pm = ondemand
pm.max_children = 12
pm.process_idle_timeout = 30s
pm.max_requests = 500
ondemand spawns PHP child processes only when HTTP requests arrive and frees memory after idle timeouts, preventing inactive workers from hoarding RAM.
3. Production Nginx Configuration
Create /etc/nginx/sites-available/cyrusmwendwa.tech:
server {
listen 80;
server_name cyrusmwendwa.tech www.cyrusmwendwa.tech;
root /var/www/personal-site/public;
index index.php;
charset utf-8;
client_max_body_size 20M;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";
gzip on;
gzip_types text/css application/javascript image/svg+xml application/json;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* \.(jpg|jpeg|png|webp|avif|gif|ico|css|js|woff2)$ {
expires 1y;
access_log off;
add_header Cache-Control "public, immutable";
}
location ~ /\.(?!well-known).* {
deny all;
}
}
4. Supervisor Queue Worker
Create /etc/supervisor/conf.d/laravel-worker.conf:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/personal-site/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=deploy
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/personal-site/storage/logs/worker.log
stopwaitsecs=3600
Reload supervisor:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
5. Automated Backups with Spatie Backup
Never run a production database without automated off-site backups. With spatie/laravel-backup configured, schedule nightly backups in crontab:
* * * * * cd /var/www/personal-site && php artisan schedule:run >> /dev/null 2>&1