Alright, so you want to deploy a Laravel application on DigitalOcean. That’s a solid choice; DigitalOcean’s Droplets give you a lot of control without overcomplicating things, and it’s pretty affordable for most projects. I’ll walk you through this step by step, focusing on a manual setup using a Droplet with Ubuntu 24.04 LTS, Nginx as the web server, and MySQL for the database, since that’s a common, reliable stack for Laravel apps. This tutorial is based on a real-time process; it’s not the quickest way (for that, there’s DigitalOcean’s App Platform, which I’ll touch on briefly at the end), but it gives you full ownership of your environment.
I’m assuming you already have a Laravel app developed locally or on GitHub—maybe something built with Laravel 11 / 12, the latest as of now. If not, you can follow along with a fresh install and swap in your code later. We’ll cover prerequisites, server setup, installation, configuration, and some security tips to keep things locked down.
Prerequisites
Before we jump into the server, make sure you’ve got these sorted on your end:
- A DigitalOcean account. If you don’t have one, sign up at digitalocean.com—it’s straightforward, and they often have promo credits for new users.
- SSH key pair generated on your local machine (use
ssh-keygenif you haven’t). This is crucial for secure access. - Your Laravel app ready, with any dependencies listed in composer.json.
- A domain name pointed to your Droplet’s IP (optional but recommended for production; you can use the IP for testing).
- Basic comfort with the terminal— we’ll be using sudo a lot.
Once that’s set, head to your DigitalOcean dashboard and create a new Droplet. Choose Ubuntu 24.04 LTS as the OS, a basic plan (start with $6/month for 1GB RAM if it’s a small app), add your SSH key during setup, and pick a region close to your users. After creation, note down the public IP—you’ll SSH into it as root initially.
Initial Server Setup and Security
Okay, now that your Droplet is spinning up, let’s connect and harden it. Security isn’t an afterthought here; we’ll bake it in from the start to avoid common pitfalls like unauthorized access.
First, SSH in as root:
ssh root@your-droplet-ipUpdate your system packages right away to patch any vulnerabilities:
sudo apt update && sudo apt upgrade -yNext, create a non-root user for everyday use—running as root is risky. Let’s call it ‘deployuser’, but you can pick whatever:
adduser deployuser
usermod -aG sudo deployuserNow, set up SSH for this user. Copy your public key (from ~/.ssh/id_rsa.pub on your local machine) to the server:
su - deployuser
mkdir ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys # Paste your public key here, save, then exit
chmod 600 ~/.ssh/authorized_keys
exit # Back to rootTest logging in as deployuser from your local machine:
ssh deployuser@your-droplet-ipIf that works, disable root login and password auth in SSH config for better security:
sudo nano /etc/ssh/sshd_configFind and set:
PermitRootLogin no
PasswordAuthentication noThen restart SSH:
sudo systemctl restart sshWhile we’re on security, set up a firewall with UFW:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full' # We'll install Nginx soon
sudo ufw enableThis blocks everything except SSH and web traffic. Also, install Fail2Ban to ban brute-force attackers:
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2banAs I mentioned in the prerequisites, comfort with the terminal helps here—these steps prevent a lot of headaches down the line.
Installing the LEMP Stack
Laravel needs PHP, a web server, and a database. We’ll go with Nginx (lightweight and great for PHP apps), PHP 8.3 (compatible with Laravel 11/12), and MySQL.
Install Nginx:
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginxTest it by visiting http://your-droplet-ip in a browser—you should see the Nginx welcome page.
Add the PHP repo for the latest version:
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:ondrej/php -y
sudo apt updateNow install PHP and extensions Laravel loves (like mbstring for strings, xml for parsing, etc.):
sudo apt install php8.3 php8.3-fpm php8.3-cli php8.3-common php8.3-curl php8.3-mbstring php8.3-mysql php8.3-xml php8.3-zip php8.3-bcmath php8.3-gd php8.3-intl -yInstall Composer globally for managing dependencies:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composerFor the database, install MySQL:
sudo apt install mysql-server -y
sudo mysql_secure_installation # Follow prompts: enable validation, remove anon users, etc.Create a database and user for your app:
sudo mysql
CREATE DATABASE laravel_db;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;Replace ‘strong_password’ with something secure—use a generator if needed.
Deploying Your Laravel Application
With the stack ready, let’s deploy your app. Navigate to the web root:
cd /var/wwwIf your app is on GitHub, clone it (replace with your repo):
sudo git clone https://github.com/yourusername/your-laravel-app.git html
sudo chown -R www-data:www-data html
cd htmlIf it’s a fresh install for testing:
composer create-project --prefer-dist laravel/laravel .Either way, install dependencies:
composer install --optimize-autoloader --no-devCopy .env.example to .env and edit it:
sudo cp .env.example .env
sudo nano .envUpdate these:
APP_KEY= # We'll generate this next
APP_URL=http://your-domain-or-ip
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD=strong_passwordGenerate the app key:
php artisan key:generateSet permissions—Nginx needs write access to storage and cache:
sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html
sudo chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cacheRun migrations and seed if your app has them:
php artisan migrate
php artisan db:seed # If applicableClear caches for good measure:
php artisan config:clear
php artisan cache:clear
php artisan route:clearConfiguring Nginx
Now point Nginx to your app. Create a config file:
sudo nano /etc/nginx/sites-available/yourappPaste this block, replacing ‘your-domain.com’ and paths as needed:
server {
listen 80;
server_name your-domain.com;
root /var/www/html/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; }
error_page 404 /index.php;
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 ~ /\.(?!well-known).* {
deny all;
}
}Enable it:
sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/Test config:
sudo nginx -tRestart Nginx:
sudo systemctl restart nginxVisit your domain or IP—your Laravel app should greet you. If you see errors, check logs with sudo tail -f /var/log/nginx/error.log.
Security Best Practices
Remember what I said about baking in security? Here’s more: Always use HTTPS—get a free Let’s Encrypt cert with Certbot:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d your-domain.comKeep Laravel updated: Run composer update regularly, but test in staging first.
Protect against common threats: Use Laravel’s built-in CSRF protection, validate all inputs, and avoid storing sensitive data in .env without encryption. Set APP_DEBUG=false in production, and monitor for vulnerabilities with tools like Laravel Security Checker.
Install Redis or Memcached for caching if your app scales, and consider DigitalOcean’s Managed Databases for offloading MySQL.
For backups, use DigitalOcean Spaces or snapshots—automate them weekly.
Easier Alternatives
If this feels too hands-on, try DigitalOcean’s App Platform. Fork their Laravel sample repo on GitHub, connect it in the dashboard, set env vars, and deploy—it handles scaling and updates automatically. It’s PaaS-style, great for quick launches.
There you have it—that should get your app live and running smoothly. If you hit snags, like permission issues, double-check the chown steps from earlier. Ping me in the comments below if something’s unclear!