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:
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