Step-by-step
-
1
Update the server and install dependencies
Start with a clean, fully-updated system. Then add the
ondrej/phpPPA to get PHP 8.2+ and install Apache, MariaDB, and all PHP extensions Laravel needs.bashapt update && apt upgrade -y add-apt-repository ppa:ondrej/php -y apt update apt install -y apache2 mariadb-server \ php8.2 php8.2-cli php8.2-fpm \ php8.2-mysql php8.2-mbstring php8.2-xml \ php8.2-curl php8.2-zip php8.2-bcmath \ php8.2-intl php8.2-gd php8.2-tokenizer \ libapache2-mod-php8.2 unzip git curl a2enmod rewrite php8.2 systemctl restart apache2 -
2
Install Composer
Composer is not in the Ubuntu package manager at a recent enough version. Download it directly from the official installer and move it to a global location.
bashcurl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer chmod +x /usr/local/bin/composer composer --version -
3
Create the MySQL database and user
Log into MariaDB and create a dedicated database and user for the application. Never use the root MySQL user in your
.env.sqlmysql -u root CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'myapp'@'localhost' IDENTIFIED BY 'StrongPassword123!'; GRANT ALL PRIVILEGES ON myapp.* TO 'myapp'@'localhost'; FLUSH PRIVILEGES; EXIT; -
4
Clone the repository
Put the project under
/var/www/. Clone your repo there, then set Apache as the owner so the web server can read and write to it. All future deployments will usegit pullfrom this directory — never copy files manually.bashcd /var/www git clone https://github.com/your-username/your-repo.git myapp.com chown -R www-data:www-data /var/www/myapp.com -
5
Configure the .env file
Copy
.env.exampleto.envand fill in your real values. The.envfile is never committed to Git — it lives only on the server. Generate a freshAPP_KEYafter copying.bashcd /var/www/myapp.com cp .env.example .env nano .env # Set APP_ENV=production, APP_DEBUG=false, APP_URL # Set DB_DATABASE, DB_USERNAME, DB_PASSWORD # Set MAIL_* if using email php artisan key:generate -
6
Install dependencies and run migrations
Install only production dependencies with the
--no-devflag and run migrations. Then fix ownership again after Composer writes to the vendor directory.bashcd /var/www/myapp.com composer install --no-dev --optimize-autoloader php artisan migrate --force php artisan db:seed --force # only if you have seeders to run in production chown -R www-data:www-data /var/www/myapp.com chmod -R 755 /var/www/myapp.com chmod -R 775 /var/www/myapp.com/storage chmod -R 775 /var/www/myapp.com/bootstrap/cache -
7
Create the Apache virtual host
Create a vhost config that points Apache's document root at the
public/directory. TheAllowOverride Alldirective lets Laravel's.htaccesshandle URL rewriting.bashnano /etc/apache2/sites-available/myapp.com.conf -
8
Write the virtual host configuration
Paste the following into the config file. Replace
myapp.comwith your actual domain. TheDocumentRootmust point topublic/, not the project root — this is the most common misconfiguration.nginx<VirtualHost *:80> ServerName myapp.com ServerAlias www.myapp.com DocumentRoot /var/www/myapp.com/public <Directory /var/www/myapp.com/public> AllowOverride All Require all granted Options -Indexes +FollowSymLinks </Directory> ErrorLog ${APACHE_LOG_DIR}/myapp-error.log CustomLog ${APACHE_LOG_DIR}/myapp-access.log combined </VirtualHost> -
9
Enable the site and cache Laravel config
Enable the vhost, disable the default site, reload Apache, then run Laravel's optimization commands to cache config, routes, and views. These make every request faster on production.
For subsequent deploys, the process is:
git pull→composer install --no-dev --optimize-autoloader(if composer.json changed) →php artisan migrate --force(if migrations were added) →php artisan optimize && php artisan view:cache.basha2ensite myapp.com.conf a2dissite 000-default.conf apache2ctl configtest # should say "Syntax OK" systemctl reload apache2 php artisan storage:link php artisan optimize php artisan view:cache
Tips & gotchas
- Set APP_DEBUG=false in production — a visible stack trace leaks your file paths and config values to anyone who triggers an error.
- Run `php artisan config:clear` before `php artisan optimize` whenever you change .env values, or the old cached values will still be used.
- Add a deploy script or alias so every deployment is a single command — prevents accidentally skipping steps like `chown` or `view:cache`.
- Install Certbot (`apt install certbot python3-certbot-apache`) to get a free Let's Encrypt SSL certificate; it auto-configures the Apache vhost.
- Create a non-root deploy user with sudo privileges for the www directory instead of running everything as root on a shared server.
Wrapping up
You now have a production Laravel deployment on Apache and MySQL with a clean Git-based update workflow. The next step is to add SSL via Certbot and set up a queue worker with Supervisor so queued jobs survive server restarts.