NGINX Hardening: Achieving A+ Security & Performance

Improving your web security and performance starts with a solid foundation. I regularly use external online generators and verification tools to ensure my NGINX configuration meets the highest standards. This guide details my steps to achieve an A+ security rating and optimal performance settings.

1. Preparation and Configuration Structure

Before making any changes, it’s vital to check your core software versions:

root@web:~# dpkg -l | grep nginx-core
ii  nginx-core  1.18.0-6.1  amd64  nginx web/proxy server (standard version)

root@web:~# dpkg -l | grep openssl
ii  openssl  1.1.1k-1  amd64  Secure Sockets Layer toolkit - cryptographic utility

My NGINX setup follows the standard Debian structure, utilizing sites-enabled for live configurations and a conf.d directory to store global, reusable configuration snippets. This modularity is a core principle of good configuration management.

2. SSL/TLS Hardening via External Standards

The first step in establishing a robust TLS configuration is to consult the moz://a SSL Configuration Generator. By inputting my NGINX (1.18.0) and OpenSSL (1.1.1k) versions and selecting the Intermediate profile, I get a rock-solid, standards-compliant foundation.

I implement these core settings in the file /etc/nginx/conf.d/ssl.conf.

#
# default ssl configuration. Used the mozilla ssl configurator as template 
# See: https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&guideline=5.6
# 

ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions
ssl_session_tickets off;

# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;

Forward Secrecy, Cipher Preference, and CAA

To ensure Forward Secrecy and optimal key exchange, I generate a strong 4096-bit Diffie Hellman parameter file and explicitly define the ECDH curve order.

cd /etc/nginx
openssl dhparam -out dhparam.pem 4096
chmod 0600 dhparam.pem

I add these parameters to ssl.conf, along with setting server cipher preference.

ssl_dhparam /etc/nginx/dhparam.pem;
ssl_ecdh_curve X25519:secp521r1:secp384r1:prime256v1;
ssl_prefer_server_ciphers on;

# replace with the IP address of your resolver
resolver 127.0.0.1;

For an additional layer of security required for the SSL Labs test, I explicitly define which Certificate Authorities (CAs) are allowed to issue certificates for my domains using a CAA (Certification Authority Authorization) DNS record.

root@ns3:~# nsupdate -l
> zone jeanbruenn.info
> update add blog.jeanbruenn.info. 3600 IN CAA 0 issue "letsencrypt.org"
> update add blog.jeanbruenn.info. 3600 IN CAA 0 iodef "mailto:himself@jeanbruenn.info"
> send

HSTS Implementation and HTTP Redirect

To enforce HTTPS consistently and minimize the risk of protocol downgrade attacks, HTTP Strict Transport Security (HSTS) is mandatory. I use NGINX’s map directive to ensure the header is only sent over HTTPS.

# HSTS (ngx_http_headers_module is required) (63072000 seconds)
map $scheme $hsts_header {
    https   max-age=63072000;
}
add_header Strict-Transport-Security $hsts_header always;

Note on includeSubdomains and preload: Adding these arguments requires careful consideration, especially if you have multiple subdomains or domains. I recommend only adding them selectively to the parent domain, as advised by the RFC6797.

To ensure all traffic is automatically encrypted, I globally redirect HTTP (Port 80) to HTTPS (Port 443).

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    location / {
        return 301 https://$host$request_uri;
    }
}

Certificates (RSA and ECDSA)

I have converted my old RSA certificates to modern ECDSA ones using Certbot, which is now the recommended standard for enhanced security and performance.

certbot renew --key-type ecdsa --elliptic-curve secp384r1 --cert-name jeanbruenn.info --force-renewal
https://www.ssllabs.com/ssltest/analyze.html?d=blog.jeanbruenn.info
https://cryptcheck.fr/https/blog.jeanbruenn.info

3. Advanced Security Headers

Beyond TLS, configuring HTTP security headers is the next layer of defense against web attacks. I consolidate these headers into /etc/nginx/conf.d/headers.conf.

/etc/nginx/conf.d/headers.conf (security headers)

