You push your code, the deployment runs, and something breaks. Maybe it is a white screen. Maybe it is a 500 error. Maybe the deployment itself fails halfway through. Whatever the symptom, the cause is almost always one of the same handful of issues.
After managing thousands of Laravel deployments through Deploynix, we have compiled the ten errors that account for the vast majority of deployment failures. Each one includes the error message you will see, why it happens, and how to fix it immediately.
1. Permission Denied on Storage and Cache Directories
The error:
file_put_contents(/var/www/app/storage/logs/laravel.log): Failed to open stream: Permission denied
Or variations targeting storage/framework/cache, storage/framework/sessions, or storage/framework/views.
Why it happens:
The web server user (typically www-data on Ubuntu) does not have write permissions to the storage directory or the bootstrap/cache directory. This commonly occurs when files are deployed with a different user's ownership, or when the deployment process creates new directories without inheriting the correct permissions.
The fix:
Set the correct ownership and permissions on your server:
chown -R deploynix:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
In Deploynix, the server provisioning process sets these permissions correctly. If they get disrupted (usually by running artisan commands as root or by a misconfigured deployment hook), you can fix them through the Deploynix web terminal without SSH.
Prevention:
Never run deployment commands as root. Deploynix runs all deployment hooks as the deploynix user, which is configured with the correct group membership. If you SSH into the server manually, always use the deploynix user.
2. The Storage Symlink Is Missing
The error:
Uploaded files or assets return 404 errors. Images that worked locally are broken in production.
Why it happens:
Laravel stores user uploads in storage/app/public but serves them from public/storage. The php artisan storage:link command creates a symbolic link between these directories. If this command was never run in production, or if a deployment created a new release directory without re-creating the symlink, files appear missing.
The fix:
Run the storage link command:
php artisan storage:link
In Deploynix, add this to your deployment hooks if it is not already there. Deploynix's zero-downtime deployments use a symlinked release structure, and the storage directory is shared across releases, so the symlink typically persists. But if you have recently provisioned a new server or changed your deployment configuration, re-running this command ensures the link exists.
Prevention:
Include php artisan storage:link as an early step in your deployment hooks. It is idempotent — running it when the link already exists does nothing harmful.
3. APP_KEY Not Set or Mismatched
The error:
No application encryption key has been specified.
Or more insidiously: encrypted data (sessions, cookies, encrypted model attributes) suddenly cannot be decrypted, causing 500 errors or silent authentication failures.
Why it happens:
The APP_KEY environment variable is missing, empty, or different from the key used to encrypt existing data. This often happens when copying .env files between environments and forgetting to set the production key, or when regenerating the key on a server that already has encrypted data in the database.
The fix:
If you have never set an APP_KEY for this environment:
php artisan key:generate
If your application has existing encrypted data, you must use the same key that encrypted that data. Check your backup .env file or your Deploynix environment configuration to find the original key.
Prevention:
Never run php artisan key:generate on a production server that has existing encrypted data. Set the APP_KEY once through Deploynix's environment variable editor and leave it. If you need to rotate keys, Laravel 11+ provides php artisan key:rotate which re-encrypts existing data.
4. Composer Memory Exhaustion
The error:
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted
This appears during composer install or composer update in a deployment hook.
Why it happens:
Composer requires significant memory for dependency resolution, especially for large projects with many packages. The default PHP memory limit (128 MB) is often insufficient.
The fix:
Increase the memory limit for the Composer command:
COMPOSER_MEMORY_LIMIT=-1 composer install --no-dev --optimize-autoloader
The -1 removes the memory limit entirely for the Composer process. This is safe because Composer is a CLI tool that runs briefly during deployment, not a long-running process.
Prevention:
Always use the COMPOSER_MEMORY_LIMIT=-1 prefix in your Deploynix deployment hooks for Composer commands. On servers with 1 GB RAM, also add a swap file (Deploynix creates a swap file during provisioning by default) to prevent out-of-memory kills.
5. NPM Build Failures
The error:
npm ERR! code ELIFECYCLE
npm ERR! errno 137
Or:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
Why it happens:
Building frontend assets with Vite or Webpack requires memory. Error code 137 specifically means the process was killed by the OOM (Out of Memory) killer. This is especially common on servers with 1-2 GB RAM.
The fix:
Increase Node's memory allocation:
NODE_OPTIONS="--max-old-space-size=1024" npm run build
If you are on a very constrained server, consider building assets locally or in CI/CD and committing the built files, avoiding the need to run npm run build on the server entirely.
Prevention:
For servers with limited RAM, the most reliable approach is to build assets in your CI/CD pipeline (GitHub Actions, GitLab CI) and deploy the pre-built files. This offloads the memory-intensive build process to a machine with adequate resources.
Alternatively, use npm ci instead of npm install in deployment hooks. npm ci is faster and uses less memory because it installs directly from package-lock.json without resolving dependencies.
6. Migration Failures and Timeouts
The error:
SQLSTATE[HY000] [2002] Connection timed out
Or:
SQLSTATE[42S01]: Base table or view already exists
Or the migration appears to hang indefinitely.
Why it happens:
Several causes: the database server is unreachable (wrong credentials, firewall issue), the migration is trying to create a table that already exists (migration was partially applied), or a migration on a large table locks it for too long, causing a timeout.
The fix:
For connection issues, verify your database credentials in the Deploynix environment editor. Ensure the database server's firewall allows connections from your app server.
For partially applied migrations, check the migrations table to see which migrations have been recorded, then either manually complete the partial migration or roll it back.
For large-table migrations, consider running them outside the deployment process:
php artisan migrate --force --isolated
The --isolated flag ensures only one migration process runs at a time, preventing conflicts during zero-downtime deployments where old and new releases briefly coexist.
Prevention:
Always use --force in production deployment hooks (it bypasses the confirmation prompt). For large tables, write migrations that use ALTER TABLE operations that MySQL can perform online, avoiding table locks.
7. Vite Manifest Not Found
The error:
Vite manifest not found at: /var/www/app/public/build/manifest.json
Why it happens:
Your Blade templates reference @vite(['resources/css/app.css', 'resources/js/app.js']), but the built assets do not exist on the server. Either npm run build was not run during deployment, it failed silently, or the public/build directory was not included in the deployed files.
The fix:
Run npm run build on the server:
npm ci && npm run build
Or, if you build assets locally, ensure public/build is not in your .gitignore.
Prevention:
Add npm ci && npm run build to your Deploynix deployment hooks, after composer install and before the application goes live. Verify that your .gitignore does not exclude public/build if you commit built assets.
8. Queue Workers Running Old Code
The error:
No explicit error message. Instead, your application behaves inconsistently: new features work in the browser but queue jobs use old logic. Or jobs fail with "class not found" errors for classes you renamed or moved.
Why it happens:
PHP queue workers are long-running processes. They boot your application once and keep it in memory. When you deploy new code, the running workers still have the old code loaded. They will not pick up changes until they are restarted.
The fix:
Restart queue workers after deployment:
php artisan queue:restart
This sends a signal to all workers to finish their current job and then restart, picking up the new code.
Prevention:
Deploynix automatically restarts daemons (including queue workers) during deployment when you have configured them through the dashboard. If you manage queue workers through deployment hooks instead, add php artisan queue:restart as a post-deployment step.
9. Environment Variables Not Loading
The error:
The application uses default values instead of production values. Database connections fail because credentials are empty. Third-party API calls fail with authentication errors.
Why it happens:
The .env file is missing, empty, or cached with old values. Laravel caches environment variables when you run php artisan config:cache. After caching, changes to .env have no effect until you re-cache.
The fix:
Clear and re-cache the configuration:
php artisan config:clear
php artisan config:cache
If the .env file itself is missing, restore it from your Deploynix environment editor (which stores your environment variables independently of the deployed files).
Prevention:
Always include php artisan config:cache in your post-deployment hooks. This ensures every deployment reads the current .env and caches it. Deploynix's deployment process writes the .env file before running your hooks, so the values are always current.
A critical related point: never use env() directly in your application code. Always use config() instead. When configuration is cached, env() returns null for all values not in the cached config, which is a common source of production bugs.
10. Disk Space Exhaustion
The error:
ErrorException: file_put_contents(): Only 0 of 1234 bytes written, possibly out of free disk space
Or database errors about insufficient space for temporary tables, or failed log writes.
Why it happens:
Old deployment releases accumulate. Log files grow without rotation. Database dumps from manual backups pile up. Docker images and system packages leave cached files. On a 25-40 GB SSD, this sneaks up on you.
The fix:
Identify what is consuming space:
du -sh /home/deploynix/*/releases/* | sort -rh | head -20
Clean old releases. Deploynix automatically removes old releases during deployment, keeping only the configured number of recent releases. If manual cleanup is needed, remove the oldest release directories.
Rotate and compress logs:
find /home/deploynix/*/storage/logs -name "*.log" -mtime +7 -delete
Prevention:
Configure Deploynix's health alerts to notify you when disk usage exceeds 80%. Set up log rotation for your Laravel logs (Deploynix configures system-level log rotation during provisioning). Limit the number of retained deployment releases.
A Systematic Approach to Deployment Debugging
When a deployment fails or produces unexpected results, follow this checklist:
- Check the deployment log in Deploynix — it shows the exact command that failed and its output
- Verify environment variables — compare what is set in Deploynix with what your application expects
- Check disk space and memory — resource exhaustion causes cryptic errors
- Review recent changes — the bug is almost always in the code you just deployed
- Test the rollback — Deploynix supports instant rollback to the previous release, which confirms whether the issue is in new code or infrastructure
Conclusion
These ten errors are not exotic edge cases. They are the everyday reality of deploying Laravel applications. The good news is that every one of them has a clear, permanent fix.
Deploynix addresses most of these proactively: correct file permissions during provisioning, daemon restart on deployment, environment variable management, disk monitoring, and zero-downtime deployments that make rollbacks instant. But knowing what can go wrong and why puts you in control when the unexpected happens.
Bookmark this page. The next time a deployment fails, start here.
Top comments (0)