How to Host a Laravel App on DeployHQ Managed VPS

Devops & Infrastructure, PHP, Tutorials, and VPS

How to Host a Laravel App on DeployHQ Managed VPS

This guide is narrowly scoped: provision a DeployHQ Managed VPS from inside DeployHQ and host a Laravel application on it end to end. Server provisioning, runtime setup (PHP-FPM + Nginx), queue workers and scheduler, the DeployHQ deployment pipeline, custom domain, first deploy.

If you're looking for the broader Laravel deployment reference — comparisons against Forge / Deployer / Laravel Cloud, decision frameworks, the 15-point pre-deploy checklist — see our comprehensive Laravel deployment guide. This post is the Managed-VPS-specific path through that landscape. If your app deploys to a shared or third-party SFTP/SSH host rather than a Managed VPS, the general Laravel deployment tutorial walks that path instead.

What you'll build

By the end:

  • A DeployHQ Managed VPS provisioned and running Ubuntu
  • PHP-FPM, Nginx, and Composer installed and configured for Laravel
  • Laravel queue worker running under Supervisor (Redis or database-backed queue)
  • Laravel scheduler wired in via cron
  • DeployHQ build pipeline running Composer install + asset compilation
  • Custom domain serving over HTTPS via Cloudflare or Let's Encrypt
  • Atomic zero-downtime deploys with one-click rollback

Expected time: 30-45 minutes for a fresh project.

Prerequisites

  • A Laravel project in a Git repository (GitHub, GitLab, Bitbucket)
  • A DeployHQ account with beta features enabled (Settings > Beta Features)
  • A database for Laravel to use — either install MySQL/PostgreSQL on the same VPS (covered below) or point at a managed database service
  • Local Composer + PHP for running migrations and verifying the build before pushing

If you don't have a DeployHQ account yet, you can start a free trial — the trial includes one Managed VPS at the smallest size at no charge.

Step 1: Provision the Managed VPS

In your DeployHQ project, click New Server:

  1. Enter a name for the server (your reference only)
  2. Select Managed VPS from the protocol picker under the Hosting section
  3. Choose a region (data center location) and a server size. For a small-to-medium Laravel app: 1 vCPU / 2 GB RAM is a reasonable starting point. Adjust based on traffic and queue throughput.
  4. SSH keys: leave blank to let DeployHQ generate one automatically, or paste your own private key if you'd rather use yours
  5. Set the deployment path to /var/www/laravel-app (or your preferred root). DeployHQ writes deploys to this path.
  6. Click Create Server

Provisioning takes about a minute. Once the status flips to active, the server appears in Settings > Hosted Resources with its IP address, region, size, and sync status.

Step 2: Install the runtime stack

SSH into the server using the key DeployHQ generated. The server runs Ubuntu, so the standard apt commands work:

sudo apt update
sudo apt install -y nginx php8.3-fpm php8.3-cli php8.3-mbstring \
  php8.3-xml php8.3-curl php8.3-zip php8.3-mysql php8.3-bcmath \
  php8.3-intl composer redis-server supervisor mysql-server

(Adjust the PHP version to match what your Laravel project requires. Laravel 11 and Laravel 12 typically need PHP 8.2 or 8.3.)

Enable services to start on boot:

sudo systemctl enable nginx php8.3-fpm redis-server mysql supervisor
sudo systemctl start nginx php8.3-fpm redis-server mysql supervisor

Create the Laravel app directory if it doesn't exist:

sudo mkdir -p /var/www/laravel-app
sudo chown -R $USER:www-data /var/www/laravel-app

Step 3: Configure Nginx for Laravel

Create /etc/nginx/sites-available/laravel-app:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/laravel-app/current/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;
    }
}

The current symlink in the root path is important — DeployHQ's zero-downtime deployments use atomic symlink switching, and Nginx serves whatever current/public/ points to at any given moment.

Enable the site:

sudo ln -s /etc/nginx/sites-available/laravel-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 4: Set up Laravel database + environment

Create the database:

sudo mysql -e "CREATE DATABASE laravel_app CHARACTER SET utf8mb4;"
sudo mysql -e "CREATE USER 'laravel_app'@'localhost' IDENTIFIED BY 'STRONG-PASSWORD';"
sudo mysql -e "GRANT ALL ON laravel_app.* TO 'laravel_app'@'localhost';"

Create /var/www/laravel-app/shared/.env with your environment variables (DB credentials, app key, mail config, queue driver, etc.). DeployHQ's symlinks pattern preserves this file across deploys — the shared/ directory persists, while each release gets a new directory under releases/ that's symlinked from current.

Step 5: Configure the build pipeline in DeployHQ

In project build settings, add the build commands. DeployHQ's build pipeline runs these on DeployHQ's build infrastructure before uploading to the VPS:

composer install --no-dev --optimize-autoloader --no-interaction
npm ci
npm run build

(Skip the npm steps if your Laravel project has no frontend build pipeline.)

In the deployment hooks section, add a post-deploy hook to run on the VPS itself:

cd /var/www/laravel-app/current
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan queue:restart

queue:restart signals all running queue workers to gracefully exit and restart with the new code — important so workers don't run stale code after a deploy.

In the server settings under Configuration, ensure shared files includes .env and shared folders includes storage (Laravel writes to storage/logs, storage/framework/cache, and storage/framework/sessions — these need to persist across deploys). Enable atomic transfers and zero-downtime mode.

Step 6: First deploy

Push to your repository (or trigger a manual deploy). DeployHQ:

  1. Clones the repo at HEAD
  2. Runs the build pipeline on DeployHQ's build infrastructure
  3. Transfers the built artifact to the VPS via SSH
  4. Symlinks current/ to the new release
  5. Runs the post-deploy hooks (migrations, cache, queue restart)
  6. Old releases are kept for rollback

When the deploy completes, visit your server's IP (or domain) and verify Laravel responds.

Common first-deploy failures:

  • Permission errors on storage/chown -R www-data:www-data /var/www/laravel-app/shared/storage and verify the symlinks
  • APP_KEY missing in .env — Laravel needs a base64 app key; generate locally with php artisan key:generate --show and put the result in your VPS .env
  • Migrations fail — run php artisan migrate --force manually first via SSH and confirm the database is reachable

Step 7: Queue workers under Supervisor

Add /etc/supervisor/conf.d/laravel-queue.conf:

[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/laravel-app/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/laravel-queue.log
stopwaitsecs=3600

Reload Supervisor:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-queue:*

numprocs=2 runs two workers; increase if your job throughput needs more. Each worker handles jobs until max-time (1 hour) and gracefully restarts.

Schedule Laravel's task scheduler via cron:

sudo crontab -e -u www-data
# Add this line:
# * * * * * cd /var/www/laravel-app/current && php artisan schedule:run >> /dev/null 2>&1

Step 8: Custom domain + HTTPS

Point your domain at the server's IP via A record. Then either:

Use Cloudflare (free): add the domain to Cloudflare, set DNS to Proxied, and Cloudflare handles HTTPS termination at its edge.

Use Let's Encrypt directly on the VPS:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Certbot updates the Nginx config to listen on 443, configures SSL, and sets up auto-renewal via systemd timer.

Common gotchas

Queue workers don't pick up new code after deploy. Long-running workers keep the old code in memory. The php artisan queue:restart in the post-deploy hook signals workers to exit gracefully and restart with the new code on the next iteration.

storage/ symlinks break across deploys. The php artisan storage:link command needs to point public/storage at storage/app/public. With atomic deploys, this needs to be set up in shared/ (not in the release). Standard pattern: create the symlink in shared/storage, and let the deploy pipeline create the per-release public/storage symlink pointing to it.

Sessions die on every deploy. If SESSION_DRIVER=file and the session files live in storage/framework/sessions, make sure that directory is in the shared folders list — otherwise it's recreated empty on each deploy.

Cron doesn't fire because of permissions. Laravel's scheduler runs as the cron user. Make sure the cron is registered for the right user (usually www-data) and that the user owns the relevant directories.

Composer runs out of memory during build. Composer can be memory-hungry on complex dependency trees. Either bump the build worker's memory or use COMPOSER_MEMORY_LIMIT=-1 in the build pipeline's environment.

What you've shipped

You now have:

  • A Laravel app running on a DeployHQ Managed VPS
  • PHP-FPM + Nginx + MySQL + Redis on a real Ubuntu box you can SSH into
  • Queue workers under Supervisor and the scheduler under cron
  • Atomic zero-downtime deploys with one-click rollback via the DeployHQ dashboard
  • HTTPS via Cloudflare or Let's Encrypt

For the broader picture of where Managed VPS fits, the hosting hub catalogs all five DeployHQ hosting types. The Managed VPS pillar guide covers the provisioning surface in more depth and includes the comparison against DigitalOcean App Platform, Linode, and Render.

If you're choosing between DeployHQ as your deployment tool and Laravel Forge as a provisioning + deployment tool, DeployHQ vs Laravel Forge covers the trade-offs and how to use them together. For a broader category reference, best software deployment tools in 2026 places DeployHQ alongside the rest of the landscape, and your universal deployment and hosting platform is the cross-cluster bridge.

What's next

If you also have a static frontend (an Astro marketing site, a SvelteKit pre-rendered docs site), pair this Managed VPS with DeployHQ Static Hosting in the same DeployHQ project — frontend on Cloudflare's edge, Laravel API on the VPS, one project, one billing relationship.

For Laravel-specific deployment depth — Composer optimization, queue strategies, deployment hooks, environment management — the canonical Laravel guide is the next reference. And for the smooth migration story if you're moving from Forge or Vapor, the migrating to DeployHQ guide covers the broader pattern.

Start a free trial if you don't have an account yet. For the full Managed VPS product reference, see the Managed VPS support library.


Questions or feedback on Laravel + Managed VPS? Email support@deployhq.com or follow @deployhq on X for product updates.