How to Deploy a Laravel Project to Production (step-by-step)

Deploy & DevOps · By · Published on · Updated on · 8 min read

Patch panel with blue Ethernet cables connected to rack ports
Network connections in a rack, evoking server infrastructure. Photo: Brett Sayles / Pexels

This tutorial walks you through deploying a Laravel project to production with Nginx, PHP‑FPM, a secure .env, migrations, queues, and the scheduler. By the end you'll have a configured server, your app running, and a verification checklist. If you later want to automate the pipeline, look into CI/CD. The focus here is a classic, straightforward Linux deployment.

Prerequisites and process overview

We’ll prepare the server, install dependencies, configure the app with .env, optimize Laravel, enable Nginx/PHP‑FPM, run migrations, start queues (Supervisor) and the scheduler (cron). Some steps require sudo/root.

If you plan to automate the pipeline later, see CI/CD with GitHub Actions for PHP/Laravel (guide reference).

  • Linux server (Ubuntu/Debian) with SSH access and sudo/root.
  • Domain pointed to the server (DNS A/AAAA).
  • Git enabled and project repository accessible.
  • PHP‑FPM, Nginx and a database (MySQL/MariaDB or PostgreSQL).
  • Composer installed; Node.js LTS if you use Vite.
  • Environment variables and secrets defined for production.

1) Prepare the server: essential packages

Update the system and install Nginx, PHP‑FPM with common extensions, Composer, Git and Node.js (if you’ll build assets with Vite). Run these commands as root or with sudo.

After installation, confirm Nginx and PHP‑FPM are running. In many environments PHP‑FPM creates a socket under /run/php/. We’ll adjust that in the Nginx config later.

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx php-fpm php-cli php-zip php-mbstring php-xml php-curl php-redis php-mysql php-pgsql git unzip curl
sudo apt install -y composer
# Opcional: Node.js LTS para build com Vite
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs

2) Project layout, deploy user and permissions

Create a deploy user and the application directory under /var/www/app. The webserver group (www-data or equivalent) usually runs Nginx/PHP‑FPM. Ensure the webserver has read access and that storage/ and bootstrap/cache are writable.

Avoid 777 permissions. Use 775 for directories and 664 for files, with the application owner as the deploy user and the webserver group.

sudo adduser --disabled-password --gecos "" deploy
sudo usermod -aG www-data deploy
sudo mkdir -p /var/www/app
sudo chown -R deploy:www-data /var/www/app
sudo chmod -R 775 /var/www/app

3) Code and Laravel configuration (.env, migrations and optimizations)

Clone the repository, install dependencies and generate the APP_KEY. Configure .env for production (APP_ENV=production, APP_DEBUG=false, DB_*, CACHE_DRIVER, SESSION_DRIVER, QUEUE_CONNECTION, LOG_CHANNEL, etc.). Never commit .env.

Apply migrations with --force to run them in production. For good schema versioning practices, refer to the Guide: agile database versioning best practices.

Finally, create the storage symlink and cache config/routes/views. If you change environment variables, run php artisan optimize:clear before re-caching.

sudo -u deploy -H bash -lc '
cd /var/www/app
# Substitua <SEU_REPO_GIT> pela URL do seu repositório (SSH/HTTPS)
git clone <SEU_REPO_GIT> .
composer install --prefer-dist --no-dev --optimize-autoloader
cp .env.example .env
php artisan key:generate
php artisan storage:link
# Ajuste o .env com credenciais reais antes de migrar
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
'
# Garantir permissões de escrita no runtime
sudo chown -R deploy:www-data /var/www/app
sudo find /var/www/app/storage /var/www/app/bootstrap/cache -type d -exec chmod 775 {} \;
sudo find /var/www/app/storage /var/www/app/bootstrap/cache -type f -exec chmod 664 {} \;

4) Frontend build (Vite) – optional

If the project uses Vite, build the assets for production. Without the build, CSS/JS might not load correctly.

After the build, Laravel will use the generated manifest to serve static files. If you change Vite settings, run the build again.

cd /var/www/app
npm ci
npm run build

5) Nginx + PHP-FPM: production configuration

Create a server block that points to public/. Use try_files to route requests to index.php. Adjust the PHP‑FPM socket according to your installed PHP version. Enable site-specific logs to help with diagnostics and observability.

For better production logging formats, refer to the Structured logs (JSON) and correlation guide.

