This guide is narrowly scoped: provision a [DeployHQ Managed VPS](https://www.deployhq.com/hosting/managed-vps) from inside [DeployHQ](https://www.deployhq.com) and host a Laravel application on it end to end. Server provisioning, runtime setup (PHP-FPM + Nginx), queue workers and scheduler, the [DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com/blog/how-to-deploy-laravel-zero-downtime-build-pipelines-and-best-practices). 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](https://www.deployhq.com/blog/deploying-a-laravel-react-application-to-fortrabbit-using-deployhq) walks that path instead.

## What you'll build

By the end:

- A [DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com) 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](https://www.deployhq.com/features/build-pipelines) 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](https://www.deployhq.com) 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](https://www.deployhq.com) dashboard
- HTTPS via Cloudflare or Let's Encrypt

For the broader picture of where Managed VPS fits, the [hosting hub](https://www.deployhq.com/hosting) catalogs all five [DeployHQ](https://www.deployhq.com) hosting types. The [Managed VPS pillar guide](https://www.deployhq.com/blog/managed-vps-hosting-on-deployhq) covers the provisioning surface in more depth and includes the comparison against DigitalOcean App Platform, Linode, and Render.

If you're choosing between [DeployHQ](https://www.deployhq.com) as your deployment tool and Laravel Forge as a provisioning + deployment tool, [DeployHQ vs Laravel Forge](https://www.deployhq.com/blog/deployhq-vs-laravel-forge-how-they-differ-and-how-to-use-them-together) covers the trade-offs and how to use them together. For a broader category reference, [best software deployment tools in 2026](https://www.deployhq.com/blog/best-software-deployment-tools) places [DeployHQ](https://www.deployhq.com) alongside the rest of the landscape, and [your universal deployment and hosting platform](https://www.deployhq.com/blog/deployhq-your-universal-deployment-platform-for-all-hosting-types) 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](https://www.deployhq.com/hosting/static) in the same [DeployHQ](https://www.deployhq.com) project — frontend on Cloudflare's edge, Laravel API on the VPS, one project, one billing relationship.

For [Laravel-specific deployment depth](https://www.deployhq.com/guides/laravel) — 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](https://www.deployhq.com/blog/migrating-to-deployhq-a-smooth-transition-guide)[DeployHQ](https://www.deployhq.com) guide covers the broader pattern.

[Start a free trial](https://www.deployhq.com/signup) if you don't have an account yet. For the full Managed VPS product reference, see the [Managed VPS support library](https://www.deployhq.com/support/servers/managed-vps-hosting).

* * *

Questions or feedback on Laravel + Managed VPS? Email [support@deployhq.com](mailto:support@deployhq.com) or follow [@deployhq](https://x.com/deployhq) on X for product updates.

