Skip to content

Instantly share code, notes, and snippets.

@kennwhite
Last active August 29, 2015 14:06
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save kennwhite/25183c3f05266ee0ad7f to your computer and use it in GitHub Desktop.
Save kennwhite/25183c3f05266ee0ad7f to your computer and use it in GitHub Desktop.
CentOS, Red Hat, Amazon Linux nginx config: A+ SSL Labs rating w/ strong legacy compatibility
# Strong nginx config for SSL Labs rating A as of 3-2015
# Broad legacy compatibility including IE8, Android 2.3+, openssl 0.9.8 clients
# Blocks most bot scan IP probes.
#
# *** Assumes: _HOSTNAME_ is replaced ***
# *** Assumes: Diffie-Hellman parameters have been generated (see: dhparam below)
#
# Includes OCSP stapling, HSTS Strict Transport security,
# session resumption, legacy backwards compatibility (XP, Android 2.3-4.3)
#
# Requires nginx 1.6+ See: http://nginx.org/en/linux_packages.html, e.g.:
# $ rpm -ivh http://nginx.org/packages/rhel/6/noarch/RPMS/nginx-release-rhel-6-0.el6.ngx.noarch.rpm
# $ yum install nginx
#
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
root /usr/share/nginx/_HOSTNAME_;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
gzip on;
server_tokens off;
server_names_hash_bucket_size 64;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Required for some CAs with Intermediate trust chains
# ssl_trusted_certificate /etc/nginx/ssl/AddTrustExternalCARoot.crt;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# Generate: openssl dhparam 2048 -out /etc/nginx/ssl/dhparam.pem
# Session Resumption
ssl_session_timeout 20m;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:20m;
# Enable OCSP stapling (req. nginx v 1.3.7+)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.4.4 8.8.8.8 valid=300s;
resolver_timeout 10s;
# For additional CORS DENY, ALLOW, SAMEORIGIN, etc. options see: http://enable-cors.org/
add_header X-Frame-Options DENY;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
#
# Cipher suites enabled below. Here's the rationale:
#
# *TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
# Android 4.4.2, Chrome 39 OSX, FF 31.3 ESR Win7, FF 34 OSX, IE 11 Win 10
# Java 8b132, OpenSSL 1.0.1h, Yahoo crawler, Yandex
# ^-- HTTP/2-compatible [AEAD]
#
# TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
# IE 11 Win7-8.1, IE 11 Win Phone 8.1, Safari 6 IOS 6.0.1, Safari 7 IOS 7.1
# Safari 8 IOS 8, Safari 7 OSX 10.9
#
# TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
# Android 4.0 - 4.4.2, Bing crawler, Google crawler, IE7 Vista, IE 8-10 Win7
# IE Mobile 10 Win Phone 8.0, Java 7u25, Safari 5.1 OS 10.6.8
# Safari 6, 10.8.4
# ^-- strong, but lacks authentication (AEAD)
#
# TLS_RSA_WITH_AES_128_CBC_SHA
# Java 6, Android 2.3.x, OpenSSL 0.9.8y [DEPRECATED - included for legacy API/mobile support]
# ^-- removed from TLS 1.3 & HTTP/2 standards, lacks Forward Secrecy & authentication (AEAD)
#
# TLS_RSA_WITH_3DES_EDE_CBC_SHA
# IE 8/XP [DEPRECATED - ONLY add (to end) of ssl_ciphers list for legacy IE8/XP support]
# ^-- STRONGLY discouraged unless IE8 on XP is absolutely required
ssl_ciphers "ECDHE_RSA_AES128_GCM_SHA256 ECDHE_RSA_AES128_CBC_SHA256 ECDHE_RSA_AES_128_CBC_SHA RSA_AES_128_CBC_SHA";
client_max_body_size 16M;
# Block IP-based bot/scanner requests (ie, GETs lacking a domain/hostname)
server {
listen 80 default_server;
return 444;
}
server {
listen 443 ssl;
return 444;
}
server {
listen 80;
server_name _HOSTNAME_;
return 301 https://_HOSTNAME_;
}
server {
listen 443 ssl;
server_name _HOSTNAME_;
# Enable Strict Transport Security (HSTS) (SSL-only)
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
}
}
@PaulMoore2014
Copy link

Hey Kenn

Just to follow up on Twitter comments...

Few params have changed now.

