This site contains affiliate links — we may earn a commission if you sign up through them.

PHP Laravel Deployment Guide on DigitalOcean Droplet 2026

Step-by-step guide to deploying a Laravel application on a DigitalOcean Droplet, covering PHP/Composer setup, MySQL, Nginx/PHP-FPM, queue workers, and SSL.

PHP Laravel Deployment Guide on DigitalOcean Droplet 2026

Deploying Laravel to production on your own DigitalOcean Droplet gives you complete flexibility and control over your environment, unlike PaaS offerings like App Platform that manage infrastructure automatically. This guide walks you through each step from preparing the server, installing PHP and Composer, configuring Nginx, PHP-FPM, queue workers, and SSL to get it ready for real-world use. We'll also cover common mistakes and best practices for teams managing their own servers.

Prepare Droplet + Install PHP/Composer

Deploying Laravel starts with choosing a Droplet size appropriate for your app's actual load. For projects just getting started or with minimal users, the Basic plan with 1 GiB RAM / 1 vCPU / 25GB SSD at $6/month works, but for a real production website with continuous traffic and queue workers, we recommend starting at 2 GiB RAM / 1 vCPU / 50GB SSD at $12/month (2,000 GiB transfer) and up, because PHP-FPM and MySQL can easily consume memory together under increasing traffic. For heavier processing, consider the 4 GiB RAM / 2 vCPU plan at $24/month. Thai users should select the sgp1 (Singapore) region, which has the lowest latency among DigitalOcean's 15 regions, followed by blr1 (Bangalore). For the OS, we recommend Ubuntu 24.04 LTS because it has newer PHP versions available through PPA. After creating your Droplet, SSH in and update the system first with apt update && apt upgrade -y. Then enable Cloud Firewall (free, no additional cost) to restrict access to only SSH (22), HTTP (80), and HTTPS (443) to reduce the attack surface. Next, install PHP 8.3 with the Laravel extensions via ondrej's PPA using add-apt-repository ppa:ondrej/php -y && apt update, then install with apt install php8.3 php8.3-fpm php8.3-mysql php8.3-mbstring php8.3-xml php8.3-bcmath php8.3-curl php8.3-zip php8.3-gd -y. Verify the version with php -v. Finally, install Composer using the official installer curl -sS https://getcomposer.org/installer | php, then move it to PATH with mv composer.phar /usr/local/bin/composer, and verify with composer --version before proceeding to install Laravel.

  1. Minimum production Droplet is 2 GiB RAM / 1 vCPU ($12/month) or higher — the $6 plan is for testing only
  2. Thai users select sgp1 (Singapore) region for lowest latency, with blr1 (Bangalore) as backup
  3. Enable Cloud Firewall free to restrict ports to 22/80/443 before installing any software
  4. Install PHP 8.3 via ondrej/php PPA with extensions: mbstring, xml, bcmath, curl, zip, gd, mysql
  5. Composer installed via official installer then moved to /usr/local/bin/composer

Install Laravel + Configure .env

From our hands-on testing — once the server is ready, there are two approaches to get Laravel code onto the Droplet: create a new project with composer create-project laravel/laravel myapp or clone an existing repository with git clone [email protected]:yourteam/myapp.git /var/www/myapp, then enter the directory and install dependencies for production with composer install --no-dev --optimize-autoloader. The --no-dev flag skips dev packages like phpunit, and --optimize-autoloader creates a classmap for faster loading in production. Next, configure the environment file with cp .env.example .env, then generate a new APP_KEY with php artisan key:generate. Never reuse the APP_KEY from local as it encrypts sessions and cookies. Then edit .env to match production, such as APP_ENV=production, APP_DEBUG=false (critical—leaving debug on exposes stack traces and config to users), and APP_URL=https://yourdomain.com to match your actual domain so URLs generated by Laravel in emails and assets are correct. Create a symbolic link for uploads with php artisan storage:link and set permissions so the web server can write to logs/cache with chown -R www-data:www-data storage bootstrap/cache followed by chmod -R 775 storage bootstrap/cache. This step is often overlooked but is a common cause of 500 errors later. Finally, test with php artisan about to verify the environment matches your intentions before connecting to the database in the next step.

  1. Create a new project with composer create-project or git clone an existing repo into /var/www/myapp
  2. composer install --no-dev --optimize-autoloader for production only; don't install dev dependencies
  3. APP_KEY must be regenerated fresh every time with php artisan key:generate; never reuse the local key
  4. Always set APP_ENV=production and APP_DEBUG=false to prevent error data leakage

Connect to MySQL

