---
title: Hardening nginx
descriptions: Hardening of the nginx configuration.
summary: Do not expose the used server version and only use secure ciphers for https.
weight: 1
categories:
- security
tags:
- nginx
- hardening
- ssl
- tls
---

# General information
- After you change the nginx configuration always test its validity by executing
```shell-session
$ nginx -t
```
- Limit everything to what is needed for proper function. Do not use nice-to-have modules/configs that do not bear any usage for the user.

# Have logging and monitor it
Logs help you identify and analyse attacks. So use them. But collecting only does not help, you must regularly analyse them.
```nginx
server {
    # ...
    error_log .../error.log warn;
    access_log .../access.log combined;
    # ...
}
```
If collecting logs, keep *GDPR* in mind and only keep them as long as it is really required for securing the server.

> I'm still looking-out for a good log management tool that helps to identify threats.

# Don't expose server version
Exposed server versions make it easier for attackers to find weaknesses of your system, so give them as few information about your system as possible.
- Set ``server_tokens off`` in the ``http`` section of your ``nginx.conf``, e.g.:
```nginx
http {
    # ...
    server_tokens off;
    # ...
}
```
- Verify that nginx config is still valid (see sec. [General information]({{< relref "hardening-nginx.md#general-information" >}}))
- Reload configuration
```shell-session
$ systemctl reload nginx
```

# Limit HTTP methods
Limit to what you need, e.g. GET only is enough for that simple blog:
```nginx
location / {
    limit_except GET { deny all; }
}
```

# Use secure TLS/SSL configuration
As of January 2022, the following configuration is considered as safe:
```nginx
server {
    # ...
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_dhparam /etc/nginx/dhparam.pem;
    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM';
    ssl_prefer_server_ciphers on;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_session_timeout 10m;
    ssl_session_cache off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off; 
    # ...
}
```

## Create Diffie-Hellman parameters file
Create your own set of Diffie-Hellman (DH) parameters for the key exchange and don't use the parameters commonly distributed with your Linux distribution. Weak DH parameters were a reason for past issues, cf. [Logjam Attack](https://weakdh.org/).

You can create your own 4096 bit DH parameter set like this
```shell-session
$ openssl dhparam -out /etc/nginx/dhparam.pem 4096
```

## SSL Protocols
- Protocols older than v1.2 are [deprecated by IETF](https://datatracker.ietf.org/doc/rfc8996/)
- Good write up why not to use older TLS versions can be found [here](https://www.venafi.com/blog/why-its-dangerous-use-outdated-tls-security-protocols).

## Let the server decide on the cipher suite
As I harden the server and am possibly more upto date than some browsers out in the wild, my server knows best which ciphers to use. Therefore, use the following option
```nginx
ssl_prefer_server_ciphers on;
```

### Verification
You can use ``nmap`` with the ``ssl-enum-ciphers`` script. At least for Ubuntu it is already itegrated into the main packages:
```shell-session
$ nmap -6 --script ssl-enum-ciphers -p 443 blog.straubs.eu
Starting Nmap 7.80 ( https://nmap.org ) at 2022-01-16 21:42 CET
Nmap scan report for blog.straubs.eu (2a01:4f8:c2c:b754::1)
Host is up (0.036s latency).
Other addresses for blog.straubs.eu (not scanned): 116.203.209.149
rDNS record for 2a01:4f8:c2c:b754::1: vpn.straubs.eu

PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers: 
|   TLSv1.2: 
|     ciphers: 
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (ecdh_x25519) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
|     compressors: 
|       NULL
|     cipher preference: server
|_  least strength: A

Nmap done: 1 IP address (1 host up) scanned in 1.30 seconds
```

[SSL Labs](https://www.ssllabs.com/ssltest/index.html) is also a great utility to check the overall security hardening on a protocol and header level.

## Ciphers
An uptodate secure cipher list can be found add [syslink.pl](https://syslink.pl/cipherlist/).

## OSCP stabling
OSCP stapling is well explained in [this Mozilla tech-blog article](https://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/). Roughly spoken, the server you are connecting to also includes current evidence that its certificate is not revoked by the signing CA. Thus it reduces the round-trip time for the client as it would be the clients job to request this information at the CA.

### Verification
You can verify the proper functioning of your setup with openssl:
```shell-session
$ openssl s_client -connect blog.straubs.eu:443 -servername blog.straubs.eu -status 2>&1 | grep -i ocsp
```
Mind that you need to specify the servername option as well as the connection option if you use server name indication (SNI), e.g. if you host multiple domains on your server.
In case of success the output should look like this
```shell-session
OCSP response: 
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
```

## SSL Session Cache
As NGINX does not provide any means of regularly cleaning up the session and ticket cache, it is best to turn it off
```nginx
ssl_session_cache off;
ssl_session_tickets off; 
```
That's important because to correctly implement [forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy) (PFS) the short-term secret, i.e. unique session keys, must not fall in the hands of an attacker. Therefore, it must be deleted/swapped regularly otherwise PFS is undermined.
Besides disabling the caches at all, one could also regularly recreate the session ticket key (see [Stephan Herber - Sichere TLS Konfiguration mit NGINX](https://www.sherbers.de/sichere-tls-konfiguration-mit-nginx/)).

# Add security headers
My current defaults:
```nginx
server {
    # ...
    add_header Strict-Transport-Security "max-age=63072000; preload;";
    add_header X-Frame-Options "SAMEORIGIN";
    add_header Content-Security-Policy "default-src 'self'" always;
    # ...
}
```
## HSTS
- [Mozialla developer information](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
- Tells browser always to use secure, i.e. https, transport
- On the page for ``domain.tld`` you can also set ``includeSubDomains`` options. This enforces for all subdomains of your domain secure transport.
- If you also want to be preloaded as HTTPS only, you can submit your own domain to the Google operated service [hstspreload.org](https://hstspreload.org/).
## X-Frame-Options
- [Mozilla developer information](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)
- Indicates if a browser is allowed to render a page from this server in a frame, iframe, embed or object tag.
## Content Security Policy (CSP)
- [Mozilla developer information](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
- Helps to mitigate cross-site scripting
- Tells browsers which resource sources are acceptable to render this page
- Can request to report violations by providing a report-uri. For details see [Moziall developer information](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP#violation_report_syntax).
```nginx
add_header Content-Security-Policy "default-src 'self'; report-uri https://collector.straubs.eu/collector" always;
```
