Deploy Laravel Project on an Apache Server (UPDATED with subfolder compatibility)

Shuqi Khor
3 min readOct 20, 2023

--

This tutorial assumes that you already know how to upload files and copy database to your production server. I’m using Laravel 10.

You know php artisan serve command very well, but your production server is a shared-hosting so it’s impossible to run this command.

“How should I deploy?!” you asked, as you have spent countless hours on your project and had it running perfectly on 127.0.0.1:8000.

Perhaps you have no idea because you’re a cat.

Fret not! Here I will introduce 2 methods to deploy your project, without the need to run php artisan serve and works well in a subfolder.

Ultimate Method: Move out .htaccess and index.php

Pros: Relatively little changes on folder structure.
Cons: None so far.

First, upload everything as usual. You could give your project its own folder if you want.

Remember to edit /.env to update the URL and its environment setting, and turn off debugging:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com/subfolder
# You need to add this line manually
ASSET_URL=https://example.com/subfolder/public

Then, move .htaccess and index.php from /public to the project root.

Now, edit your index.php and erase all the “../”, like this:

// from
require __DIR__.'/../vendor/autoload.php';
// to
require __DIR__.'/vendor/autoload.php';

That’s it!

Method #2 (imperfect): Upload as usual but add a .htaccess at root

Pros: You could keep your existing folder structure and don’t need to edit your existing codes.
Cons: URL::current() is weird if it’s in a subfolder

First, upload everything to /public_html as usual. You could ignore /artisan executable file and the .git folder (if you have one).

Then, create a new .htaccess file at the project root with these contents:

RewriteEngine on

RewriteCond %{REQUEST_URI} !^/public/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ public/$1
RewriteRule ^(/)?$ public/index.php [L]

Credit: This was modified from Wordpress documentation.

Now, remember to update the base URL at .env and /config/app.php:

# .env
APP_URL=https://example.com/subfolder
// config/app.php
return [
...
'url' => env('APP_URL', 'https://example.com/subfolder'),

If your project is in a sub-folder, head to /app/Providers/AppServiceProvider.php and add this line in the boot method:

// app/Providers/AppServiceProvider.php

class AppServiceProvider extends ServiceProvider
{
...
public function boot(): void
{
// add this line
\URL::forceRootUrl(\Config::get('app.url'));
}
}

This will make sure all the URLs generated using url() and URL::to() are prefixed with the correct base URL.

Next (also only applicable if your project is in a subfolder), head to /app/Providers/RouteServiceProvider.php and add your subfolder prefix:

class RouteServiceProvider extends ServiceProvider
{
...
public function boot(): void
{
...
$this->routes(function () {
...
Route::middleware('web')
->prefix('subfolder') // <---- this is the added line
->group(base_path('routes/web.php'));
});

This will make sure the route captures the correct path since your project is not served at root. Otherwise most if not everything will be 404.

Now the drawback is that, since we have forced the base URL to include the subfolder, and forced a prefix in routing at the same time, your subfolder will repeat once when you try and get the current URL:

URL::current();
// returns https://example.com/subfolder/subfolder

url()->current();
// returns https://example.com/subfolder/subfolder

That’s it. This method could even work in your local apache setup without the need to run “php artisan serve”.

Well, that’s it. Please let me know if you found a better method.

Have a nice day!

--

--