#
# securityheaders
#

# see: https://scotthelme.co.uk/hardening-your-http-response-headers/#x-frame-options
#      https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
#      https://infosec.mozilla.org/guidelines/web_security#x-frame-options
add_header X-Frame-Options "SAMEORIGIN" always;

# see: https://scotthelme.co.uk/hardening-your-http-response-headers/#x-content-type-options
#      https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
add_header X-Content-Type-Options "nosniff" always;

# see: https://scotthelme.co.uk/a-new-security-header-referrer-policy/
#      https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
add_header Referrer-Policy "no-referrer" always;

# see: https://scotthelme.co.uk/goodbye-feature-policy-and-hello-permissions-policy/
#      https://github.com/w3c/webappsec-permissions-policy/blob/main/permissions-policy-explainer.md
#      https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md
add_header Permissions-Policy "camera=(), microphone=()" always;

# see: https://scotthelme.co.uk/a-new-security-header-feature-policy/
#      https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
# For compatibility reasons, including Feature-Policy (the former header for
# Permission-Policy, as well.
add_header Feature-Policy "microphone 'none'" always;
https://securityheaders.com/?q=blog.jeanbruenn.info&hide=on&followRedirects=on

/etc/nginx/conf.d/headers.conf (mozilla observatory)

#
# mozilla observatory
#

# A setting of 0 disables this, and currently the observatory will reduce
# your points if you disable it. However, read the github issue - you want
# it disabled.
# see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
#      https://github.com/mozilla/http-observatory/issues/432
add_header X-XSS-Protection 0 always;

# see: https://scotthelme.co.uk/content-security-policy-an-introduction/
#      https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
#      https://developers.google.com/web/fundamentals/security/csp
# you REALLY want to check what this is doing BEFORE using it. 2nd link.
# add_header Content-Security-Policy "frame-ancestors 'none'" always;
https://observatory.mozilla.org/analyze/blog.jeanbruenn.info

4. Performance Tuning

While security is paramount, performance is critical for user experience. I use tools like Google PageSpeed Insights to identify bottlenecks. My initial check flagged the lack of text compression.

I resolved this by adding /etc/nginx/conf.d/compression.conf:

gzip on;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1400;
gzip_comp_level 3;

Tip: A gzip_min_length of 1400 bytes avoids wasting resources on files that benefit little from compression.

https://developers.google.com/speed/pagespeed/insights/?hl=en&url=https%3A%2F%2Fblog.jeanbruenn.info%2F

Verification and Results

Continuous verification is a key principle in devops. I use external tools as my observability mechanism to validate the configuration changes.

ToolFocusRanking (My Configuration)
Qualys SSL LabsSSL/TLS Configuration and VulnerabilitiesA+
CryptCheckCipher Strength and Key ExchangeA+
securityheaders.comHTTP Security HeadersA+
Mozilla ObservatoryOverall Web Security ScoreB+

Note: I’d especially recommend the links to Scott Helme’s page—in my opinion, Scott did an excellent job explaining the rationale behind these headers. I also checked Webbkoll—a page I stumbled upon—which, besides complaining about the missing CSP, also displays useful GDPR/DSGVO information. This tool confirmed that disabling X-XSS-Protection (setting it to 0) is the correct secure practice today, even if older checkers complain.

See Also
  • Webbkoll. Web Security and Privacy Checker. https://webbkoll.dataskydd.net/
  • Mozilla. SSL Configuration Generator (v5.6). https://ssl-config.mozilla.org/
  • RFC 6797. HTTP Strict Transport Security (HSTS). https://www.rfc-editor.org/rfc/rfc6797
  • Scott Helme. Security Header Deep Dive. https://scotthelme.co.uk/hardening-your-http-response-headers/
  • Qualys SSL Labs. SSL Server Test. https://www.ssllabs.com/ssltest/
  • CryptCheck. TLS Configuration Checker. https://cryptcheck.fr/
  • securityheaders.com Security Headers Checker. https://securityheaders.com/
  • Google. PageSpeed Insights. https://developers.google.com/speed/pagespeed/insights/

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.