PHP & Laravel — Zero to Hero Episode 23: Deploying Laravel — Taking Your Application Live

What Are We Doing in This Post?

Your Laravel blog is complete. It has authentication, posts, relationships, validation, and a clean UI. But right now it only runs on your computer. Nobody else can see it.

Deployment is the process of taking your application from your local machine and putting it on a real server connected to the internet so anyone in the world can visit it.

This episode covers everything — choosing a hosting provider, setting up the server, uploading your code, configuring the production environment, running migrations, and making your application live.


What You Need Before Deploying

A domain name — something like myblog.com. You can buy one from Namecheap or GoDaddy for around Rs. 800 per year.

A hosting provider that supports PHP 8.2 and MySQL. We will use a VPS (Virtual Private Server) from providers like DigitalOcean, Hostinger, or Vultr. A basic VPS costs around Rs. 500 to Rs. 800 per month.

Alternatively, for beginners, shared hosting from Hostinger works fine and is cheaper — around Rs. 150 per month. We will cover both approaches.


Option A — Shared Hosting (Beginner Friendly)

Shared hosting is the easiest option. You get a control panel (cPanel), file manager, and MySQL databases already set up. No server configuration needed.

Hostinger, Namecheap, and Bluehost all offer shared hosting with PHP 8.2 support.

Step 1 — Prepare Your Application for Production

Before uploading anything, run these commands locally:


    composer install --optimize-autoloader --no-dev
    php artisan config:cache
    php artisan route:cache
    php artisan view:cache

--no-dev skips development-only packages — smaller vendor folder for production.

config:cache compiles all config files into a single cached file — faster config loading.

route:cache compiles all routes into a single cached file — faster route matching.

view:cache pre-compiles all Blade templates — no compilation overhead on first request.

Step 2 — Update Your .env for Production

Create a production .env file. Never upload your local .env — it has your local database credentials. The production server needs its own .env with production values:


    APP_NAME=MyBlog
    APP_ENV=production
    APP_KEY=base64:your-key-here
    APP_DEBUG=false
    APP_URL=https://yourdomain.com

    LOG_CHANNEL=stack
    LOG_LEVEL=error

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=your_production_database
    DB_USERNAME=your_db_user
    DB_PASSWORD=your_strong_password
    DB_COLLATION=utf8mb4_unicode_ci

    SESSION_DRIVER=file
    CACHE_STORE=file

APP_DEBUG=false is critical. In production, debug mode must always be off. When debug is on, error pages show your full application code, file paths, and environment variables to anyone who triggers an error. This is a severe security vulnerability.

APP_ENV=production tells Laravel to use production-optimized settings.

LOG_LEVEL=error means only actual errors get logged — not debug information, warnings, or notices that would fill up log files.

Step 3 — What to Upload

You do not upload everything. Here is exactly what goes to the server:

Upload these:

app/
bootstrap/
config/
database/
public/
resources/
routes/
storage/
composer.json
composer.lock
artisan
.env

Do not upload these:

vendor/          (run composer install on the server instead)
node_modules/    (not needed for production without a build step)
.git/            (version control history — not needed on server)

Step 4 — Upload via cPanel File Manager

In cPanel, go to File Manager. Navigate to public_html — this is the web root, what visitors see when they go to your domain.

Here is the key to Laravel on shared hosting — your entire Laravel project should NOT go inside public_html. Only the contents of Laravel's public/ folder go there.

Upload your entire Laravel project to one level above public_html — so the structure looks like:

home/
└── yourusername/
    ├── myapp/          (your Laravel project)
    │   ├── app/
    │   ├── bootstrap/
    │   ├── config/
    │   ├── database/
    │   ├── resources/
    │   ├── routes/
    │   ├── storage/
    │   ├── vendor/
    │   └── public/
    └── public_html/    (this is what visitors see)

Now you need to point public_html to Laravel's public/ folder. The cleanest way is to replace the contents of public_html with a single index.php file that bootstraps from your Laravel project:

Create a new index.php directly in public_html:


    <?php

    define('LARAVEL_START', microtime(true));

    require __DIR__ . '/../myapp/vendor/autoload.php';

    $app = require_once __DIR__ . '/../myapp/bootstrap/app.php';

    $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

    $response = $kernel->handle(
        $request = Illuminate\Http\Request::capture()
    )->send();

    $kernel->terminate($request, $response);

