Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 Remove public from URL: Point the Web Root to the public Folder

๐Ÿ› ๏ธ Laravel Tutorials5 min read

Laravel 13 Remove public from URL: Point the Web Root to the public Folder

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 12, 2026
Laravel 13 Remove public from URL: Point the Web Root to the public Folder

To remove public from a Laravel 13 URL, configure your web server so its document root is the project's public directory, not the project root. With Nginx that means root /var/www/app/public;, with Apache DocumentRoot /var/www/app/public. Then example.com/ serves the app directly and files like .env stay outside the web root.

If you see /public in your URLs, the server is pointing one level too high. The fix belongs in the server configuration, not in Laravel's code.

Why is the public folder the web root?

Laravel keeps only the front controller (index.php), compiled assets and the storage symlink in public. Everything else, including .env, config, vendor and storage, sits above it. When the document root is public, those files cannot be requested over HTTP. When the document root is the project root, a request like example.com/.env may return your database password and APP_KEY unless something else blocks it. That is why the common "move index.php to the root" trick is a security risk.

Prerequisites

  • Laravel 13 (PHP 8.3 or newer) deployed to a server or VPS.
  • Access to the Nginx or Apache site configuration, or a hosting panel that lets you change the document root.

Step 1: Nginx configuration

A minimal server block for a Laravel app with PHP-FPM:

Shell
server {
    listen 80;
    server_name example.com;
    root /var/www/example/public;

    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 ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.3-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;
    }
}

The key line is root ... /public;. The try_files directive sends every request that is not a real file to index.php, which is how Laravel's routing works. Adjust the PHP-FPM socket to match the PHP version you run. Test and reload with sudo nginx -t && sudo systemctl reload nginx.

Step 2: Apache configuration

Shell
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example/public

    <Directory /var/www/example/public>
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/example-error.log
    CustomLog ${APACHE_LOG_DIR}/example-access.log combined
</VirtualHost>

Laravel ships a public/.htaccess that rewrites requests to index.php. It only works when mod_rewrite is enabled (sudo a2enmod rewrite) and AllowOverride All is set for the public directory. Reload Apache after the change.

Step 3: Update APP_URL

Set the URL without /public in .env, then rebuild the config cache:

Code
APP_URL=https://example.com
Code
php artisan config:cache
php artisan route:cache

APP_URL is used by the url() helper in console commands, queued mail and notifications, so a wrong value there produces broken links in emails even when the site itself works.

How do you do this on shared hosting?

Most hosting panels let you set the document root for a domain or subdomain. Point it at /home/user/example/public. If your host forces public_html as the web root and you cannot change it, upload the Laravel project to a folder outside public_html and put only the contents of public inside public_html. Then edit the paths in public_html/index.php so they point to the project folder:

JavaScript
<?php

use Illuminate\Foundation\Application;
use Illuminate\Http\Request;

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

if (file_exists($maintenance = __DIR__.'/../example/storage/framework/maintenance.php')) {
    require $maintenance;
}

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

/** @var Application $app */
$app = require_once __DIR__.'/../example/bootstrap/app.php';

$app->handleRequest(Request::capture());

Compare this with the index.php in your own Laravel 13 project and change only the paths. The application code still lives outside the public folder, which keeps .env private.

What about a root .htaccess that rewrites to public?

A rule in the project root that rewrites everything into public/ hides the folder from URLs, but the project root is still the document root. One mistake in the rules can expose files. Treat it as a last resort and explicitly deny dotfiles if you use it.

Common mistakes

  • Moving index.php and .htaccess into the project root. It appears to work but can expose .env, composer.json and logs.
  • Forgetting storage:link after changing paths. Uploaded files return 404 until public/storage exists in the served public folder.
  • Old cached config. After editing APP_URL, run php artisan config:cache again.
  • Asset URLs still containing /public. Use asset() and @vite rather than hard-coded paths.

How do you verify it works?

  1. Open https://example.com/; the home page should load without /public.
  2. Open https://example.com/.env and https://example.com/composer.json; both must return 404 or 403, never the file.
  3. Open any nested route to confirm rewriting works.
  4. Send a test email or notification and check that links use the correct domain.

What about Docker and containers?

The same rule applies inside containers. In an Nginx container, mount the project at a path like /var/www/html and set root /var/www/html/public; in the site config. In the official PHP Apache images, change the document root with an environment variable and a sed line in your Dockerfile, as the image documentation shows, instead of copying files into a different folder. Either way, the container serves public and the rest of the project stays private.

FAQ

Can I remove public with a Laravel config option?

No. The public path is where the web server starts. It is a server setting.

Does this apply to Laravel Herd, Valet or Sail?

Those tools already serve the public folder for you, which is why local URLs never show it.

What if I host several apps on one server?

Create one server block or virtual host per app, each with its own public directory as the root.

References

Article Index & Topic Tags
LaravelNginxApacheDeployment

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.