Guides · ← All guides
Common Website Misconfigurations (and How to Fix Them)
Last updated: October 13, 2026 · 9 min read
Ask a penetration tester where breaches start and you'll rarely hear about exotic zero-days. You'll hear about misconfigurations: settings that were never tightened, defaults that were never changed, temporary openings that became permanent. They are the highest-probability findings in every assessment because they're easy to make, easy to miss, and often trivial to exploit.
How trivial? In January 2017, attackers scanned the internet for MongoDB databases left open to the world (no passwords on the admin accounts, ports answering anyone who knocked) and wiped them, replacing the data with ransom notes. Security researchers tracking the wave counted more than 27,000 compromised databases in about a week; handles like Harak1r1 and kraken0 became briefly infamous, and some victims paid ransoms only to learn the attackers never had their data to return. There was no vulnerability to patch. No zero-day. The databases were simply open, and the fix (bind to localhost, require authentication) had existed all along. That is the entire thesis of this guide: attackers don't break in nearly as often as they log in.
Below are the misconfigurations that show up again and again, each with what the attacker sees and the exact fix. Work them in order.
1. Open database and service ports
MySQL on 3306, Redis on 6379, MongoDB on 27017, SSH on 22, all answering the whole internet. This is the MongoDB story above, still repeating: Shodan, the search engine for internet-connected devices, continuously indexes reachable services, so an open database is discoverable by anyone with a browser, no scanning required. Older Redis versions famously shipped with no authentication at all, trusting the network to keep strangers out. A reasonable design only if the network actually does.
The fix is a network boundary, not a prayer. Bind datastores to localhost or a private network, require authentication everywhere, and manage them through a bastion host or VPN, never directly from the internet. In your cloud security group or firewall, the rule is simple: the database port is reachable from your application servers and nothing else. A port scan takes seconds; an exposed database with weak credentials takes minutes to loot. Close the port first, ask questions later.
2. Exposed admin panels
/admin, /wp-admin, /phpmyadmin, reachable from everywhere and often guarded by nothing stronger than a password somebody chose in 2019. Directory-discovery scans request these paths by default because they work so often. A database admin tool like phpMyAdmin sitting on the public internet is effectively a second front door to your data, and brute-force tools are patient in a way humans aren't.
The fix has layers, and you want at least two. First, never expose database administration publicly at all. Reach it through a VPN or an SSH tunnel. Second, restrict admin paths by IP allowlist at the web server level. In Nginx that looks like this:
location /admin {
allow 203.0.113.42; # your office IP
deny all;
# ... your normal admin config
}
Third, put anything sensitive behind an additional authentication gate even after the allowlist: defense in depth means a leaked IP or a misconfigured rule doesn't hand over the keys. And rename nothing as a substitute for restricting: security through obscurity at /secret-admin-xyz slows an attacker by seconds.
3. Exposed .git directories
A .git folder deployed to production is one of the few findings that makes assessors use the word "critical" without hedging. Here's why, in plain terms: the .git directory contains your entire version history. An attacker who can fetch it can reconstruct your source code: every file, every commit, every secret that was ever committed and later "removed." Git never forgets; that's its job. The API key you committed in March and deleted in April is still sitting in the history, downloadable by anyone who asks the right URLs.
This is a pipeline problem, not a one-server problem. The fix is to never deploy .git to production in the first place: build deployment artifacts (archives, containers, rsync excludes) that omit it, and fix the pipeline rather than deleting the folder on one server and calling it done. As a backstop, block dotfile access at the web server level so a future slip doesn't re-expose it:
# Nginx: deny all hidden files and directories
location ~ /\. {
deny all;
}
4. Missing security headers
Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, and the Secure and HttpOnly cookie flags are free, standardized protections that browsers enforce on your behalf. Check your own with one command: curl -sI https://yourdomain.com — and look at what's absent. Missing them doesn't mean you're breached; it means you're declining defenses against clickjacking, protocol downgrade, and cookie theft that cost a few configuration lines to enable.
Set them once in your server or CDN configuration and forget them. The two that buy the most: Strict-Transport-Security with a long max-age (browsers then refuse to ever connect over plain HTTP), and cookie flags: Secure so cookies only travel over HTTPS, HttpOnly so JavaScript can't read them (which neuters a whole class of session theft after an XSS flaw). These are low-severity findings individually, and some of the cheapest wins in the report.
5. Directory listings enabled
When a web server has no index file for a directory and listing is enabled, it helpfully shows every file in that folder (uploads, logs, backups, internal documents) as a clickable page titled something like Index of /uploads. Attackers love these pages because they're a sitemap the owner drew for them.
Disable listing in your server configuration (Options -Indexes in Apache, autoindex off; in Nginx) and place an empty index file in upload directories as a backstop for the day somebody's config change re-enables it. Thirty seconds of configuration against an attacker browsing your file tree like a catalog.
6. Verbose error pages and version banners
Stack traces, framework version banners, and debug toolbars in production hand attackers a precise inventory of your software — which framework, which version, which database. A response header reading Server: Apache/2.4.49 isn't just a version string; it's a pointer to the exact set of known CVEs affecting that build, including the path-traversal flaw catalogued as CVE-2021-41773. The attacker's research step becomes a lookup.
The fix: generic error pages in production ("something went wrong," a reference ID, nothing else), version banners disabled (ServerTokens Prod and ServerSignature Off in Apache), framework debug mode off outside development, and stack traces logged server-side where they belong. Never rendered to the visitor who triggered them.
7. Forgotten subdomains and staging sites
staging.yoursite.com, old.yoursite.com, test.yoursite.com: unmaintained, unpatched, often with weaker credentials than production, and trusted by your main domain's cookies and CORS settings. They persist because nobody owns the subdomain list; decommissioning feels like someone else's job.
Inventory them the way an attacker does: certificate transparency logs. Every certificate ever issued for your domain is public — open crt.sh, search your domain, and read the list of subdomains you've forgotten. Then decommission what shouldn't exist and bring what remains up to production standards: patched, monitored, credentialed like it matters. Because to an attacker, it does.
8. Backup files in web roots
config.php.bak, site.zip, db.sql: leftover backups sitting in publicly accessible directories. The story is always the same, so let's tell it once, clearly labeled as the hypothetical it is: imagine it's Friday, 6:47 PM. A developer is pushing a risky update, makes a quick backup of the site into the web root "just in case," the deploy works, and the backup stays. Eight months later an automated scan requests /backup.zip and the server cheerfully serves 200 MB of code, configuration, and a database dump. Nobody did anything malicious. Somebody was in a hurry, once.
The fix: store backups outside the web root entirely. A backup inside the document root is a publication, not a backup. And add backup extensions (.bak, .zip, .sql, .tar.gz) to your server's deny rules as a backstop for the next hurried Friday.
The weekend hardening routine
If you do nothing else, do this in order, this weekend: scan your open ports and close what shouldn't face the internet. Pull your admin tools behind a VPN or IP allowlist. Check for /.git/HEAD and common backup filenames on your own domain. Run curl -I and add the missing headers. Turn off directory listing and verbose errors. Inventory your subdomains on crt.sh.
Notice the pattern across all eight: none of these require an attacker to be clever. They require the defender to have been busy. Misconfigurations are a maintenance problem, and the fix is a routine: assess on a schedule, fix by severity, re-check after every deployment. The cheapest breach to prevent is the one caused by a setting nobody remembered to change.
Frequently asked questions
What is the most common website misconfiguration?
Exposed services and interfaces: database ports reachable from the internet, admin panels without access restrictions, and leftover files like .git directories or backups in the web root. Each one started as something reasonable (a debugging session, a quick backup, a default install) and never got reverted.
How do attackers find misconfigurations?
The same way assessments do: automated scanning. Port scans find open services, certificate transparency logs reveal subdomains, directory probes request thousands of common paths, and Shodan continuously indexes reachable services across the internet. If it answers publicly, it will be found.
How do I check my site for misconfigurations?
Run an external assessment: scan your open ports, request your admin paths and common backup filenames, check response headers with curl -I, and inventory subdomains via crt.sh. Fix by severity (ports and exposed files first), then re-scan to verify, and repeat after every deployment.