Security

Web server hardening checklist for Nginx and Apache

Lock down a public web server step by step: hide version details, deny hidden files, disable listings and unused methods, enforce modern TLS, limit request sizes and stop PHP running in upload folders.

IntermediateLinux VPS3 min read

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; in http.
  • Apache: ServerTokens Prod and ServerSignature Off.
  • PHP: expose_php = Off in php.ini, to drop the X-Powered-By header.

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 /\.git

The Nginx rule keeps /.well-known/ reachable for Let's Encrypt. Better still, keep such files outside the web root entirely.

Disable directory listings

  • Nginx: autoindex is off by default; make sure nobody turned it on.
  • Apache: Options -Indexes in 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 Off

Add 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 20971520 and 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

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.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.