Web Server Hardening Checklist (2026)
A web server hardening checklist is a set of configuration and operational steps that reduce a web server’s attack surface and limit blast radius if it’s compromised. It focuses on secure defaults for exposure, identity, encryption, input handling, and observability.
A web server hardening checklist is a practical, repeatable set of steps to reduce attack surface on Nginx, Apache, and IIS—so a single bug, misconfiguration, or stolen credential doesn’t become a full environment compromise. This guide focuses on safe defaults for exposure, identity/access, TLS, headers, request limits, and monitoring, with verification commands you can run immediately.
How it works
Hardening works by systematically addressing the most common failure modes attackers exploit—outdated software, exposed admin endpoints, permissive file permissions, weak TLS, dangerous HTTP methods, verbose error leakage, and missing monitoring.
Use this checklist as a repeatable baseline for Nginx, Apache HTTP Server, and IIS, whether you run on VMs, bare metal, or containers.
1) Patch management and inventory (do this first)
- Maintain an accurate inventory: OS version, web server version, modules, deployed apps, and open ports.
- Patch the OS and web server packages quickly; subscribe to vendor security advisories.
- Remove abandoned modules and legacy runtimes (old PHP/ASP.NET components, unused CGI handlers).
Verification
- Confirm versions and loaded modules:
bash
nginx -V 2>&1 | tr ' ' '\n' | sed -n '1,120p'
apachectl -V
apachectl -M
- Check open ports:
bash
ss -lntup
2) Reduce attack surface (disable/close everything you don’t need)
- Expose only required ports (typically 80/443). Block direct access to app/admin ports at the firewall/security group.
- Disable directory listing, sample apps, default sites, and test endpoints.
- Remove unused HTTP methods (e.g., TRACE), and restrict PUT/DELETE unless explicitly required.
- Separate roles: avoid hosting unrelated services on the same web server.
Technical notes
- Nginx: deny hidden files and sensitive paths
nginx
location ~ /\.(?!well-known) { deny all; }
location ~* /(backup|old|test|tmp)/ { deny all; }
- Apache: disable TRACE
apache
TraceEnable off
3) Least privilege: users, permissions, and filesystem layout
- Run the web server as a non-privileged account (no shell, minimal group membership).
- Ensure web content is readable by the server user, but not writable unless required (uploads should be isolated).
- Split writable directories (uploads, cache) from code directories, and enforce ownership/permissions.
- Use separate service accounts per site/app when feasible.
Technical notes - Typical Linux permission goals: - Code: owned by deploy user/root, group-readable by web server; not writable by web server. - Uploads: writable by web server, but isolated; restrict executable bits.
Example: ```bash chown -R root:root /var/www/app find /var/www/app -type d -exec chmod 755 {} \; find /var/www/app -type f -exec chmod 644 {} \;
mkdir -p /var/www/app/uploads chown -R www-data:www-data /var/www/app/uploads chmod 750 /var/www/app/uploads ```
4) TLS/HTTPS: modern crypto and safe defaults
- Redirect HTTP→HTTPS; only allow plaintext HTTP if there’s a justified reason.
- Prefer TLS 1.2/1.3; disable legacy protocols and weak ciphers.
- Enable HSTS (after validating HTTPS is stable).
- Use certificates with automated renewal and alerting.
Technical notes - Nginx TLS baseline (adjust paths/ciphers to your policy) ```nginx ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security “max-age=31536000; includeSubDomains” always;
- Quick certificate/TLS checks:bash
openssl s_client -connect example.com:443 -servername example.com /dev/null | openssl x509 -noout -dates -subject -issuer
```
5) Security headers: baseline protection in every response
Add headers to reduce XSS/Clickjacking/MIME sniffing risks and to guide browsers.
Recommended baseline (tune to your app; especially CSP):
- Content-Security-Policy (CSP): start in Report-Only, then enforce.
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY (or SAMEORIGIN if needed)
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy (restrict powerful APIs)
- Strict-Transport-Security (HSTS, HTTPS only)
Technical notes - Nginx header example ```nginx add_header X-Content-Type-Options “nosniff” always; add_header X-Frame-Options “DENY” always; add_header Referrer-Policy “strict-origin-when-cross-origin” always; add_header Permissions-Policy “geolocation=(), microphone=(), camera=()” always;
# Start CSP cautiously; consider Report-Only first
# add_header Content-Security-Policy-Report-Only “default-src ‘self’; report-uri /csp-report” always;
- Validate headers:bash
curl -sI https://example.com | egrep -i ‘strict-transport|content-security|x-frame|x-content-type|referrer|permissions’
```
6) Request handling: limits, timeouts, and input boundaries
- Set body size limits to reduce upload abuse and memory pressure.
- Configure conservative timeouts to mitigate slow request attacks.
- Rate limit sensitive endpoints (login, password reset, search).
- Normalize and validate upstream headers (don’t trust
X-Forwarded-*unless from known proxies).
Technical notes - Nginx: basic body limit + rate limiting ```nginx client_max_body_size 10m;
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
location = /login { limit_req zone=login burst=20 nodelay; proxy_pass http://app_upstream; } ```
7) Reverse proxy and upstream isolation
- Prefer a reverse proxy (Nginx/Apache) in front of app servers.
- Restrict direct access to upstream services (bind to localhost/private network only).
- Set explicit
Hosthandling and deny unexpected hosts to reduce host-header abuse.
Technical notes - Deny unknown hosts (Nginx): ```nginx server { listen 443 ssl; server_name example.com www.example.com; # … }
server { listen 443 ssl default_server; return 444; } ```
8) Authentication, admin access, and credential blast radius
- Put admin panels behind VPN, IP allowlists, or identity-aware proxies; avoid exposing
/adminto the public internet when you can. - Enforce strong, unique passwords for any web server admin accounts (including IIS/Windows, control panels, and basic auth).
- Use MFA wherever supported (especially for hosting control panels and cloud consoles).
- Use separate credentials for staging vs production; don’t reuse service accounts across hosts.
If you’re defining incident impact and recovery priorities, explicitly model what happens if a web credential leaks—see what is the blast radius of a credential. For practical guidance on password policy, see how do i create a strong password.
Practical tooling (optional) - For teams who manage many admin and service credentials, a password manager can reduce reuse and improve rotation workflows. 1Password is one option: Try 1Password →
9) Logging, monitoring, and alerting (hardening isn’t complete without it)
- Enable access and error logs; ship them off-host (SIEM/syslog/log forwarder).
- Log authentication events and admin actions at the application layer.
- Add alerts for:
- spikes in 4xx/5xx,
- repeated 401/403,
- unusual user agents,
- bursts to login endpoints,
- requests to sensitive paths (
/.env,/wp-admin,/cgi-bin/,/.git/).
Technical notes
- Useful log patterns to hunt:
- Path traversal probes: ../ or %2e%2e%2f
- Hidden file probes: /.git, /.env, /.DS_Store
- Scanner UAs: nikto, sqlmap, masscan-like behavior
- Example quick grep:
bash
zgrep -hE '(\.{2}/|%2e%2e%2f|/\.git|/\.env|sqlmap|nikto)' /var/log/nginx/access*.log*
10) WAF and bot/rate controls (optional but often high ROI)
- Use a WAF (managed or self-hosted) to block common injection and exploit patterns.
- Enable bot protections and geo/IP allowlists when appropriate.
- Don’t rely on WAF alone—treat it as a compensating control.
11) Backups, recovery, and integrity checks
- Back up configs, site content, and app secrets securely (encrypted at rest, access-controlled).
- Test restores. A backup you can’t restore is not a control.
- Consider file integrity monitoring (FIM) for web roots and config directories.
12) Container/Kubernetes specifics (if applicable)
- Run as non-root; set read-only root filesystem where possible.
- Drop Linux capabilities; use seccomp/AppArmor/SELinux profiles.
- Don’t bake secrets into images; use secret stores.
- Use network policies to restrict east-west traffic.
13) Validation: prove the hardening actually works
- Run authenticated vulnerability scans where possible; supplement with configuration linting.
- Perform an external TLS scan and header checks as part of CI/CD or change control.
- Re-validate after upgrades and configuration changes.
Technical notes - Quick external checks: ```bash # Port scan from a controlled host nmap -sS -sV -p 80,443 example.com
# Simple content discovery sanity check (keep it authorized) curl -s https://example.com/.git/HEAD -I ```
When you’ll encounter it
- New server builds or migrations (VM refresh, cloud move, containerization).
- Post-incident remediation, especially after web shell, credential compromise, or defacement.
- Compliance or customer security reviews (SOC 2, ISO 27001 controls mapping, vendor questionnaires).
- Major app releases that introduce uploads, auth flows, APIs, or new reverse proxy paths.
- Before exposing services to the internet, including staging environments that accidentally become public.
Related terms
Removing unnecessary services, modules, and endpoints to minimize entry points.
Granting only the permissions required for the web server and app to function.
Disabling weak protocols/ciphers and enforcing HTTPS best practices.
HTTP response headers that guide browser security behavior (CSP, HSTS, etc.).
Layer-7 filtering to block common web attacks and abusive traffic.
Throttling requests to reduce brute force and resource exhaustion.
Detecting unexpected changes to code/config files.
Front-end server that terminates TLS and routes requests to upstream applications.
Central collection and alerting on logs for detection and response.