Any web hostBackup file in the web root

Backup or .env file publicly downloadable from your website: what to do now

Any file inside your site's public folder can be downloaded by anyone who guesses its name, including full-site zip archives, database dumps and .env files left by migrations or deploys. Move the files out of the web root, confirm the URLs now return 404, block the patterns at the server, and rotate every secret the files contained.

This guide covers check 03: Secrets and key management; check 08: Observability and deployment in the Zenveus Production Readiness Standard.

For builders

What this means, in plain words

Your web host serves any file it finds in the public folder. A backup zip, a database export or a .env file left there can be downloaded by anyone who requests the right name, and automated scanners request the common names all day. Move the files somewhere private, block those file types, and change any passwords or keys they contained.

A scoped repair request

Using an AI builder? Paste this

Use this prompt in Lovable, Cursor, Replit, or Claude Code with the relevant server files available.

Audit my site's public web root for files that should never be served: backup archives, database dumps, migration-plugin exports and .env or config backups. List each match with its size and date, but do not print file contents or secret values. Propose moving them outside the web root, add a server rule that denies these extensions and dotfiles, and update deploy and backup settings so they are not recreated. Give me a checklist of credentials to rotate based on file names only.

The same failure may appear as

  • wp-content.zip or a full-site archive sits in public_html
  • A database dump (.sql or .sql.gz) is reachable by URL
  • A migration plugin's backup file can be downloaded
  • .env or config.bak returns its contents in the browser

Find the failure layer

Run these checks before rewriting anything

Each check removes a class of causes. Keep the first failing result, its timestamp, and the production log beside it.

01

Find archives and dumps in the web root

From the site root, list files whose names end in .zip, .tar, .tar.gz, .gz, .sql, .sql.gz, .wpress, .bak, .old and .env, plus any folder a backup or migration plugin created.

If this failsAny match inside the public folder is a candidate leak; record its size and modified date before moving it.
find . -maxdepth 4 -type f \( -iname '*.zip' -o -iname '*.tar*' -o -iname '*.gz' -o -iname '*.sql' -o -iname '*.wpress' -o -iname '*.bak' -o -iname '.env*' \) -not -path './wp-content/plugins/*'
02

Request each file anonymously

From a machine outside your network, request each candidate URL without cookies. A range request fetches only the first bytes, so you don't download the whole archive.

If this failsA 200 or 206 response means the file is public; 403 or 404 means the server is already refusing it.
curl -s -o /dev/null -r 0-99 -w '%{http_code}\n' https://example.com/wp-content.zip
03

Work out what the file contained

List the archive or dump contents without extracting it. Look for database dumps, wp-config.php, .env files, private keys and credential files.

If this failsDatabase rows, connection strings or keys mean you must rotate those credentials, not only move the file.
unzip -l backup.zip | grep -iE '\.(sql|wpress|env|pem|key)$|wp-config'
04

Check access logs for downloads

If logs go back far enough, search them for requests to the file's path and note any 200 responses from addresses you don't recognise.

If this failsA successful download by an unknown address turns this into an incident: rotate secrets and review what the data allowed.

Ranked diagnosis

Common root causes, in the order we would test them

01

Migration or backup plugin output

Backup and migration tools often write their archives inside the site folder so the same host can restore them.

02

Manual backup before a change

Someone zipped the site or dumped the database next to it before an update and never removed the file.

03

Deploy copies the whole repository

A deploy uploads .env, config backups or build artifacts because nothing excludes them.

04

No server rule for sensitive extensions

The web server serves any static file by default, so a forgotten file is public as soon as it exists.

Step-by-step repair

How to fix this in your app

Work in the site's root folder over SSH or your host's file manager, then add the deny rule to the web server configuration or the root .htaccess file.

Production safety ruleNever disable access controls, expose service keys, or add wildcard CORS as a routine shortcut.

01

Move every archive out of the public folder

Move backups and dumps to a private directory above the web root or to private storage. Don't delete them until you know whether you still need them.

02

Rotate what the files exposed

If a file contained database credentials, API keys, salts or a .env, change those values at the source and update the app. Moving a file does not undo an earlier download.

03

Deny sensitive extensions at the server

Add a rule that refuses archive, dump and dotfile requests in public folders, so the next forgotten file is not served.

04

Change where backups are written

Point backup and migration tools at storage outside the web root, and exclude .env and backup patterns from deploys.

Implementation example

Apache or LiteSpeed (.htaccess): refuse archives, dumps and dotfiles

<FilesMatch "\.(zip|tar|gz|tgz|sql|wpress|bak|old)$">
  Require all denied
</FilesMatch>
<FilesMatch "^\.(env|git)">
  Require all denied
</FilesMatch>

Test the rule on a copy first: a plugin that serves downloads with these extensions from the public folder would stop working. On Nginx, use equivalent location blocks that return 404.

Prove the repair

How to check that the fix worked

Run these checks with synthetic data in your test environment, then repeat the relevant acceptance checks after deployment.

  • Each former backup URL returns 404 or 403 to an anonymous request.
  • A test file named check.zip placed in the web root is refused by the server rule.
  • The site, admin and downloads that users rely on still work.
  • Credentials that were inside the files have been changed and the app runs with the new values.

If the check still fails

If a file still downloads after the rule is added, check whether a CDN or page cache is serving a stored copy and purge it, and confirm the rule sits in the directory that actually serves the file.

When the built-in AI fix makes it worse

Recover one reproducible failure.

Pause generated changes, restore a known working branch, and capture one failing request with its logs. Change one layer and rerun the allowed and denied checks before proceeding.

Engineering handoff

What Zenveus checks when the quick fix is not enough

We trace one production request through the complete path, isolate the failing boundary, and leave behind evidence your team can repeat.

Exposure inventory

Every public archive, dump and dotfile, with how long it was reachable.

Content review

Which credentials, personal data and code each exposed file contained.

Rotation plan

Ordered credential changes with the app updates each one needs.

Recurrence controls

Server rules, deploy excludes and backup destinations that stop it happening again.

Typical repair pattern

Every former backup URL returns 404 and the exposed secrets are rotated

Anonymous requests for the old paths fail, a server rule blocks the patterns, and credentials that were inside the files have new values.

Evidence left behind
  • Root-cause note
  • Verified production check
  • Rollback and prevention steps

Before the next release

Prevent this failure from returning

Run the web-root search as part of every deploy or monthly maintenance.
Keep backups in private storage with their own access control, never in public_html.

Clear answers

Questions teams ask before they touch production

01If nobody linked to the file, can anyone find it?

Yes. Scanners routinely request common names such as backup.zip, wp-content.zip, site.sql and .env on any domain they find, so an unlinked file is not hidden.

02Is moving the file enough?

Moving it stops new downloads. If the file contained credentials and you can't rule out an earlier download, rotate those credentials too.

03Should I delete old backups?

Move them somewhere private first. Delete them only once you're sure a newer, tested backup exists and you don't need the old one for an investigation.

04How do I know the fix worked in my app?

Run the verification checks on this page against your live domain from outside your network. A file that returns 404 or 403 to an anonymous request is no longer public.

Free next step

Check the boundary before you hand it over

The free tool helps you inspect this symptom. Its result does not establish whether the whole app is production ready.

Take it further

Fixed this one? Find the next one first.

This fix solves one symptom. A guided self-assessment walks you through the rest of the app, one session at a time, and ends with a report and a ranked fix list you can act on or hand to a developer.

Planning more than one stage? Founder Bundles combine the guided products from $149 and save up to $405.

The Verdict

Know whether the symptom is contained or structural.

We can see the symptom from here. What we cannot tell you from outside is whether it is contained or structural. A scanner collects evidence. A named senior engineer makes the decision. For $299, a named senior engineer reads your code and signs a written Verdict against the nine checks in the Zenveus Production Readiness Standard. The 48-hour clock begins when the required access and context are available. If the report does not give your developer a list they can act on, you do not pay.

The 48-hour clock starts when the required access and context are available.

Scroll to Top

Optional tracking stays off until you enable it. You can change your choice at any time.

Essential — always active
Security, requested features, and remembering these choices.

Read our cookie policy