Nginx for Laravel: A Modern, Focused Configuration Guide

4 min readUpdated on

Nginx should expose only Laravel's public directory, pass application paths to index.php, and refuse hidden files or arbitrary PHP execution. On nginx 1.25.1+, use http2 on;, not listen ... http2.

nginx
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/current/public;
    index index.php;
    location / { try_files $uri $uri/ /index.php?$query_string; }
    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
    }
    location ~ /\. { deny all; }
}

Use nginx -t before systemctl reload nginx. Keep TLS, redirect and FastCGI values in reviewed includes. If TLS terminates upstream, configure trusted proxies in Laravel; a client-supplied forwarded header is not trustworthy.

Reverse proxies and caching

WebSockets need HTTP/1.1 and upgrade headers. Cache immutable assets aggressively; do not cache authenticated HTML, cookie-sensitive pages or authorization responses without an explicit cache key and invalidation policy.

nginx
location /app/ {
    proxy_pass http://reverb:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

A complete TLS boundary

Keep port 80 deliberately small and make the HTTPS virtual host the only place that serves Laravel. This prevents a forgotten HTTP server block from exposing a different root or PHP policy after a deployment.

nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;
    root /var/www/current/public;
    index index.php;
    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-Content-Type-Options nosniff always;
    add_header X-Frame-Options SAMEORIGIN always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;
    location / { try_files $uri $uri/ /index.php?$query_string; }
    location ~ \\.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_param HTTPS on;
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
    }
    location ~ /\\.(?!well-known).* { deny all; }
    location ~* \\.(?:env|log|sql|bak)$ { deny all; }
}

$realpath_root resolves a release symlink before PHP-FPM receives the script. That is important when a current symlink changes between requests. Do not add a broad PHP location merely because an old CMS used one: Laravel has one public front controller.

Cache artefacts, not personalised application responses

Vite assets have a content hash in their URL, which makes a long immutable cache safe. HTML, /api, signed downloads and cookie-varying routes do not belong in this location. A FastCGI cache is a separate system with a cache key, bypass rules, invalidation and a proof that logged-in HTML cannot leak.

nginx
location ~* \\.(?:css|js|mjs|woff2|svg|png|jpe?g|webp)$ {
    try_files $uri =404;
    expires 1y;
    add_header Cache-Control "public, max-age=31536000, immutable";
    access_log off;
}

Limit an abuse-prone route and verify the reload

Define a zone in http, then apply it narrowly to authentication endpoints. It limits cheap abuse; it does not replace Laravel throttles, authorization or webhook signature verification.

nginx
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;

location = /login {
    limit_req zone=login burst=10 nodelay;
    try_files $uri $uri/ /index.php?$query_string;
}
bash
sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com/health

When not to use this configuration

Do not paste this into shared hosting or a platform that owns Nginx; use its supported integration. Docker image builds, local development cache and deployment belong in focused guides, because their failure and rollback paths differ from a production proxy.

Related articles

Product support

Working through a similar problem in your project?

If this article touches an issue you are dealing with, I can help turn it into a practical implementation plan and a sensible next step.

Comments (0)
Sign in to leave a comment

You need to be signed in to add a comment.

Login

Need someone to take responsibility for the next step?

Let’s talk about your project and define a scope that actually makes sense for your goals.