Nginx config for Laravel in production: the complete setup
A production Laravel Nginx config needs four things: set root to the project's /public directory, use index index.php, route unmatched requests with try_files $uri $uri/ /index.php?$query_string, and pass .php requests to a PHP-FPM socket such as unix:/var/run/php/php8.3-fpm.sock. Add a second server block on port 80 that returns a 301 to HTTPS, deny access to hidden files, and set client_max_body_size for uploads. Almost every Laravel 404 on Nginx comes from the root pointing at the project folder instead of /public.
Skip the manual setup. DeployManage provisions your server and ships zero-downtime deploys on any cloud.
$ get started freeNginx does not know anything about Laravel. It serves static files and hands PHP requests to PHP-FPM, and that is the whole story. Laravel routing lives inside public/index.php, so the entire job of your Nginx configuration is to point the document root at the public folder, forward every unmatched request to that single front controller, and connect to the correct PHP-FPM socket. Get those three things right and the framework works. Get any one wrong and you get a 404 or a bare file not found error. This guide gives you a complete, runnable production server block and explains each piece so you can debug the usual failures instead of guessing.
What does a Laravel Nginx config need?
Four settings do the real work. The root must point at the project's public directory, not the project root. The index should be index.php. Unmatched URLs get routed with try_files $uri $uri/ /index.php?$query_string so Laravel's router can take over. And .php requests get passed to PHP-FPM over a Unix socket. Everything else (gzip, security headers, upload limits, the HTTPS redirect) is hardening around those four essentials. Here is a full config that runs a Laravel app on port 443 with a matching HTTP redirect.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php;
charset utf-8;
client_max_body_size 20M;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header X-XSS-Protection "1; mode=block";
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript text/javascript application/xml application/xml+rss image/svg+xml;
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.(?!well-known).* {
deny all;
}
error_page 404 /index.php;
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Save this as /etc/nginx/sites-available/example.com, symlink it into sites-enabled, then run sudo nginx -t to test the syntax and sudo systemctl reload nginx to apply it. Adjust example.com, the root path, and the PHP-FPM socket version to match your box before reloading.
Why is my Laravel site showing a 404 on Nginx?
Nine times out of ten the root is pointing at the project directory instead of the public subfolder. If root is /var/www/example.com, Nginx serves the repository root, which has no index.php for the router to reach, so every route returns 404. Fix it by appending /public to the path: root /var/www/example.com/public;. The other two common causes are a missing try_files line, which means unmatched routes never reach the front controller, and a fastcgi_pass that points at a socket path or port PHP-FPM is not listening on, which produces a 502 or a file not found. Confirm the socket path with ls /var/run/php/ before you assume the config is wrong.
What should the Nginx root be for Laravel?
The root must be the public directory inside your Laravel project, for example /var/www/example.com/public. Laravel puts its front controller, assets, and the .htaccess-equivalent entry point in public on purpose, so that the rest of the codebase (including .env, storage, and vendor) sits above the web root and cannot be requested directly. Pointing the root one level too high exposes source files and breaks routing at the same time. If you ever see your .env or composer.json served as a download, the root is wrong.
How does the try_files directive work in Laravel?
The line try_files $uri $uri/ /index.php?$query_string; tells Nginx to try three things in order. First it looks for a real file matching the request URI, so /css/app.css is served straight from disk. Then it tries the URI as a directory. If neither exists, it falls back to /index.php and appends the original query string, which lets Laravel's router resolve the path. This is what makes pretty URLs like /dashboard/settings work without a matching file on disk. Drop the fallback and any route that is not a physical file returns 404. Keep the order exactly as shown, since putting /index.php first would route static assets through PHP unnecessarily.
How do I configure PHP-FPM for Laravel with Nginx?
Requests for .php files are passed to PHP-FPM over a Unix socket inside the location ~ \.php$ block. The socket path encodes the PHP version, so on a server running PHP 8.3 it is usually unix:/var/run/php/php8.3-fpm.sock. Three lines matter beyond fastcgi_pass: fastcgi_split_path_info separates the script from any path info, include fastcgi_params pulls in the standard CGI variables, and fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name tells PHP-FPM which file to execute. Using $realpath_root instead of $document_root resolves symlinks, which matters for zero-downtime deploys that swap a current symlink between releases. If you get a file not found from PHP, this parameter is the first thing to check.
How do I redirect HTTP to HTTPS for Laravel?
Use a separate server block that listens on port 80 and issues a permanent redirect: return 301 https://$host$request_uri;. Keeping the redirect in its own block is cleaner than an if inside the HTTPS block and keeps the original host and path intact. On the Laravel side, set APP_URL to the https:// address and, if you sit behind a load balancer or proxy, configure TrustProxies so the framework generates HTTPS URLs and does not create a redirect loop. Once the redirect is live, run Certbot or your ACME client to issue the certificate that the HTTPS block references.
How do I add gzip and security headers?
Gzip is enabled with gzip on plus a gzip_types list covering text, CSS, JavaScript, JSON, XML, and SVG. There is no point compressing already-compressed formats like JPEG or PNG, so leave them out. For security, the add_header lines set X-Frame-Options to block clickjacking, X-Content-Type-Options to stop MIME sniffing, and a sensible Referrer-Policy. A production site should also add a Strict-Transport-Security header once you are confident HTTPS is stable, and a Content-Security-Policy tuned to your assets. Keep in mind that a bare add_header inside a nested location replaces the parent headers rather than adding to them, so define them at the server level as shown here.
How do I serve files from Laravel storage?
User uploads written to storage/app/public are exposed through a symlink at public/storage, created by running php artisan storage:link. Because the link lives inside the public root, the existing try_files rule serves those files directly with no extra Nginx config. If uploaded images or PDFs return 404, the symlink is usually missing or was not recreated after a fresh deploy, so re-run the artisan command. For large uploads, raise client_max_body_size in Nginx and match upload_max_filesize and post_max_size in your PHP-FPM pool, since the smaller of the two limits wins.
How do I run Laravel in a subdirectory?
Running a Laravel app under a path such as example.com/app is possible but fiddly, and it is worth avoiding when you can give the app its own subdomain instead. If you must use a subdirectory, add a nested location that aliases the path to the project's public folder and adjust the internal try_files fallback to point at /app/index.php. You will also need to set ASSET_URL so generated asset links include the prefix. A dedicated server_name per app keeps the config far simpler and removes an entire class of asset-path bugs, so reach for a subdomain first.
If you would rather not maintain any of this by hand, this is exactly the kind of thing a control panel automates. DeployManage writes and manages a hardened Nginx config for you when you connect a server, so there is nothing to hand-edit and the root, try_files, and PHP-FPM socket are always correct; you can see how a panel that generates the Nginx config for you handles a Laravel app, or follow the full server walkthrough on how to deploy a Laravel app to a VPS. Once a site is live, adding new features is its own project; a common one is dropping in a support assistant that trains on your own site content, which lives in the front end and never touches the server block you just wrote.
Do I need Apache or Nginx for Laravel?
You can run Laravel on either, but Nginx is the more common production choice because it uses less memory under load and pairs cleanly with PHP-FPM. Apache works well too and ships with a ready-made .htaccess in Laravel's public folder that handles the front-controller routing for you. The trade-off: Apache reads per-directory .htaccess files, which is convenient but slower, while Nginx keeps all rules in one server block that it loads once. For a single app you will not notice the difference. For a busy server hosting several sites, Nginx tends to scale further on the same hardware.
Putting it together
The config above is production-ready: it points the root at public, routes through index.php with the canonical try_files line, talks to PHP-FPM over a versioned socket, compresses responses, sets baseline security headers, blocks hidden files, and forces HTTPS. Copy it, change the domain, path, and PHP version, run nginx -t, and reload. When something breaks, work through the same short list every time: is the root pointing at public, is try_files present, and is fastcgi_pass aimed at a socket PHP-FPM is actually listening on. Those three cover the vast majority of Laravel 404 and file not found errors on Nginx.
Frequently asked questions
Why is my Laravel site showing a 404 on Nginx?
Almost always the root points at the project folder instead of its public subfolder, so Nginx never reaches index.php. Set root to /var/www/example.com/public. The other common causes are a missing try_files line, which stops unmatched routes reaching the front controller, or a fastcgi_pass aimed at the wrong socket.
What should the Nginx root be for Laravel?
The root must be the public directory inside your project, for example /var/www/example.com/public. Laravel keeps its front controller and assets in public so the rest of the code, including .env and storage, sits above the web root and cannot be requested directly. Pointing one level too high breaks routing and exposes source files.
How do I configure PHP-FPM for Laravel with Nginx?
Inside the location for .php files, set fastcgi_pass to the PHP-FPM socket, such as unix:/var/run/php/php8.3-fpm.sock, add fastcgi_split_path_info, include fastcgi_params, and set SCRIPT_FILENAME to $realpath_root$fastcgi_script_name. Match the socket to your installed PHP version and confirm the path with ls /var/run/php/ before reloading.
How do I redirect HTTP to HTTPS for Laravel?
Add a separate server block listening on port 80 with return 301 https://$host$request_uri, which preserves the host and path. On the Laravel side set APP_URL to the https address, and configure TrustProxies if you sit behind a load balancer so the app does not create a redirect loop.
Do I need Apache or Nginx for Laravel?
Either works, but Nginx is the more common production choice because it uses less memory under load and pairs cleanly with PHP-FPM. Apache also works and ships a ready-made .htaccess in Laravel's public folder. For one app the difference is minor; for a busy multi-site server, Nginx tends to scale further.