Programming Intermediate 14 min

How to Deploy a Laravel App to a VPS (Apache + MySQL)

Deploying Laravel to a VPS gives you full control over your server environment — PHP version, extensions, cron jobs, queue workers, everything. This guide walks through a production-ready setup on a fresh Ubuntu server using Apache and MySQL/MariaDB.

We'll go from a bare machine to a live Laravel app, then cover how to push updates cleanly using Git so deployments stay predictable and repeatable.

The same approach works for any Laravel 10/11/12 project. Commands assume Ubuntu 22.04 or 24.04; adjust package names for other distros if needed.

Step-by-step

  1. 1

    Update the server and install dependencies

    Start with a clean, fully-updated system. Then add the ondrej/php PPA to get PHP 8.2+ and install Apache, MariaDB, and all PHP extensions Laravel needs.

    bash
    apt 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. 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.

    bash
    curl -sS https://getcomposer.org/installer | php
    mv composer.phar /usr/local/bin/composer
    chmod +x /usr/local/bin/composer
    composer --version
  3. 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.

    sql
    mysql -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. 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 use git pull from this directory — never copy files manually.

    bash
    cd /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. 5

    Configure the .env file

    Copy .env.example to .env and fill in your real values. The .env file is never committed to Git — it lives only on the server. Generate a fresh APP_KEY after copying.

    bash
    cd /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. 6

    Install dependencies and run migrations

    Install only production dependencies with the --no-dev flag and run migrations. Then fix ownership again after Composer writes to the vendor directory.

    bash
    cd /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. 7

    Create the Apache virtual host

    Create a vhost config that points Apache's document root at the public/ directory. The AllowOverride All directive lets Laravel's .htaccess handle URL rewriting.

    bash
    nano /etc/apache2/sites-available/myapp.com.conf
  8. 8

    Write the virtual host configuration

    Paste the following into the config file. Replace myapp.com with your actual domain. The DocumentRoot must point to public/, 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. 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 pullcomposer install --no-dev --optimize-autoloader (if composer.json changed) → php artisan migrate --force (if migrations were added) → php artisan optimize && php artisan view:cache.

    bash
    a2ensite 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.

#Laravel #Deployment #VPS #Linux
Back to all guides

Need Help With Your Project?

Book a free 30-minute consultation to discuss your technical challenges and explore solutions together.