Laravel supports both self-hosted MySQL on the same Droplet or DigitalOcean's Managed Database as a separate instance; each has trade-offs. Self-hosting with apt install mysql-server -y has no additional cost but you manage backups, patching, and tuning yourself. DigitalOcean's Managed MySQL starts at $15.15/month for Basic tier with 1 vCPU / 1 GiB RAM and 10-30GiB storage, offering automated backup, failover, and metrics out of the box—ideal for projects needing high stability or teams that don't want to manage the database. Both can connect via VPC (private networking, free) so traffic between Droplet and database doesn't traverse the public internet. If you choose self-hosting, run mysql_secure_installation after install to set a root password and close basic vulnerabilities. Then create a database and app-specific user with CREATE DATABASE myapp; CREATE USER 'myapp'@'localhost' IDENTIFIED BY 'strong-password'; GRANT ALL PRIVILEGES ON myapp.* TO 'myapp'@'localhost'; FLUSH PRIVILEGES;. Never use the root user in your production app. Then edit .env to match your actual config: DB_CONNECTION=mysql, DB_HOST=127.0.0.1, DB_PORT=3306, DB_DATABASE=myapp, DB_USERNAME=myapp, DB_PASSWORD=strong-password. For Managed Database, replace DB_HOST with the private hostname from your control panel. Finally, run migrations for real with php artisan migrate --force; the --force flag is required for production because Laravel won't run migrations without explicit confirmation when APP_ENV=production is detected, as a safety measure.

Configure Nginx + PHP-FPM

Nginx acts as the front-end web server that receives requests and forwards PHP files to PHP-FPM for processing via socket. Start by installing with apt install nginx -y, then create a new config file at /etc/nginx/sites-available/myapp where the root points only to the Laravel public directory, not the project root, because .env and application code must never be directly accessible via URL. A minimal config example is server { listen 80; server_name yourdomain.com; root /var/www/myapp/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; } location ~ /\.(?!well-known).* { deny all; } }. A common mistake is the fastcgi_pass path not matching the actual PHP-FPM socket for your installed version—always verify with ls /run/php/ first. After writing the config, enable it by creating a symbolic link to sites-enabled with ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/, then always test syntax before reloading with nginx -t. If there are no errors, reload with systemctl reload nginx. On the PHP-FPM side, check the pool config at /etc/php/8.3/fpm/pool.d/www.conf, especially pm.max_children, which should match your Droplet's RAM—on a 2 GiB machine, don't set it too high or you'll run out of memory since each child process consumes RAM separately. After adjusting the pool config, restart with systemctl restart php8.3-fpm, then open your browser to test the domain and verify you see the Laravel welcome page or your actual app.

Key takeaway: Nginx root must point to /var/www/myapp/public only, never the project root

Queue Worker and SSL

Laravel apps with background jobs like sending emails or processing files should use queues instead of synchronous processing to avoid making users wait. Test running a worker with php artisan queue:work first, but this command stops immediately when the SSH session closes or the server reboots, so you need Supervisor to keep the worker running and auto-restart on crash. Install Supervisor with apt install supervisor -y, then create a config file at /etc/supervisor/conf.d/myapp-worker.conf with content roughly like [program:myapp-worker] process_name=%(program_name)s_%(process_num)02d command=php /var/www/myapp/artisan queue:work --sleep=3 --tries=3 --max-time=3600 autostart=true autorestart=true numprocs=2 user=www-data. Then run supervisorctl reread && supervisorctl update && supervisorctl start myapp-worker:*. The critical part is that every time you deploy new code, you must run php artisan queue:restart to signal the old workers to finish their jobs and reload the new code, otherwise they'll keep running the old code cached in memory. For SSL, use Let's Encrypt via Certbot, which issues free certificates and auto-renews them. Install with apt install certbot python3-certbot-nginx -y, then issue a certificate and auto-configure Nginx in one command certbot --nginx -d yourdomain.com -d www.yourdomain.com. Certbot sets up a systemd timer to renew every 90 days automatically. Verify with certbot renew --dry-run first. Before using SSL, make sure Cloud Firewall has port 443 open. After SSL is set up, force a redirect from http to https site-wide for user security.

When to Use This Approach (Real-World Use Cases)

Self-hosting Laravel on a Droplet suits teams that want full control over the environment—like needing custom PHP extensions, tuning MySQL themselves, or running multiple cron jobs and queue workers with custom scheduling. It also keeps costs predictable since you pay a fixed monthly amount per Droplet size rather than variable costs like serverless services. On the flip side, if your team has no dedicated sysadmin or prefers fast deployment without managing Nginx/PHP-FPM/SSL, DigitalOcean App Platform is a better choice. It's a PaaS that builds and deploys from a Git repository automatically, starting at $5/month for a shared container (1 vCPU, 512MiB RAM, 50GiB transfer) up to $50/month (2 vCPU, 4GiB RAM, 250GiB transfer) for larger workloads. Another consideration is the database. For small projects or tight budgets where you accept the risk of managing it yourself, running MySQL on the same Droplet is sufficient, but once the app gets real users and needs high uptime with automated backups, moving to Managed MySQL at $15.15/month is worth the time savings. For projects expected to scale quickly and run multiple instances with a load balancer, consider DigitalOcean Kubernetes (DOKS)—the control plane is free, and you only pay for node pools at standard Droplet pricing ($12/month per node minimum). Overall, the single-Droplet approach in this guide is best for small-to-medium projects where your team can manage the system and wants full control over the stack.