sudo tee /etc/nginx/sites-available/laravel.conf > /dev/null <<'NGINX'
server {
    listen 80;
    server_name exemplo.com www.exemplo.com;

    root /var/www/app/public;
    index index.php;

    access_log /var/log/nginx/laravel_access.log;
    error_log  /var/log/nginx/laravel_error.log;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock; # ajuste para sua versão, ex.: /run/php/php8.2-fpm.sock
        fastcgi_param PHP_VALUE "opcache.validate_timestamps=0";
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2?)$ {
        expires 7d;
        access_log off;
        try_files $uri $uri/ @laravel;
    }

    location @laravel {
        rewrite ^/(.*)$ /index.php?$query_string last;
    }

    client_max_body_size 10M;
}
NGINX
sudo ln -s /etc/nginx/sites-available/laravel.conf /etc/nginx/sites-enabled/laravel.conf
sudo nginx -t && sudo systemctl reload nginx

6) Queues with Supervisor (resilient queue:work)

Use Supervisor to keep queue workers running. Define the user, working directory and stability parameters (tries, timeout, max-time). After configuring, reload Supervisor and check the worker status.

For PHP observability strategies (APM, traces), see the PHP observability comparison (New Relic, Elastic, OpenTelemetry).

sudo apt install -y supervisor
sudo tee /etc/supervisor/conf.d/laravel-worker.conf > /dev/null <<'SUP'
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work --sleep=3 --tries=3 --max-time=3600 --timeout=120
autostart=true
autorestart=true
user=deploy
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-worker.log
stopwaitsecs=600
SUP
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status

7) Scheduler (cron) and final deploy checks

The Laravel scheduler should run every minute via cron. Add the job to the crontab of the user that runs the app. Then validate Nginx, make an HTTP request and inspect logs/queues.

If you want deeper guidance, read the official Laravel documentation on deployment, queues and scheduling:

- Deployment: https://laravel.com/docs/deployment

- Queues: https://laravel.com/docs/queues

- Scheduling: https://laravel.com/docs/scheduling

Common troubleshooting:

- 502 Bad Gateway: incorrect PHP‑FPM socket/port; adjust fastcgi_pass and check the php-fpm service.

- 500 in production: check APP_KEY in .env and permissions for storage/ and bootstrap/cache.

- Broken CSS/JS: run npm run build and verify asset URLs.

- Changed .env and nothing changed? Run php artisan optimize:clear and re-cache.

# Agendador do Laravel (executa a cada minuto)
( crontab -l 2>/dev/null; echo "* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1" ) | crontab -u deploy -

# Verificações rápidas
systemctl status nginx --no-pager
curl -I http://exemplo.com
tail -n 50 /var/log/nginx/laravel_error.log
php /var/www/app/artisan queue:failed

Frequently asked questions

Is zero-downtime deploy possible with this flow?

Yes. Use a release-based strategy with versioned release directories and an atomic symlink swap (for example releases/current) so the live site only switches after build and migrations complete. Add health checks before switching and keep a ready previous release for fast rollback. Automating the steps in CI/CD reduces human error and helps coordinate migrations, asset builds and service restarts.

How do I roll back if something goes wrong after deploy?

Rollback by switching back to the previous release or reverting to the previous commit/tag. If the deploy ran migrations, consider php artisan migrate:rollback only when safe for your data; database backups improve safety. Clear and re-cache config/routes/views, reload Nginx and restart queue workers/Supervisor. Using versioned releases and backups makes rollbacks predictable and minimizes downtime.

Where do I configure production logs in Laravel?

Configure logging in .env using LOG_CHANNEL and LOG_LEVEL to select channels like daily, stack or stderr. Keep separate Nginx access and error logs per site for HTTP diagnostics. Standardizing on structured formats (for example JSON) and adding correlation IDs improves observability and troubleshooting. Ensure log files are writable by the webserver or a log rotation system manages retention and permissions.

Do I need Node.js in production for a Laravel app using Vite?

Node.js is required only to compile frontend assets (npm run build) so the production server itself does not need Node if you deploy built assets. In CI/CD pipelines, the build runs during the pipeline and the server receives the compiled files and manifest. If you build on the server instead, install Node LTS and run the production build before switching releases.

How should I handle secrets (database passwords, keys) in .env?

Do not commit .env to version control. Keep production secrets as environment variables with restrictive file permissions so only the deploy user and necessary services can read them. For larger infrastructures, prefer secret managers provided by your cloud or orchestration platform. Regularly audit who has server and repository access and rotate secrets as part of your security practices.

Want to evolve your deploy to CI/CD?

This step-by-step gets your Laravel app live and secure. If you need to review the process, standardize environments and automate releases, let’s talk.

Talk about deployment pipeline

See also