Overview
nginx is a high performance web server designed for serving high-performance, scalable applications in an efficient, responsive manner. It can be used to serve static content, load balance HTTP requests, and reverse proxy FCGI/PSGI/USWGI and arbitrary TCP connections. Given this, it's important to be able to securely configure and deploy nginx installations to provide a secure web frontend for your application and minimize attack surfaces.
Securing the binary
Keep updated
nginx's core codebase (memory management, socket handling, etc) is very secure and stable, though vulnerabilities in the main binary itself do pop up from time to time. For this reason it's very important to keep nginx up-to-date. Most modern Linux distributions will not push the latest version of nginx into their default package lists, so to install the latest version of nginx via a package, you may need to add additional package repositories to your system. See nginx's documentation below for per-distro details.
Compiling from source
As an alternative to building packages, it's possible to build nginx from source. Doing so allows you to run the latest version available from the nginx development team and allows for additional security configurations. Building from source requires a few steps. First, you'll need to make sure you have the necessary operating system packages for compiling from source.
[user@instance]$ sudo apt-get update && sudo apt-get install automake gcc \ libpcre3-dev zlib1g-dev make -y
Next you will need the source archive and its signature, both of which you can download from the official nginx site. This example will use version 1.12.2, the current stable branch as of this writing:
[user@instance]$ wget -q https://nginx.org/download/nginx-1.12.2.tar.gz{,.asc}
You'll also want to grab the developer's signing key and verify the contents of your download. First, you'll need the signing key, which can be downloaded from nginx.org:
[user@instance]$ curl -sS https://nginx.org/keys/mdounin.key | gpg --import gpg: directory `/home/ubuntu/.gnupg' created gpg: new configuration file `/home/ubuntu/.gnupg/gpg.conf' created gpg: WARNING: options in `/home/ubuntu/.gnupg/gpg.conf' are not yet active during this run gpg: keyring `/home/ubuntu/.gnupg/secring.gpg' created gpg: keyring `/home/ubuntu/.gnupg/pubring.gpg' created gpg: /home/ubuntu/.gnupg/trustdb.gpg: trustdb created gpg: key A1C052F8: public key "Maxim Dounin <mdounin@mdounin.ru>" imported gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1) gpg: no ultimately trusted keys found
Now verify the signature:
[user@instance]$ gpg --trusted-key 0x520A9993A1C052F8 --verify nginx-1.12.2.tar.gz{.asc,} gpg: Signature made Mon 01 Apr 2024 01:18:21 PM UTC using RSA key ID A1C052F8 gpg: key A1C052F8 marked as ultimately trusted gpg: checking the trustdb gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u gpg: Good signature from "Maxim Dounin <mdounin@mdounin.ru>"
From here, unpack the tarball, compile nginx, and install it:
[user@instance]$ tar -zxf nginx-1.12.2.tar.gz [user@instance]$ cd nginx-1.12.2/ [user@instance]$ ./configure && make && sudo make install
Removing unnecessary modules
By default, nginx compiles with a number of modules that extend its functionality. These allow nginx to be extended to perform a number of functions but it's unlikely that every module will be used on any given server. It's recommended to remove unused modules to reduce the size of the compiled binary, and reduce the attack surface nginx presents to the world (for example, a vulnerability found in the uwsgi proxy would not be exploitable against a server that does not leverage the uswgi module). Removing modules can be done at compile-time via the configure script. For example:
[user@instance]$ ./configure --without-http_uwsgi_module
The configure script provided with the nginx script provides a large number of compile-time options.
Securing configurations
Computers are only as smart as the people using them. nginx is built to be stable and secure, but it will only be as secure as the user who configures it. Once nginx is built and installed, configuring the server to be as minimal as possible is important.
Run as an unprivileged user
In security, the principle of least privilege states that an entity should be given no more permission than necessary to accomplish its goals within a given system. In the context of your nginx web server, this means locking down nginx to run only with the permissions necessary to run.
First, create a new user without sudo privileges. Then you can configure nginx to run as an unprivileged system user (e.g., not the root user or a user with sudo privileges). This is done via the user directive in the /etc/nginx/nginx.conf configuration file. At first it may appear commented out:
#user nobody;
Uncomment it and change the user to the new user you created.
user nginx;
Disable Server Tokens
The HTTP spec recommends (but does not require) that web servers identify themselves via the Server header. Historically, web servers have included their version information as part of this header. Disclosing the version of nginx running can be undesirable, particularly in environments sensitive to information disclosure. nginx can be configured to not display its version in the Server header by editing the nginx.conf file with the following:
server_tokens off;
Hide upstream proxy headers
In the same vein, when nginx is used to proxy requests from an upstream server (such as a PHP-FPM instance), it can be beneficial to hide certain headers sent in the upstream response (for example, the version of PHP running). For example, consider the following response from an nginx server running a PHP application:
[user@instance]$ curl -I https://example.com HTTP/1.1 200 OK Server: nginx Content-Type: text/html; charset=UTF-8 Connection: keep-alive Vary: Accept-Encoding X-Powered-By: PHP/8.2
Disclosing the version of PHP can be undesirable; nginx configurations make this easy to hide with the proxy_hide_header directive:
proxy_hide_header X-Powered-By;
Resending the request to the same server would now have this result:
[user@instance]$ curl -I https://example.com HTTP/1.1 200 OK Server: nginx Content-Type: text/html; charset=UTF-8 Connection: keep-alive Vary: Accept-Encoding
Add security headers
In addition to masking sensitive information, nginx can be used to inject headers with security-positive implications into responses as well. A trivial example is adding an X-Frame-Options header to prevent clickjacking attacks:
add_header X-Frame-Options SAMEORIGIN;
This directive can also be used to add arbitrary headers at your whim.
Restrict access by IP
Sensitive areas of websites, such as admin control panels, should have strict access controls placed on them. nginx makes it easy to whitelist IP access to certain locations of your website and deny traffic to all other IP addresses:
location /wp-admin { # allow access from one IP and an additional IP range, # and block everything else allow 1.2.3.4; allow 192.168.0.0/24; deny all; }
Restrict access by password
Access to certain locations can also be set via password-based credentials, using the same format that Apache's .htaccess and .htpasswd files use:
location /wp-admin {
auth_basic "Admin Area";
auth_basic_user_file /path/to/.htpasswd;
}
Where the contents of .htpasswd looks something like:
user1:password1 user2:password2 user3:password3
Securing SSL/TLS
nginx excels at serving SSL/TLS traffic. Configuring your web server to provide securing SSL/TLS configurations for clients is essential to maintaining a secure connection.
As a note, it's strongly recommended that encrypted traffic use only newer TLS protocols, instead of legacy SSL. Both versions of SSL widely available today (SSLv2 and SSLv3) have severe security flaws, and should never be used in production environments. Historically, the directives associated with SSL/TLS configuration in nginx are prefixed with ssl. However to promote the use of modern security protocols, this tutorial uses the term TLS when referencing encrypted (HTTPS) traffic, and ssl when applicable to specific nginx configuration directives.
Turn TLS On
In order to serve encrypted traffic, SSL/TLS needs to be enabled for your server. Fortunately, encrypted connections can be enabled/disabled on a per-server basis in nginx:
server { # regular server listening for HTTP traffic listen 80; } server { # server listening for SSL traffic on port 443; listen 443 ssl; }
Enable strong TLS ciphers
By default, nginx allows for a wide variety of cryptographic ciphers to be used in TLS connections. Some of these ciphers are legacy offerings that are weak or prone to attack, and shouldn't be used. DreamHost recommends using the Modern or Intermediate cipher suites outlined by Mozilla here:
The modern list of ciphers is stronger, but will cause connectivity problems for older platforms like Internet Explorer or Windows XP. Additionally, it's recommended that the server prefer which ciphers can be used:
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; ssl_prefer_server_ciphers on;
Enable TLS session caching
Opening a new TLS connection to a server is very expensive as a result of the cryptographic protocols involved. To maintain a high-performance environment, it's recommended to cache existing TLS connections so that each new request from a client/browser does not need to perform the full TLS handshake:
ssl_session_cache shared:SSL:50m; ssl_session_timeout 5m;
Use custom Diffie-Hellman parameters
The Logjam attack, published in 2015, showed that it was possible for attackers (such as nation-state actors) to break the Diffie-Hellman key exchange, used to implement forward secrecy (essentially, another layer on top of existing encrypted messages). Mitigating this attack is possible in nginx by computing a unique set of Diffie-Hellman parameters and configuring nginx to use this value:
[user@instance]$ openssl dhparam 2048 > /path/to/dhparam
From here you only need to tell nginx to use the custom values you generated above:
ssl_dhparam /path/to/dhparam;
For more information on the Logjam attack, see the "Weak Diffie-Hellman and the Logjam Attack" site here:
Force all connections over TLS
Encrypted communications are only useful when actually in use. If desirable, it is possible to tell browsers to only use TLS connections for your site. This is accomplished with the Strict-Transport-Security header, which can be added in your nginx config like this:
add_header Strict-Transport-Security max-age=15768000;
You can also configure nginx to send a 301 redirect for plaintext HTTP requests to the TLS version of your site:
server { listen 80; server_name example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com; # the rest of the appropriate server block below... }
Additional security measures
Beyond the basics of installing a secure nginx binary, locking down access to sensitive areas of your site, and properly serving TLS connections, there are some additional steps that can be taken for the extra security-conscious user.
Install a WAF
A WAF (web application firewall) is a piece of software designed to inspect HTTP/HTTPS traffic, deny malicious requests, and generally act as an additional layer of security in your web stack. A properly configured WAF can protect your site from SQLi, XSS, CSRF, and DDoS attacks, as well as provide brute force attack mitigation and zero-day threat patching. There are a few open-source WAF options available for nginx:
ModSecurity
ModSecurity, originally written as a WAF for Apache servers, is the de-facto standard for open-source WAF solutions. Recent work on the project has shifted focus toward nginx support; for more information and details on installation and configuration, see the project's homepage and GitHub page here:
Naxsi
Naxsi is a lightweight alternative to ModSecurity, designed as a native nginx module, and focuses on XSS/SQLi prevention in request parameters.
OpenResty
For users of the OpenResty bundle seeking a scriptable, high-performance WAF, check out lua-resty-waf, which seeks to provide a ModSecurity- compatible rule engine integrated into the nginx + LuaJIT ecosystem.
Automated Log Analysis + Monitoring
Programs like Fail2Ban can be used to monitor nginx access and error logs, searching for attack patterns and taking actions against the attacking client (such as dropping IP addresses, reporting malicious behavior to the IP's owner, etc). Fail2Ban is extensible, allowing you to write your own search pattern and response behavior. For more information and details on installation and configuration, see the project's GitHub page here:
Limit Input Traffic via IPTables
Beyond securing nginx itself, it's important to secure the host environment used to host your web server. Locking down access to things like SSH can greatly increase the security of the host by preventing intrusion attempts. A common approach is to whitelist known IPs that will access your host via SSH, and deny all other port 22 traffic, or to use a jump box that strictly filters shell access. You can also do this by configuring a custom security group for your instance. For more information, see DreamHost's article here: