The fix, in one snippet
location ~* \.(sql|bak|old|zip|tar|tar\.gz|swp)$ { deny all; return 404; }
Illustrative values. Change the paths, hostnames and options to match your own site before using it.
The sections below explain what is tested, why it matters and the alternatives.
What we test
Scan.now requests a short fixed list of well-known backup names: /backup.zip, /backup.sql, /backup.tar.gz, /site.zip, /www.zip, /db.sql, /dump.sql, /database.sql, /<hostname>.zip, /wp-config.php.bak, /wp-config.php~, /config.php.bak, /.htaccess.bak and /web.config.bak. It fetches only the first few kilobytes of any 200 response using a Range header and checks the content rather than trusting the status: ZIP files start with PK\x03\x04, gzip with 1F 8B, SQL dumps with -- MySQL dump, CREATE TABLE or INSERT INTO, and a .bak of a PHP file is confirmed by a literal <?php in the body, which means the server returned the source as text. Soft-404 HTML pages are ignored. Whole archives are never downloaded. Rated high; when the file is a configuration backup with credentials, treat it as critical.
Why it matters
A backup archive is the whole site in one download: application source, configuration with database credentials and API keys, and often the uploads directory with customer documents. A database dump adds the user table, with email addresses and password hashes that go straight into offline cracking, plus whatever personal data the application stores. Editor and admin backups are smaller but just as damaging: wp-config.php.bak is served as plain text because the web server only runs .php through the interpreter, and it contains the database password and the authentication salts. Attackers guess file names from the hostname and common conventions, so "nobody knows the URL" is not protection, and search engines index these files when they are linked from anywhere. The exposed files guide explains how scanners find them and the WordPress guide covers the CMS-specific cases.
How to fix it
Never write backups into the document root. Send them off-server (object storage with restricted access, or a backup host) and delete any that are already there:
find /var/www -type f \( -name '*.bak' -o -name '*.sql' -o -name '*.zip' -o -name '*.tar.gz' -o -name '*~' \) -print
Then deny the extensions at the server so a future mistake is not published. nginx (adjust the list if you intentionally serve archives for download):
location ~* \.(bak|old|orig|save|swp|sql|tar|gz|tgz|zip|7z|rar)$ {
deny all;
return 404;
}
location ~* ~$ { deny all; return 404; }
Apache 2.4:
<FilesMatch "\.(bak|old|orig|save|swp|sql|tar|gz|tgz|zip|7z|rar)$|~$">
Require all denied
</FilesMatch>
Cloudflare WAF custom rule: (http.request.uri.path matches "\.(bak|sql|zip|tar\.gz)$"), action Block, on Pro plans or above where regex matching is available. If a configuration backup was reachable, rotate the credentials it contains and, for WordPress, regenerate the salts in wp-config.php. Check the access log for who downloaded it before you closed the door.
Where this fits
Exposed backup or archive files is check 3 of 14 that the website vulnerability scanner runs under exposed files and information disclosure, ordered the way they are worth fixing. That ordering is the point: Fixing this one while the check above it still fails buys less than it looks like.
Fix this one first
Above it in the same category sits Exposed .git repository (critical), where the site's .git directory is reachable over HTTP. An attacker who has that does not need this, so it is the better use of the same hour.
What fixing this still leaves open
Exposed backup or archive files closes one route in. Immediately below it: Exposed phpinfo() page, where a phpinfo() page is publicly reachable; Directory listing enabled, where the web server generates an index of files for a directory that has no index page, exposing every file in it, including ones that are not linked from anywhere; Exposed server status page, where a server status page (Apache mod_status, nginx stub_status or PHP-FPM status) is reachable from the internet, revealing live requests, client IPs, internal hostnames and load details.
Found in the same scan
The website vulnerability scanner reports this alongside checks from other categories that are at least as serious, including Certificate chain and hostname validation, where the certificate presented for this hostname did not validate: The chain does not reach a trusted root, an intermediate is missing, the name does not match, or the certificate is self-signed or expired, and HTTPS is available, where the site could not be reached over HTTPS on port 443, or the TLS handshake failed. A single run of website vulnerability scanner answers all of them at once.
The first few lines
You are a senior web engineer. I ran a security and SEO scanner against my site and it reported the finding below. Fix it properly rather than suppressing the symptom.
Finding: Exposed backup or archive files (high severity)
Scanner check id: exposed-backup-files
The rest of this prompt names the pages and line numbers we found the problem on, the configuration to change, and the constraints a good answer has to respect. It is free, it just needs an account so the work is not scraped wholesale.
Sign in with GoogleSigning in is free and takes one click. We store your email address and nothing else.
References
Related guides
9 min read · Updated Sep 12, 2026
Exposed Files: .git, .env, Backups and Debug Pages That Leak Your Site's Secrets
Many breaches start with a file that should never have been public: A .git folder, a .env with database credentials, a backup archive or...
Read the guide
9 min read · Updated Sep 15, 2026
Scanning a WordPress Site for Vulnerabilities: What Attackers Look For First
WordPress runs a large share of the web and is attacked accordingly. What an external scan reveals about a WordPress site, from version...
Read the guide