DevOps & Systems 2 min read 761 views

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.

CM
Cyrus Mwendwa AUTHOR • ARCHITECT
Senior Product Engineer • Nairobi, Kenya

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