Nginx tutorial: secure a web server
Harden the Nginx web server by disabling server_tokens, removing X-Powered-By, restricting HTTP methods, enabling TLS, and compiling without unused modules.
This tutorial walks through hardening a Nginx web server by disabling unnecessary modules, hiding version information, removing the X-Powered-By header, restricting HTTP methods, and enforcing TLS. By the end, you will understand the security purpose of each step and have a hardened Nginx configuration.
What You Will Need
- Root or sudo access to a Linux server running Nginx.
- SSH access to edit the nginx.conf configuration file at
/etc/nginx/nginx.conf. - A valid SSL/TLS certificate for the domain (for the TLS step).
Step 1: Disable Unused Nginx Modules
Nginx ships with preinstalled modules that may not be required for the application. Each enabled module increases the attack surface because it adds code paths that could contain vulnerabilities. Disabling unused modules reduces the amount of code exposed to potential exploits.
Disabling modules requires recompiling Nginx from source. Uninstall the existing Nginx package first, then download and compile the source with
--without flags for each unwanted module.
./configure --without-http_autoindex_module --without-http_ssi_module
make
sudo make installThe
--without-http_autoindex_module flag removes directory listing functionality. The
--without-http_ssi_module flag disables Server Side Includes. Review the full list of optional modules in the Nginx source documentation and exclude any that the application does not use.
Step 2: Disable server_tokens in nginx.conf
Nginx displays its version number in the
Server HTTP response header and on default error pages when
server_tokens is enabled. Attackers use this version information to identify known vulnerabilities specific to that Nginx release. Hiding the version number forces attackers to probe the server instead of relying on passive reconnaissance.
Add
server_tokens off; inside the
http { } block of nginx.conf:
http {
server_tokens off;
}Reload the Nginx configuration and verify the change with
curl:
sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com/The
Server response header should display
nginx without a version number.
Step 3: Remove the X-Powered-By Header in nginx.conf
Nginx and backend application servers (such as PHP-FPM) can add an
X-Powered-By header to responses. This header discloses the backend technology stack, such as
X-Powered-By: PHP/8.2. Removing this header prevents attackers from identifying the exact programming language and version handling requests.
The
more_clear_headers directive from the
ngx_http_headers_more_module removes the header. Add it inside the
server { } block:
server {
more_clear_headers "Server";
more_clear_headers "X-Powered-By";
}The
ngx_http_headers_more_module is not included in the default Nginx package. Install it from a third-party repository or compile Nginx with the module.
If the server uses PHP-FPM and the
headers_more module is not available, hide the
X-Powered-By header using the
fastcgi_hide_header directive instead:
location ~ \.php$ {
fastcgi_hide_header X-Powered-By;
}Reload Nginx and verify the header is no longer present:
sudo systemctl reload nginx
curl -I https://example.com/Step 4: Restrict HTTP Methods in nginx.conf
Nginx accepts all HTTP methods by default, including
DELETE,
TRACE, and
OPTIONS. These methods can expose the server to cross-site tracing (XST) attacks and unintended data modification if the application does not explicitly handle them. Restricting methods to
GET,
POST, and
HEAD reduces the attack surface.
Add a conditional block inside the
server { } directive that returns a 405 Method Not Allowed status code for any disallowed method:
server {
if ($request_method !~ ^(GET|HEAD|POST)$) {
return 405;
}
}Nginx returns HTTP 405 for any request using a method other than GET, HEAD, or POST. Adjust the allowed methods to match the application requirements. A REST API may need
PUT,
PATCH, and
DELETE in addition to the defaults.
Reload Nginx after making the change:
sudo systemctl reload nginxStep 5: Enforce TLS 1.2 and TLS 1.3 in nginx.conf
Nginx supports multiple SSL/TLS protocol versions, including SSLv3 and TLS 1.0. These older protocols contain known vulnerabilities such as POODLE (SSLv3) and BEAST (TLS 1.0). Configuring Nginx to accept only TLS 1.2 and TLS 1.3 prevents clients from negotiating insecure connections.
Add the
ssl_protocols directive inside the
server { } block that handles HTTPS traffic:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
}The
ssl_prefer_server_ciphers on directive tells Nginx to use the server's cipher preference order instead of the client's. This prevents clients from selecting weak ciphers.
Reload Nginx and test the connection with
curl:
sudo nginx -t
sudo systemctl reload nginx
curl -vI https://example.com/ 2>&1 | grep "SSL connection"The output should show a TLS 1.2 or TLS 1.3 connection.
What You Learned
Nginx security hardening involves multiple layers. Disabling unused modules reduces the compiled code that could contain vulnerabilities. The
server_tokens off directive hides the Nginx version from response headers. The
X-Powered-By header exposes the backend stack and should be removed with
more_clear_headers or
fastcgi_hide_header. Restricting HTTP methods to GET, POST, and HEAD prevents method-based exploits. Enforcing TLS 1.2 and TLS 1.3 with
ssl_protocols blocks clients from using deprecated, insecure protocols.
What to Do Next
For additional nginx.conf security recommendations, see the nginx.conf best practicespage. To configure access logging for security monitoring, see Nginx tutorial: configure request logging.