Common Mistakes and Fixes

The most common mistake is getting 500 errors after deploy, usually from storage or bootstrap/cache permission issues. Fix by running chown -R www-data:www-data storage bootstrap/cache && chmod -R 775 storage bootstrap/cache again and checking the actual log at storage/logs/laravel.log instead of guessing. Another issue is editing .env but the app still using old values because Laravel cached the config. Fix with php artisan config:clear first, then php artisan config:cache to cache new values. A frequent gotcha is the Nginx fastcgi_pass path not matching the real PHP-FPM socket, especially after upgrading PHP versions like 8.2 to 8.3 and forgetting to update the config, causing Nginx to return 502 Bad Gateway. Always check with ls /run/php/ against your config. For queue workers, forgetting to run php artisan queue:restart after deploy means workers keep running old code. From the database side, SQLSTATE[HY000] [2002] Connection refused usually means MySQL's bind-address is locked to 127.0.0.1 but your DB_HOST in .env points elsewhere, or the user wasn't granted privileges from the connecting host. Finally, on small Droplets with 512MiB-1GiB RAM, composer install might crash from out-of-memory. Fix by temporarily adding swap with fallocate -l 1G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile before running composer.

Best Practices (Best Practices)

To avoid user-facing downtime during each deploy, use zero-downtime deployment: pull new code into a separate release folder, then switch the current symbolic link to point to the new release only after everything is ready (the approach tools like Deployer and Envoyer use) instead of git pull on top of running code, which can show users partial updates mid-deploy. Before every restart, cache config, routes, and views with php artisan config:cache && php artisan route:cache && php artisan view:cache to reduce processing per request. Never commit .env to git—set up .gitignore from the start. For scheduled tasks, create just one cron line that calls the Laravel scheduler * * * * * cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1, then define all tasks in code, letting Laravel manage them. Let Supervisor handle queue workers as described so they auto-restart on crash or reboot. For backups, take Droplet snapshots regularly—they cost only $0.06/GiB/month and are invaluable for disaster recovery. Enable DigitalOcean Monitoring (free with 1 Uptime Check per account) to alert you if the server has problems. For security, disable SSH password login and use SSH keys only, restrict ports with Cloud Firewall (free), and bind a Reserved IP from the start if you plan future migrations or rebuilds—it's free when attached to an active Droplet and saves you from updating DNS each server change.

Get $200 Free Credit →

Frequently Asked Questions

What Droplet size is adequate for a small-to-medium Laravel web app?
For testing or very light traffic, the 1 GiB RAM / 1 vCPU plan at $6/month works, but for real production with PHP-FPM, MySQL, and queue workers running together, start at 2 GiB RAM / 1 vCPU for $12/month and scale up to 4 GiB RAM / 2 vCPU ($24/month) as traffic increases.
Should I self-host MySQL on the Droplet or use DigitalOcean Managed Database?
Self-hosting MySQL on the Droplet costs nothing extra if you can handle backup/patching yourself, but if you need high reliability, automated backups, and failover, Managed MySQL starting at $15.15/month (1 vCPU/1 GiB RAM, 10-30GiB storage) is worth it once your app has real users.
Does SSL cost anything for a Laravel app on a Droplet?
No cost. Use Let's Encrypt via Certbot for free—issue and install certificates automatically with one command certbot --nginx -d yourdomain.com, and it auto-renews every 90 days via systemd timer without needing separate cron setup.
My queue worker stops running every time I close SSH or reboot the server—how do I fix it?
Running php artisan queue:work directly stops when the session ends. Use Supervisor with autostart=true and autorestart=true to keep workers running continuously and auto-restart on crash or server reboot.
How does deploying Laravel on a Droplet differ from DigitalOcean App Platform?
A Droplet gives you full server control (Nginx, PHP-FPM, cron, queue) and suits teams with a sysadmin. App Platform is a PaaS that auto-builds/deploys from Git without infrastructure management, starting at $5/month (1 vCPU, 512MiB RAM) and best for teams prioritizing speed over system control.
Which DigitalOcean region should Thai users choose for deploying Laravel?
sgp1 (Singapore) has the lowest latency for Thai users among DigitalOcean's 15 regions, with blr1 (Bangalore) as a backup. Both regions support all Droplet features including VPC and Managed Database.