Also copy the .htaccess file from your Laravel public/ folder into public_html:


    <IfModule mod_rewrite.c>
        <IfModule mod_mod_negotiate.c>
            Options -MultiViews -Indexes
        </IfModule>

        RewriteEngine On
        RewriteCond %{HTTP:Authorization} .
        RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteRule ^ index.php [L]
    </IfModule>

Step 5 — Create Database in cPanel

In cPanel, go to MySQL Databases. Create a new database, a new user with a strong password, and assign the user to the database with all privileges. Note the database name, username, and password — these go into your production .env.

Step 6 — Run Composer on the Server

In cPanel, go to Terminal (most modern hosts have this). Navigate to your project and run:


    cd ~/myapp
    composer install --optimize-autoloader --no-dev

If Terminal is not available, use SSH — connect with an SSH client like PuTTY.

Step 7 — Run Migrations


    php artisan migrate --force

The --force flag is required in production. Laravel protects you from accidentally running migrations on a production database — this flag confirms you know what you are doing.

Step 8 — Fix Storage Permissions


    chmod -R 775 storage
    chmod -R 775 bootstrap/cache

These folders need write permissions — Laravel writes log files, cache files, and session files here. Without correct permissions, your application will throw 500 errors.

Also run:


    php artisan storage:link

This creates a symbolic link from public/storage to storage/app/public — necessary for any file uploads to be publicly accessible.


Option B — VPS Deployment (DigitalOcean / Hostinger VPS)

A VPS gives you a full Linux server with root access. More control, better performance, but requires more setup. This is how professional applications are deployed.

Step 1 — Create a VPS

Sign up on DigitalOcean (digitalocean.com) or Hostinger VPS. Create a new Droplet or VPS with Ubuntu 22.04 LTS. Choose the $6/month plan for a basic blog.

Once created, you get an IP address. Connect via SSH:


    ssh root@your-server-ip

Step 2 — Install Required Software

Run these commands on your server one by one:

Update the system:


    apt update && apt upgrade -y

Install PHP 8.2 with required extensions:


    apt install -y software-properties-common
    add-apt-repository ppa:ondrej/php -y
    apt update
    apt install -y php8.2 php8.2-fpm php8.2-mysql php8.2-xml php8.2-curl php8.2-zip php8.2-mbstring php8.2-bcmath php8.2-tokenizer

Install MySQL:


    apt install -y mysql-server
    mysql_secure_installation

Install Nginx:


    apt install -y nginx

Install Composer:


    curl -sS https://getcomposer.org/installer | php
    mv composer.phar /usr/local/bin/composer
    chmod +x /usr/local/bin/composer

Install Git:


    apt install -y git

Step 3 — Create a MySQL Database


    mysql -u root -p

Inside MySQL:


    CREATE DATABASE myblog_production CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'myblog_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
    GRANT ALL PRIVILEGES ON myblog_production.* TO 'myblog_user'@'localhost';
    FLUSH PRIVILEGES;
    EXIT;

Step 4 — Upload Your Code

The cleanest way is Git. Push your code to GitHub (without the .env and vendor folders — both are in .gitignore already) and then pull it on the server.

On the server:


    cd /var/www
    git clone https://github.com/yourusername/myapp.git
    cd myapp

If you are not using Git, use SCP to transfer files from your local machine:


    scp -r ./myapp root@your-server-ip:/var/www/myapp

Step 5 — Install Dependencies and Configure Environment


    cd /var/www/myapp
    composer install --optimize-autoloader --no-dev
    cp .env.example .env

Edit the .env file with your production values:


    nano .env

Update these values:


    APP_ENV=production
    APP_DEBUG=false
    APP_URL=https://yourdomain.com

    DB_DATABASE=myblog_production
    DB_USERNAME=myblog_user
    DB_PASSWORD=StrongPassword123!
    DB_COLLATION=utf8mb4_unicode_ci

Generate the application key:

    php artisan key:generate

Step 6 — Set Permissions


    chown -R www-data:www-data /var/www/myapp
    chmod -R 755 /var/www/myapp
    chmod -R 775 /var/www/myapp/storage
    chmod -R 775 /var/www/myapp/bootstrap/cache

Step 7 — Configure Nginx

Create an Nginx configuration file for your site:


    nano /etc/nginx/sites-available/myapp

Paste this configuration:


    server {
        listen 80;
        listen [::]:80;
        server_name yourdomain.com www.yourdomain.com;
        root /var/www/myapp/public;

        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-Content-Type-Options "nosniff";

        index index.php;

        charset utf-8;

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

        location = /favicon.ico { access_log off; log_not_found off; }
        location = /robots.txt  { access_log off; log_not_found off; }

        error_page 404 /index.php;

        location ~ \.php$ {
            fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            include fastcgi_params;
            fastcgi_hide_header X-Powered-By;
        }

        location ~ /\.(?!well-known).* {
            deny all;
        }
    }

Enable the site and restart Nginx:


    ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
    nginx -t
    systemctl restart nginx
    systemctl restart php8.2-fpm

Step 8 — Run Migrations and Cache


    php artisan migrate --force
    php artisan db:seed --force
    php artisan storage:link
    php artisan config:cache
    php artisan route:cache
    php artisan view:cache

Step 9 — Set Up SSL (HTTPS)

Install Certbot:


    apt install -y certbot python3-certbot-nginx
    certbot --nginx -d yourdomain.com -d www.yourdomain.com

Follow the prompts. Certbot automatically configures Nginx for HTTPS and sets up automatic certificate renewal. Your site is now accessible via https://yourdomain.com.


Handling Deployments After the First One

Every time you push new code, you need to update the server. Create a simple deployment script at /var/www/myapp/deploy.sh:

#!/bin/bash


    cd /var/www/myapp

    git pull origin main

    composer install --optimize-autoloader --no-dev

    php artisan migrate --force

    php artisan config:cache
    php artisan route:cache
    php artisan view:cache

    php artisan queue:restart

    chown -R www-data:www-data storage bootstrap/cache

    echo "Deployment complete."

Make it executable:


    chmod +x deploy.sh

Every future deployment is one command:


    ./deploy.sh


Important Production Checklist

Before going live, verify every item on this list:

APP_DEBUG=false in .env — never true in production.

APP_ENV=production in .env.

Strong APP_KEY generated with php artisan key:generate.

Strong database password — not root with empty password.

storage/ and bootstrap/cache/ are writable by the web server.

php artisan storage:link has been run.

All caches are warmed — config, route, view.

HTTPS is configured — never serve a production app over plain HTTP.

composer install --no-dev was used — no development packages in production.

The .env file is not publicly accessible — it lives outside public/ so it can never be downloaded by a browser.


Common Deployment Errors and Fixes

500 Internal Server Error — most common cause:

Check the Laravel log:


    tail -f /var/www/myapp/storage/logs/laravel.log

The actual error is always in this log file. The browser just shows 500 — the log shows the real problem.

Permission denied errors:


    chown -R www-data:www-data /var/www/myapp/storage
    chmod -R 775 /var/www/myapp/storage

Class not found after deployment:


    composer dump-autoload
    php artisan config:clear
    php artisan cache:clear

Migrations failing:

Check your .env database credentials. Connect to MySQL manually to verify they work:


    mysql -u myblog_user -p myblog_production

Nginx showing default page instead of your app:


    nginx -t
    systemctl restart nginx

Make sure the Nginx site is enabled in sites-enabled and the root points to your Laravel public/ directory.


What Did We Learn in This Post?

Shared hosting works for beginners — upload the project one level above public_html, point the web root to Laravel's public/ folder via a custom index.php.

VPS deployment gives you full control — install PHP 8.2, MySQL, Nginx, and Composer on Ubuntu, configure Nginx to serve from Laravel's public/ directory.

Production .env must have APP_DEBUG=false and APP_ENV=production. Never expose debug information in production.

composer install --no-dev, php artisan config:cache, route:cache, and view:cache optimize the application for production performance.

php artisan migrate --force runs migrations in production. The --force flag bypasses the production safety check.

storage/ and bootstrap/cache/ must be writable by the web server. Always run php artisan storage:link for file uploads.

SSL via Certbot is free, automatic, and mandatory for any real application.

Always check storage/logs/laravel.log first when debugging production errors.


What is Coming Next?

This completes the core PHP and Laravel — Zero to Hero course. You have gone from zero PHP knowledge to building and deploying a complete database-driven web application with authentication, relationships, validation, and a live URL.

The next series of posts will cover advanced Laravel topics — queues and jobs, sending emails, file uploads, building REST APIs, Laravel Sanctum for API authentication, and performance optimization.

The foundation is solid. The journey continues.


This is Episode 23 of the PHP and Laravel — Zero to Hero series. Thank you for following along from the very beginning.


No comments:

Post a Comment

PHP & Laravel — Zero to Hero Episode 23: Deploying Laravel — Taking Your Application Live

What Are We Doing in This Post? Your Laravel blog is complete. It has authentication, posts, relationships, validation, and a clean UI. But...