A local Laravel application can work perfectly and still fail after release. Missing PHP extensions, incorrect document roots, stale configuration, and inactive queue workers cause most deployment problems. If you need to deploy Laravel project code safely, use a repeatable release process and verify each layer before sending traffic to it.
The hosting choice changes the commands, but the requirements stay consistent. Your server needs the correct PHP version, protected secrets, a writable Laravel runtime, and a web root pointed at public/.
Choose the right Laravel hosting model
Your hosting environment determines how much of the deployment process you control.
Shared hosting
Shared hosting is suitable for small Laravel applications with modest traffic. Choose a provider that supports the required PHP version, Composer, SSH access, cron jobs, databases, and document-root configuration.
The main limitation is process control. You may not be able to run Supervisor, Redis workers, custom Nginx rules, or background services. Scheduled tasks can usually run through cron, but queues may need short worker commands instead of permanent processes.
Keep the full Laravel application outside the public web directory whenever the host allows it. Only the contents of public/ should be directly accessible from the internet.
A VPS
A VPS gives you control over PHP-FPM, Nginx or Apache, database services, Redis, Supervisor, SSL, firewall rules, and deployment scripts. It also gives you responsibility for updates, backups, monitoring, and access control.
A VPS works well when the application needs queues, scheduled jobs, private files, or several deployment environments. A provider-specific Laravel setup guide, such as this Laravel VPS deployment guide, can help with server differences.
Managed Laravel hosting
Managed options reduce server administration. Laravel Cloud, Forge, and Vapor handle different parts of provisioning and deployment. Review their current pricing, supported services, PHP versions, queue options, and database setup before committing.
The best choice is the one that matches your application. Don’t pay for a VPS if the site only needs a database and standard web requests. Don’t use restrictive shared hosting for an application that depends on workers and scheduled processing.
Prepare the application before release
Start with a clean production build. Test the exact commit you plan to deploy, not an uncommitted local version.
The current Laravel 13 documentation lists PHP 8.3 as the minimum version. Laravel 12 requires PHP 8.2. Required extensions include Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer, and XML. Check the Laravel version used by your application because the PHP requirement can change between major versions.
Use Composer locally to confirm that dependencies install correctly:
composer validate
composer install
php artisan test
Commit composer.json and composer.lock. The lock file keeps production dependencies consistent with your tested build.
Your production installation should exclude development packages:
composer install --no-dev --prefer-dist --optimize-autoloader
Run this command inside the application directory. It installs the versions recorded in the lock file and creates an optimized autoloader.
Build frontend assets before the application reaches production:
npm ci
npm run build
Do this during your build process when possible. The production server should receive the compiled assets, not depend on an interactive development command.
The official Laravel deployment documentation covers the framework’s current production requirements and optimization commands. Use the documentation for your installed Laravel version when commands differ.
Protect environment variables and application secrets
Never commit .env to Git. Add it to .gitignore and create the production file directly on the server or through your hosting provider’s secret settings.
A production environment normally includes values similar to these:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com
LOG_CHANNEL=stack
LOG_LEVEL=error
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=application
DB_USERNAME=application_user
DB_PASSWORD=use-a-secret-value
CACHE_STORE=redis
QUEUE_CONNECTION=redis
Use the database, cache, and queue drivers your host actually provides. SQLite may work for a small internal application, but MySQL or PostgreSQL is usually a better production choice for a public Laravel site.
Generate the application key once for a new installation:
php artisan key:generate
Don’t run this command on every deployment. Changing APP_KEY can make existing encrypted data unreadable and can invalidate application sessions.
Keep API keys, database passwords, mail credentials, and payment secrets in approved secret storage. Hosting panels, environment managers, and deployment platforms often provide encrypted variable storage. If a secret enters Git history, remove it and rotate it. Deleting the file in a later commit doesn’t make the old secret safe.
After updating .env, clear stale cached configuration before rebuilding it:
php artisan config:clear
php artisan config:cache
Once configuration is cached, calls to env() outside configuration files can return unexpected results. Read environment values inside files in config/, then access them through Laravel’s configuration system.
Point the web server to Laravel’s public directory
This is the most important server setting. The web server must serve the Laravel public/ directory, not the project root.
A typical Nginx server block looks like this:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/current/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;
}
}
The PHP-FPM socket path varies by operating system and PHP version. Confirm it on the server before restarting Nginx. Add HTTPS through your provider or a trusted certificate service, then redirect HTTP traffic to HTTPS.
The try_files rule sends requests that aren’t real files to Laravel’s front controller. Without it, application routes may return 404 errors even though the Laravel installation is correct.
For Apache, enable URL rewriting and keep Laravel’s generated public/.htaccess file. For shared hosting, set the domain document root to the application’s public directory if the control panel supports it.
If the host forces the document root to public_html, keep the rest of the application above that directory. Some providers require copying the public contents into public_html and adjusting index.php paths. Treat that as a hosting-specific workaround, not the preferred structure. Never expose .env, storage, vendor, or the full project root through the browser.
Run the production deployment sequence
A simple manual release can follow this order:
cd /var/www/example.com/current
git fetch origin
git checkout production
git pull --ff-only origin production
composer install --no-dev --prefer-dist --optimize-autoloader
npm ci
npm run build
php artisan storage:link
php artisan migrate --force
php artisan optimize
Each command has a separate purpose. git pull --ff-only avoids creating an accidental merge on the server. Composer installs the locked production dependencies. The frontend commands compile browser assets. storage:link exposes approved public files. Migrations update the database schema. optimize prepares Laravel’s caches for production.
Review migrations before running them. Take a database backup first, especially when a migration changes or removes columns. The --force flag confirms that you intentionally want to run migrations in a production environment.
Laravel needs write access to storage and bootstrap/cache. Grant ownership or group permissions to the PHP-FPM user without using broad 777 permissions. The correct service user may be www-data, nginx, or a provider-specific account.
For larger applications, use release directories such as releases/20260905-1200 and a current symlink. Build the new release separately, run checks, then switch the symlink. Deployment tools such as Deployer’s Laravel recipe can automate this pattern.
Configure queues, storage, and scheduled tasks
A web request shouldn’t wait for email delivery, report generation, image processing, or other slow work. Send those tasks to a queue.
A VPS deployment can run a persistent worker through Supervisor:
[program:laravel-worker]
command=php /var/www/example.com/current/artisan queue:work redis --sleep=3 --tries=3 --timeout=90
directory=/var/www/example.com/current
user=www-data
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/log/laravel-worker.log
Use the correct application path, PHP binary, queue driver, user, and log location. After each deployment, restart workers so they load the new code:
php artisan queue:restart
Laravel uses a restart signal rather than killing active jobs immediately. Workers finish their current work, then restart and load the updated application.
Read the official Laravel queue documentation before selecting retry, timeout, and failed-job settings. Set the worker timeout longer than the longest expected job, but don’t allow a broken job to run forever.
Laravel’s scheduler needs one cron entry that runs every minute:
* * * * * cd /var/www/example.com/current && php artisan schedule:run --quiet
On shared hosting, use the full path to PHP and the full path to artisan. For example, the provider may require /usr/local/bin/php instead of php.
Public uploads need the storage symlink:
php artisan storage:link
This connects public/storage to storage/app/public. Keep private documents on a private disk and serve them through authorized application routes. Don’t place sensitive files inside public/.
Verify the deployment before sending traffic
Open the site in a private browser window. Test the home page, authentication, forms, file uploads, email, database writes, and any payment or webhook flow.
Check the application log:
tail -f storage/logs/laravel.log
Also check Nginx or Apache logs, PHP-FPM logs, queue worker logs, and the hosting provider’s error panel. A page that loads successfully can still hide failed jobs or rejected scheduled commands.
Confirm these production settings:
APP_DEBUG=false- The domain uses HTTPS.
- The web root points to
public/. storageandbootstrap/cacheare writable.- Database credentials work.
php artisan schedule:runexecutes successfully.- Queue workers process a test job.
public/storagepoints to the intended storage location.- No
.env, log, backup, or source files are publicly downloadable.
Use a health check that tests more than the HTTP status code. A 200 response doesn’t prove that the database, cache, queue, or third-party services work.
Create a rollback plan before the first release. Keep the previous trusted release available, record the deployed commit, and save migration details. If the new release fails, point the server back to the previous version, restore data only when required, and investigate the failure before retrying.
Don’t overwrite the last trusted result during an automated deployment. Record the person responsible for approving the release, the location of deployment logs, and the method used to identify the last known-good commit or release directory. This makes recovery a defined procedure instead of a guess.
Maintain the application after launch
Deployment doesn’t end when the home page loads. Monitor error rates, queue failures, scheduled commands, disk usage, database backups, and certificate expiration.
Use a deployment checklist that records:
- The Git commit or release identifier.
- PHP and Laravel versions.
- Migration status.
- Cache and asset build status.
- Queue worker restart status.
- Smoke-test results.
- Reviewer name and deployment time.
Automate repeatable checks with CI where possible. A pipeline can run tests, validate Composer files, build assets, and deploy only after the required checks pass. DeployHQ’s Laravel deployment guidance provides another reference for release pipelines and reduced-downtime deployments.
Keep secrets out of build logs and command history. Limit server access to the people who need it. Apply operating system and PHP updates on a schedule, but test version changes before applying them to the live application.
Conclusion
To deploy Laravel project code reliably, control four things first: the PHP environment, the secret configuration, the public/ web root, and the background services. Then run production dependencies, migrations, storage setup, and caches in a fixed order.
Shared hosting can work for smaller applications. A VPS or managed Laravel platform is better when the project needs workers, Redis, scheduled jobs, private files, or repeatable releases. Record the last trusted deployment and keep a tested rollback path, because a production fix is only useful when you can apply it safely.
