← All articles

How to check if your Laravel .env file is publicly exposed

· 4 min read

Your .env file holds the keys to everything: the database password, APP_KEY, mail credentials, payment and cloud API keys. If a web server will hand that file to anyone who asks for https://yourdomain.com/.env, the rest of your security work does not matter.

It happens more often than you would expect. Automated bots request /.env and /.git/config on every site they can find, all day, because enough sites still answer.

How it happens

Laravel is designed so that only the public/ directory is reachable from the web. The .env file sits one level above it. The file becomes exposed when the web server's document root points at the project folder instead of public/, for example /var/www/myapp instead of /var/www/myapp/public. This typically comes from:

  • a hand-written nginx or Apache config with the wrong root,
  • a domain pointed at the project directory on shared or panel-based hosting,
  • a copied config from a different, non-Laravel project.

Test it in ten seconds

From any machine, request the file and look at the HTTP status:

curl -s -o /dev/null -w '%{http_code}\n' https://yourdomain.com/.env
  • 403 or 404: the file is not served. This is what you want.
  • 200: the server returned something for /.env. Treat it as exposed until you have looked at the body.

A 200 is not always a leak. Some single-page apps answer every unknown path with their index.html. To be sure, look at what came back:

curl -s https://yourdomain.com/.env | head -5

If you see lines like APP_NAME= or DB_PASSWORD=, it is exposed. Run the same test for the Git metadata, which can leak your source code:

curl -s -o /dev/null -w '%{http_code}\n' https://yourdomain.com/.git/config

You can also run the free PHPGuard scan on your domain. It requests /.env and /.git/config from outside and flags either one that answers with HTTP 200.

Fix it

1. Point the document root at public/

This is the real fix. In nginx:

server {
    server_name yourdomain.com;
    root /var/www/myapp/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}

In Apache, set DocumentRoot /var/www/myapp/public in the virtual host.

2. Block dotfiles as a second layer

Even with the right root, deny access to hidden files so a future mistake does not become a leak. In nginx, keep .well-known reachable for Let's Encrypt:

location ~ /\.(?!well-known) {
    deny all;
}

Reload and test again:

sudo nginx -t && sudo systemctl reload nginx
curl -s -o /dev/null -w '%{http_code}\n' https://yourdomain.com/.env

If it was exposed, assume the secrets are burned

Fixing the config stops new reads. It does not undo the ones that already happened. Check your access log for successful requests to the file:

grep -E 'GET /\.env' /var/log/nginx/access.log | grep ' 200 '

If there are any, or if you cannot rule it out, rotate everything that was in the file:

  • database passwords and any other service credentials,
  • mail, payment and cloud API keys,
  • APP_KEY. Be careful here: changing it invalidates existing sessions and anything encrypted with it, so plan the change instead of running php artisan key:generate blindly on a live app.

Keep it from coming back

  • Set restrictive permissions so other users on the server cannot read it: chmod 640 .env, owned by the deploy user with the web server group.
  • Keep .env out of version control, and never include it in a deploy artifact that is placed under the web root.
  • Test after every server or deploy change. A config tweak is exactly when this regresses.

With a PHPGuard account, the agent also checks .env permissions on each Laravel app on your server, and the external checks re-run automatically every 15 minutes, so a regression gets caught instead of sitting open for months.

Check your own site

The free PHPGuard scan checks for exposed .env and .git files, missing security headers and SSL problems in seconds. For continuous monitoring from inside your server, start the 14-day free trial (no card required).

More articles