Why must a Laravel 13 app's web server document root point at public/, and what leaks if it points at the project root instead?
answer
- one front controller
- public/index.php and .htaccess
- the .env file one level up
- storage/logs/laravel.log
- never serve from a subdirectory
basics
~20 spublic/ holds only the front controller index.php and assets, so every request goes through Laravel. Serving the project root lets browsers request files like .env with credentials and APP_KEY, storage/logs/laravel.log, composer.json and vendor code directly.
solid answer
~40 sLaravel is built around one **front controller**: `public/index.php` loads the autoloader, requires `bootstrap/app.php` and hands the request to the application, and `public/.htaccess` (or the equivalent server rule) rewrites every non-file URL to it. Everything sensitive sits **one level above** `public/`: the `.env` file with the database password and `APP_KEY`, `storage/logs/laravel.log`, `config/`, `composer.json`, `composer.lock` and `vendor/`. With the document root at the project folder, a server that serves static files can hand those out directly, for example a request for `/.env`. The docs are explicit: serve Laravel from the root of the configured web directory, never from a subdirectory of it. Uploaded files meant to be public reach `public/` through a symbolic link, not by moving the root.
code
nginx · 17 linesserver {
listen 80;
server_name timetable.example;
root /srv/timetable/public; # not /srv/timetable
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}go deeper
Remember that public/ is the web root and index.php the single entry point, and that .env must never be reachable from a browser.
Explain how index.php reaches the rest of the app one level up, what .htaccess does, and which files leak when the root is wrong.
Audit deployments for the right document root, including shared-hosting setups, and expose uploads only through the public storage link.
Make the document root part of the platform's hardening baseline and check it automatically for every deployed app.
## The front-controller layout Every HTTP request to a Laravel app enters through **`public/index.php`**. In Laravel 13 that file: 1. defines `LARAVEL_START` for timing; 2. includes `storage/framework/maintenance.php` if the app is in maintenance mode; 3. requires `vendor/autoload.php`; 4. requires `bootstrap/app.php` to get the application; 5. calls `$app->handleRequest(Request::capture())`. Everything else the app needs is reached with paths like `__DIR__.'/../vendor/autoload.php'`, that is, **one directory above `public/`**. The layout is deliberate: the web server only needs to see `public/`, and PHP reads the rest from disk. ## What `public/` contains - `index.php`, the front controller; - `.htaccess`, the Apache rewrite rules: existing files and folders are served directly, every other URL is rewritten to `index.php`, and `Authorization` and `X-XSRF-Token` headers are passed through; - `favicon.ico` and `robots.txt`; - `build/` after the front-end build; - optionally `storage`, a symbolic link to `storage/app/public` for public uploads. ## What leaks when the root is wrong Point the document root at the project folder of a **school-timetable app** and the server's static-file handling now covers the whole project: | Request | What it exposes | |---|---| | `/.env` | `DB_PASSWORD`, mail and API credentials, `APP_KEY` | | `/storage/logs/laravel.log` | Stack traces, SQL, sometimes personal data of pupils and staff | | `/composer.json`, `/composer.lock` | Exact package versions for targeting known vulnerabilities | | `/config/database.php` | Usually executed rather than shown, but it depends on server config | | `/vendor/...` | Third-party PHP files that were never meant to be entry points | Whether a given file is served as text or executed depends on the server's rules, which is exactly the point: with the right root you do not depend on those rules at all. ## Getting it right - Set the web server's document root (for example Nginx `root` or an Apache virtual host's `DocumentRoot`) to the app's `public/` directory. - The docs warn against serving the app from a **subdirectory** of a web root, for example `/var/www/html/timetable/public` reached as `https://school.example/timetable/public`: everything above `public/` would still be inside the web root. - On shared hosting that only offers a fixed web directory, put the Laravel project outside it and make that web directory hold (or link to) the contents of `public/`. - Local tools already do this: `php artisan serve` runs PHP's built-in server with `public/` as its working directory, and Herd and Valet serve the `public/` folder of each site. ## Checking a live server A quick audit of an existing deployment: 1. Request `https://timetable.example/.env` and `https://timetable.example/storage/logs/laravel.log`; both must return 404 (from Laravel) rather than file contents. 2. Read the server configuration and confirm the root ends in `/public`. 3. Confirm `public/` contains only the expected files: `index.php`, `.htaccess`, `favicon.ico`, `robots.txt`, `build/` and the `storage` link. 4. Check that no copy of `.env` or a backup such as `.env.backup` was ever placed inside `public/`. If the first request returns the file, rotate every credential in it, including `APP_KEY`, after fixing the root: anything served once must be treated as leaked. ## Public files the right way User uploads that must be downloadable live in `storage/app/public`, and a symbolic link `public/storage` makes them reachable. The link exposes that one folder, not the rest of `storage/`. ## Common misreadings - "The `.htaccess` file protects `.env`": it rewrites requests inside `public/`; it cannot protect files above the document root, and Nginx ignores it entirely. - "Only `.env` matters": logs and lock files leak useful information too. - "Moving `index.php` to the project root is equivalent": it puts the whole project inside the web root.
- How do uploaded files become downloadable if storage/ stays outside the web root?Files meant to be public are stored under `storage/app/public`, and a symbolic link at `public/storage` points to that folder, so only that one directory becomes reachable. The rest of `storage/`, including `app/private`, `framework/` and `logs/`, stays outside the document root.
- Why does the public/.htaccess file not make serving the project root safe?Apache applies an `.htaccess` file to the directory it sits in and below, so `public/.htaccess` governs requests inside `public/` only. With the root at the project folder, `.env` and `storage/` sit outside its reach, and servers such as Nginx ignore `.htaccess` files completely.
public/ is the reception desk of a school office: visitors speak to the receptionist, who fetches what they are allowed to see. Pointing the web root at the project folder is like removing the desk and letting visitors walk into the back office, where the filing cabinets with keys and staff records stand open.
saying these in an interview costs you the question
- The .htaccess file in public/ blocks access to .env anyway
- Serving from /var/www/html/app/public as a subfolder is fine
- Only index.php needs to be public; config files are harmless
- Moving index.php to the project root is an equivalent setup
- Uploads should be stored directly in public/ to be downloadable