Nginx Hardening Checklist for Production Servers

- What belongs on an Nginx hardening checklist?
- Start with the effective configuration, not a sample file
- Harden the Ubuntu host before tuning Nginx
- Make host routing fail closed
- Separate Nginx, PHP-FPM, code, and writable data
- Bound requests to the application's real contract
- Reduce information exposure without mistaking it for a boundary
- Treat TLS and response headers as application changes
- Add rate limiting only after the client identity is trustworthy
- Keep logs useful and proportionate
- Make every production change reversible
- Final production review
- Sources
What belongs on an Nginx hardening checklist?
Nginx hardening is a controlled reduction of attack surface: inventory the effective configuration, expose only required services, route unknown hosts safely, separate Nginx from PHP-FPM, bound requests to what the application accepts, maintain TLS, limit sensitive logging, and verify every change with a rollback ready. This checklist uses Ubuntu 24.04 LTS as its reference platform. It contains no command blocks because the configuration was not tested on a clean Ubuntu 24.04 host.
A checklist cannot know which package source, modules, application routes, proxies, upload sizes, or recovery tools exist on a particular server. Record those facts before changing anything.
| Gate | Evidence to keep | Failure the gate is meant to prevent | Owner and rollback |
|---|---|---|---|
| Version and config inventory | Package origins, enabled modules, effective Nginx and PHP-FPM configuration | Applying advice for a different build | Named operator; last known-good config |
| Network exposure | Listening services, firewall policy, administrative access route | Unnecessary public services | System owner; console or recovery access |
| Host routing | Expected names and an explicit default-server policy on every public port | Unknown hosts reaching a real site | Nginx owner; prior server blocks |
| Files and runtime | Public roots, owners, PHP-FPM pools, writable paths | Web processes changing code or reading another site | Application owner; permission manifest |
| Request controls | Application-approved methods, body limits, and timeouts | Unbounded or invalid traffic consuming resources | Application owner; recorded prior values |
| TLS and headers | Certificate scope, renewal evidence, protocol test, header report | Expiry, downgrade, or a policy that breaks the app | TLS owner; tested prior policy |
| Abuse controls | Baseline traffic, trusted proxy chain, dry-run results | Blocking legitimate clients or trusting forged addresses | Operations owner; immediate disable path |
| Logs and monitoring | Required fields, retention, alerts, access controls | Missing incidents or retaining secrets | Operations and privacy owners; prior format |
Start with the effective configuration, not a sample file
Record the Ubuntu release, Nginx package source and build, PHP version, PHP-FPM pools, loaded modules, active server blocks, listening addresses, certificate paths, and upstreams. Include configuration brought in through other files. A hardening change reviewed against one visible file can miss an inherited directive or a second virtual host.
Nginx documents a configuration test and a separate option that dumps the complete configuration. Use the platform's approved operational procedure to obtain both syntax-test evidence and the effective configuration. This article does not publish those commands because they were not tested here as written.
Treat a scanner result as an observation, not an implementation plan. Give each finding an owner, proposed control, validation test, and rollback condition.
Harden the Ubuntu host before tuning Nginx
The web server cannot compensate for an exposed administration service or an unpatched operating system. Ubuntu's current security guidance recommends updates, least-privilege accounts, a firewall, secured remote access, and AppArmor. Verify which services listen publicly, which account can administer them, and how recovery works if network access is lost.
Ubuntu says unattended-upgrades is installed by default and security updates are enabled after installation. It also warns that adding a third-party repository does not automatically make that repository eligible for unattended upgrades. Inspect the actual allowed origins and logs instead of assuming every installed package is covered.
AppArmor is installed and loaded by default on Ubuntu, but that does not prove every workload has a useful enforcing profile. Record profile state and denials. Firewall, SSH, update, and AppArmor changes require a system owner and tested recovery path.
For the base stack and its package context, start with the Ubuntu 24.04 LEMP installation guide.
Make host routing fail closed
Nginx selects a virtual server by address, port, and request host. Its documentation states that an unmatched host is handled by the default server for that listening port. Therefore, list every public address and port, identify the default for each, and test an expected name, an unknown name, the server IP, and a request without a normal host value.
The default path must be deliberate. It should not expose a production application merely because that server block appears first in a file. Keep document roots specific to one site, outside user-upload or backup directories, and confirm that directory indexing is not enabled where no listing is intended. Search public roots for environment files, archives, database exports, editor copies, source maps, and deployment artifacts before launch.
The Nginx server-block guide explains virtual-host structure. Every public listener also needs a tested unknown-host outcome.
Separate Nginx, PHP-FPM, code, and writable data
Run application code and Nginx workers without administrative privileges. Give the web path only the access required by the application. Code and configuration normally need read access, while uploads, caches, sessions, and generated files need narrowly defined writable locations. A recursive world-writable permission change is not a hardening method.
Separate applications into PHP-FPM pools when they need distinct users, limits, logs, or deployment ownership. The PHP manual documents that clear_env defaults to enabled, which prevents arbitrary environment variables from reaching workers unless added to the pool configuration. It also documents security.limit_extensions, whose default permits .php and .phar; review the effective value against the application's entry points and the Nginx-to-FPM mapping.
Keep an FPM status endpoint off the public Internet. If operations needs it, restrict it to the monitoring path and authorized clients. Record socket or loopback ownership, Nginx access, pool user, application write paths, and cross-site isolation as separate checks.
Bound requests to the application's real contract
Set request-body size, accepted methods, header and body timeouts, and upstream timeouts from application requirements and observed traffic. There is no universal secure value. An upload service, an API, and a read-only documentation site have different contracts.
Nginx documents a client_max_body_size default of 1m; a request above the configured limit receives a 413 response, while a value of 0 disables the check. That documented default is a fact about the directive, not a recommendation. The application owner should supply the largest legitimate request, expected method set, slowest valid operation, and proxy path before an operator changes the values.
Test normal requests, boundary-sized requests, invalid methods, slow upstream behavior, and the application's own error handling. A lower limit that rejects valid backups or media is an outage. A very high limit chosen to make the error disappear removes the control.
Reduce information exposure without mistaking it for a boundary
Nginx documents server_tokens on as the default. Turning version emission off removes the version from Nginx-generated error pages and the Server response header field. It does not patch the service, remove every product clue, or hide information added by an upstream application.
PHP's expose_php setting similarly controls PHP identification in web-server headers. PHP's own security documentation describes this as a limited information-reduction measure, not a security control by itself. Verify the response at the public edge, including errors and redirects, but keep update status and exposure reduction as separate checklist lines.
Public responses should avoid stack traces, local paths, queries, secrets, and internal service names. Preserve actionable detail in access-controlled logs.
Treat TLS and response headers as application changes
Maintain certificate coverage, renewal automation, private-key access, redirects, and the deployed certificate chain. Confirm renewal with the actual automation path and monitor the certificate served at the edge, not merely a file on disk. The existing Certbot and Nginx guide covers certificate installation.
Do not copy a cipher list or protocol block whose origin and platform support are unknown. Use current guidance for the installed TLS library and Nginx build, then test representative clients. Do not enable HTTP Strict Transport Security until the owner understands its persistence and the effect on every covered hostname.
Content Security Policy, framing controls, referrer policy, content-type handling, and permissions policy are application-dependent. Inventory scripts, styles, frames, APIs, fonts, media, and third-party origins before enforcement. Start with report-only behavior where the policy supports it, inspect violations, and obtain application-owner approval before blocking. A green header scanner does not prove the application remains usable.
Add rate limiting only after the client identity is trustworthy
Nginx's request-limiting module applies a leaky-bucket method to a configured key and supports dry-run observation. The appropriate key, rate, burst behavior, and exclusions depend on the application. No fixed value belongs in a general checklist.
First establish whether Nginx sees the actual client or a reverse proxy. Trust only known proxy addresses and verify the resulting client identity in logs. Otherwise, one shared proxy address can make unrelated users look like one client, while an untrusted forwarded header can make the limit ineffective.
Observe candidate rules before enforcement. Account for login endpoints, APIs, health checks, webhooks, shared networks, and legitimate bursts. Define the enforcement metric and rollback threshold. Rate limiting is not authentication or a denial-of-service guarantee.
Keep logs useful and proportionate
Nginx access logs can include status, request length, bytes sent, and request time. Choose the minimum fields required for operations and incident review. Avoid logging authorization values, session identifiers, passwords, full sensitive query strings, or request bodies. Check application and proxy logs as well; sensitive data often enters upstream of Nginx.
Define ownership, permissions, rotation, retention, clock synchronization, alerting, and deletion for each log. Monitor changes in error rate, upstream failures, rejected requests, certificate expiry, disk use, restart failures, and update failures. A log file with no alert or review owner is storage, not monitoring.
Make every production change reversible
Before editing, preserve the previous configuration through the team's approved version-control or backup process and confirm how it will be restored. Make one coherent change at a time. Validate syntax and the effective configuration, exercise the affected routes in a staging or canary path, reload through the approved service procedure, then inspect process state, logs, response headers, certificates, and application transactions.
Do not proceed when recovery access is uncertain. Firewall, remote-access, permission, TLS, and process-user changes can remove administrative or application access. A named operator must own the observation window and rollback decision.
Record the old and new evidence, reviewer, deployment time, validation results, exceptions, and rollback outcome. Hardening is maintained state, not a one-time edit.
Final production review
- The OS, package origins, modules, effective configuration, public listeners, and owners are recorded.
- Security updates and third-party repository coverage are verified from configuration and logs.
- Administrative access, firewall policy, AppArmor state, recovery access, and rollback ownership are known.
- Every public port has tested expected-host and unknown-host behavior.
- Public roots contain no backups, secrets, exports, or unintended listings.
- Nginx, PHP-FPM pools, application code, and writable data have distinct least-privilege boundaries.
- Request sizes, methods, and timeouts match an application-approved contract.
- TLS renewal, edge certificate, redirects, and response-header behavior are monitored.
- Rate limits use a verified client identity and were observed before enforcement.
- Logs omit unnecessary sensitive data and have rotation, retention, access, and alert owners.
- The change has syntax, effective-config, route, application, and rollback evidence.
Sources
- Ubuntu Server documentation: Security suggestions — updated June 26, 2026; supports updates, least privilege, firewall, SSH, package reduction, and AppArmor guidance.
- Ubuntu Server documentation: Automatic updates — accessed September 3, 2026; supports the default installation and daily security-update behavior of
unattended-upgrades, its configuration and logs, and the warning about third-party repositories. - Ubuntu Server documentation: AppArmor — accessed September 3, 2026; supports AppArmor's purpose and default installed-and-loaded state on Ubuntu.
- Nginx: How nginx processes a request — accessed September 3, 2026; supports host selection and default-server behavior per listening port.
- Nginx HTTP core module — accessed September 3, 2026; supports the exact
server_tokensandclient_max_body_sizebehavior and defaults described here. - Nginx request-limiting module — accessed September 3, 2026; supports the leaky-bucket model and dry-run capability.
- Nginx HTTP log module — accessed September 3, 2026; supports configurable access-log formats and the named timing, size, and status fields.
- PHP manual: PHP-FPM configuration — accessed September 3, 2026; supports
clear_env,security.limit_extensions, and FPM status-path behavior. - PHP manual: Hiding PHP — accessed September 3, 2026; supports the limited purpose of reducing exposed PHP information.
An independent publication. Not affiliated with any prior owner of this domain.