A default web server install is built to work, not to be secure. This web server hardening checklist closes the common gaps attackers and scanners look for: leaked version numbers, exposed .git and .env files, directory listings, weak TLS and PHP execution in upload folders. Each item includes the Nginx and Apache settings.
Before you start
- Root access to the server and a way to test (curl, a browser).
- The OS itself should be hardened too: Linux server hardening checklist.
- Test changes on staging; some apps rely on behaviour you may disable.
Keep the software updated
Most real compromises use known, already-patched bugs. Enable automatic security updates (automatic security updates) and update PHP, the web server and the apps on it.
Hide version information
- Nginx:
server_tokens off;inhttp. - Apache:
ServerTokens ProdandServerSignature Off. - PHP:
expose_php = Offin php.ini, to drop theX-Powered-Byheader.
This doesn't fix vulnerabilities, but it stops scanners from instantly matching your exact version against exploit lists.
Block hidden and sensitive files
Leaked .git folders and .env files expose source code and passwords.
# Nginx
location ~ /\.(?!well-known) { deny all; }
location ~* \.(sql|bak|old|swp|log|ini)$ { deny all; }# Apache
<FilesMatch "^\.">
Require all denied
</FilesMatch>
<FilesMatch "\.(sql|bak|old|swp|log|ini)$">
Require all denied
</FilesMatch>
RedirectMatch 404 /\.gitThe Nginx rule keeps /.well-known/ reachable for Let's Encrypt. Better still, keep such files outside the web root entirely.
Disable directory listings
- Nginx:
autoindexis off by default; make sure nobody turned it on. - Apache:
Options -Indexesin the site's<Directory>, or disable mod_autoindex.
Don't execute PHP in upload folders
A classic attack uploads shell.php as an "image" and then requests it.
# Nginx (before the general PHP location)
location ~* ^/(wp-content/uploads|uploads|media)/.*\.php$ { deny all; }# Apache (.htaccess inside the uploads folder)
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>Allow only the HTTP methods you use
# Nginx, inside server
if ($request_method !~ ^(GET|HEAD|POST)$) { return 405; }# Apache, inside the site Directory
<LimitExcept GET POST HEAD>
Require all denied
</LimitExcept>
TraceEnable OffAdd PUT, PATCH, DELETE and OPTIONS if you host a REST API or need CORS.
Enforce modern TLS
- Allow only TLS 1.2 and 1.3, with forward-secret ciphers.
- Redirect all HTTP to HTTPS and send HSTS.
- Enable OCSP stapling where supported.
Details: TLS versions and cipher suites.
Send security headers
X-Content-Type-Options, Referrer-Policy, frame protection and a Content-Security-Policy: security headers in Nginx and Apache.
Limit request size and slow clients
- Nginx:
client_max_body_size 20m;(or what your uploads need),client_body_timeout 15s;,client_header_timeout 15s;. - Apache:
LimitRequestBody 20971520and mod_reqtimeout (RequestReadTimeout header=20-40,MinRate=500).
Add a catch-all default site
Requests by bare IP or unknown hostnames (mostly scanners) should not reach a real site. Use a default server returning 444 in Nginx, or a first vhost with Require all denied in Apache.
Rate limit and filter
- Rate-limit logins and APIs: Nginx rate limiting.
- Add a WAF: ModSecurity with OWASP CRS.
- Ban repeat offenders from logs with Fail2ban.
Isolate sites and least privilege
Run each site's PHP as its own user, keep code read-only to PHP where possible, and never use 777. See web server users and permissions.
Verify
curl -sI https://example.com/ | grep -iE 'server|x-powered'
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/.env
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/.git/config
curl -s -o /dev/null -w "%{http_code}\n" -X TRACE https://example.com/Sensitive paths should return 403 or 404, TRACE 405, and headers should show no version numbers.
FAQ: web server hardening
Is hiding the version "security through obscurity"?
Partly, yes. It's a small extra layer, never a replacement for updates.
Do I need all this on shared hosting?
The server side is handled by your host. On MdmHost, focus on app updates, strong passwords and .htaccess rules for your own folders.
How often should I review the config?
After every major change and at least a few times a year.
