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:
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
<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:
APP_URL=https://example.comphp artisan config:cache
php artisan route:cacheAPP_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:
<?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.phpand.htaccessinto the project root. It appears to work but can expose.env,composer.jsonand logs. - Forgetting
storage:linkafter changing paths. Uploaded files return 404 untilpublic/storageexists in the served public folder. - Old cached config. After editing
APP_URL, runphp artisan config:cacheagain. - Asset URLs still containing
/public. Useasset()and@viterather than hard-coded paths.
How do you verify it works?
- Open
https://example.com/; the home page should load without/public. - Open
https://example.com/.envandhttps://example.com/composer.json; both must return 404 or 403, never the file. - Open any nested route to confirm rewriting works.
- 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.