ssl on; should be removed and "ssl" added to the end of the listen line.
X-Frame isn't long for this world.
The dhparam is only sufficient for an 80% Qualys. You need 4096 for 100%, likewise with the key.
SPDY 2 support would speed things up, or upgrade to NGINX 1.6.x for SPDY 3.1+

Our configs do 100/95/100/90 A+ with PCI/FIPS supported.
https://www.ssllabs.com/ssltest/analyze.html?d=ramblingrant.co.uk

@kennwhite
Copy link
Author

Hi Paul,

Thanks so much for the feedback. ssl parameter updated. On X-Frame, I know about http://tools.ietf.org/html/rfc7034, but for the time being it looks like it helps more than it hurts, see e.g.,
https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet#Defending_with_X-Frame-Options_Response_Headers and https://frederik-braun.com/xfo-clickjacking.pdf.

On the DH parameters, I believe when we tested at 4096 bits, there were issues with some mainstream Java 7 APIs. As far as SPDY, I understood that it is still tagged experimental (http://nginx.org/en/docs/http/ngx_http_spdy_module.html), but I'm definitely interested in looking closer.

On the Qualsys SSL Labs rating, w/ Nginx 1.6.2 this config is scoring: 100/95/90/90 A+ as of 22-Sep-2014.

Thanks again for the suggestions!

@ptudor
Copy link

ptudor commented Sep 23, 2014

So I've been A+ for my nginx-proxy for localhost Apache for a long time (though I may have removed HSTS in some places for self-signed certificates, to avoid "You cannot proceed because the website operator has requested heightened security for this domain" even when I have the certificates in my keychain. But here's comments, for the sake of everyone making the Internet better.

  1. I redirect to SSL as a transitionary method on a per-vhost basis, but what I'd point out here for your config is the $request_uri element in the 301. (308, someday.)
    set $force_https_redirect 0;
     if ($host = "www.gemmagps.com") {
       set $force_https_redirect 1;
    }
     if ($force_https_redirect = 1) {
       return 301 https://$host$request_uri;
    }
  1. mod_spdy for happiness in modern browsers...
     listen      172.17.17.17:443 ssl spdy;
     listen [2001:db8:dead:beef::c0:ffee]:443 ssl spdy;
  1. Just in case someone blindly copying this doesn't realize DENY could be SAMEORIGIN...
     add_header X-Frame-Options SAMEORIGIN;
  1. And I have this, "reuse SSL session parameters to avoid SSL handshakes for parallel and subsequent connections"
     ssl_session_cache   shared:SSL:10m;
  1. And I'll admit I've never seen a resolver defined in an nginx config, but all my resolv.confs use localhost with nearby alternates, only 4.2.2.2/4.2.2.4 or 8.8.8.8 if things are totally broke.

@kennwhite
Copy link
Author

Thanks Patrick,
On the uri redirect component, my particular use case requires a fairly tight timeout, and doesn't allow deep links into the app, but for more public content sites, you're right, this is probably a good practice.

For SPDY, it can be very useful, but (as of October 2014), Nginx still tags the extension as "experimental", and cautions caveat emptor: http://nginx.org/en/docs/http/ngx_http_spdy_module.html

The headers SAMEORIGIN directive (and DENY) is often a good idea, but either can be an issue for certain types of CORS implementations (which range from explicit ALLOWs for domain feeds, to sketchy jsonp, to outright hideous iFrame hacks). I've added a link for more background on CORS.

There is a session cache with a slightly longer reuse window on line 42. I debated more heavy comments, but I think the proper answer is a link to my blog post that's been in draft for several months. ;-)

On the resolver IPs, this is straight from the nginx docs: http://nginx.com/blog/improve-seo-https-nginx/. I think the idea is that it's a (slight) mitigation to casual DNS hijacks vis-a-vis public access points/hotspots that require a TOS or login. This is common in many hotel and cafes, and much better to be warned of cert mismatches I suppose.

Thanks for the review. It's much appreciated!

@therealmik
Copy link

Re resolver: I was looking through the resolver code and it uses 16 bits of random(3) for the ident and a single port to send requests. I raised this with nginx, and the response was:

Yes, the resolver in nginx isn't expected to be used in hostile
environments. Rather, it's to be used to talk to a real local DNS
server in a non-blocking way.

Which is reasonable - they wanted a workaround for the system resolver libraries blocking, but it also means that using it other than localhost (especially on an internet-facing interface) is a risk that shouldn't be encouraged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment