Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Best nginx configuration for improved security(and performance). Complete blog post here http://tautt.com/best-nginx-configuration-for-security/
# to generate your dhparam.pem file, run in the terminal
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
# read more here http://tautt.com/best-nginx-configuration-for-security/
# don't send the nginx version number in error pages and Server header
server_tokens off;
# config to don't allow the browser to render the page inside an frame or iframe
# and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
add_header X-Frame-Options SAMEORIGIN;
# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
# currently suppoorted in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx
# http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx
# 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";
# with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
# you can tell the browser that it can only download content from the domains you explicitly allow
# http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# https://www.owasp.org/index.php/Content_Security_Policy
# I need to change our application code so we can increase security by disabling 'unsafe-inline' 'unsafe-eval'
# directives for css and js(if you have inline css or js, you will need to keep it too).
# more: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://assets.zendesk.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://assets.zendesk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://assets.zendesk.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://assets.zendesk.com https://www.facebook.com https://s-static.ak.facebook.com https://tautt.zendesk.com; object-src 'none'";
# redirect all http traffic to https
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name .forgott.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name .forgott.com;
ssl_certificate /etc/nginx/ssl/star_forgott_com.crt;
ssl_certificate_key /etc/nginx/ssl/star_forgott_com.key;
# enable session resumption to improve https performance
# http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# enables server-side protection from BEAST attacks
# http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html
ssl_prefer_server_ciphers on;
# disable SSLv3(enabled by default since nginx 0.8.19) since it's less secure then TLS http://en.wikipedia.org/wiki/Secure_Sockets_Layer#SSL_3.0
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# ciphers chosen for forward secrecy and compatibility
# http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
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';
# enable ocsp stapling (mechanism by which a site can convey certificate revocation information to visitors in a privacy-preserving, scalable manner)
# http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
resolver 8.8.8.8 8.8.4.4;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/star_forgott_com.crt;
# config to enable HSTS(HTTP Strict Transport Security) https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
# to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping
# also https://hstspreload.org/
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
# ... the rest of your configuration
}
@ryankearney

This comment has been minimized.

Show comment
Hide comment
@ryankearney

ryankearney Jan 2, 2014

The Strict-Transport-Security header needs to be moved inside the http block with the ssl listen statement or you risk sending Strict-Transport-Security headers over HTTP sites you may also have configured on the server.

Additionally, the rewrite for the http server block should be a return 301 instead. See this Nginx documentation for more details: http://wiki.nginx.org/Pitfalls#Taxing_Rewrites

The Strict-Transport-Security header needs to be moved inside the http block with the ssl listen statement or you risk sending Strict-Transport-Security headers over HTTP sites you may also have configured on the server.

Additionally, the rewrite for the http server block should be a return 301 instead. See this Nginx documentation for more details: http://wiki.nginx.org/Pitfalls#Taxing_Rewrites

@plentz

This comment has been minimized.

Show comment
Hide comment
@plentz

plentz Jan 11, 2014

@ryankearney updated, thanks :)

Owner

plentz commented Jan 11, 2014

@ryankearney updated, thanks :)

@barrybingo

This comment has been minimized.

Show comment
Hide comment
@barrybingo

barrybingo Jan 11, 2014

ssl_session_timeout 5m; <- Should this appear twice in the config?

ssl_session_timeout 5m; <- Should this appear twice in the config?

@tzuryby

This comment has been minimized.

Show comment
Hide comment
@tzuryby

tzuryby Feb 12, 2014

@barrybingo that is probably a typo. once is just enough.

tzuryby commented Feb 12, 2014

@barrybingo that is probably a typo. once is just enough.

@altonius

This comment has been minimized.

Show comment
Hide comment
@altonius

altonius Feb 16, 2014

As you're already redirecting all HTTP traffic to HTTPS, have you looked into using SPDY to increase performance?

nginx 1.5.10 (mainline) has SPDY 3.1 support.

Also if you're running nginx as a proxy have you considered turning off the X-Powered-By header by adding the following line to the server config section.

proxy_hide_header X-Powered-By;

As you're already redirecting all HTTP traffic to HTTPS, have you looked into using SPDY to increase performance?

nginx 1.5.10 (mainline) has SPDY 3.1 support.

Also if you're running nginx as a proxy have you considered turning off the X-Powered-By header by adding the following line to the server config section.

proxy_hide_header X-Powered-By;

@RoldanLT

This comment has been minimized.

Show comment
Hide comment
@RoldanLT

RoldanLT Feb 19, 2014

How to force redirect all request to www. version?
Here's my current config:

server {
server_name domain.com www.domain.com;
return 301 https://www.domain.com$request_uri;
}

server {
listen 443 ssl spdy;
server_name www.domain.com;

domain.com to https://www.domain.com = Works
www.domain.com to https://www.domain.com = Works
https://domain.com to https://www.domain.com = Not working

How to force redirect all request to www. version?
Here's my current config:

server {
server_name domain.com www.domain.com;
return 301 https://www.domain.com$request_uri;
}

server {
listen 443 ssl spdy;
server_name www.domain.com;

domain.com to https://www.domain.com = Works
www.domain.com to https://www.domain.com = Works
https://domain.com to https://www.domain.com = Not working

@plentz

This comment has been minimized.

Show comment
Hide comment
@plentz

plentz Feb 20, 2014

@barrybingo not really, thanks!

Owner

plentz commented Feb 20, 2014

@barrybingo not really, thanks!

@johnantoni

This comment has been minimized.

Show comment
Hide comment

nice work

@vincentclee

This comment has been minimized.

Show comment
Hide comment
@vincentclee

vincentclee Apr 19, 2014

RoldanLT

ssltunnel.net to https://www.ssltunnel.net = Works
www.ssltunnel.net to https://www.ssltunnel.net = Works
https://ssltunnel.net to https://www.ssltunnel.net = Works

#ssltunnel.net
server {
        listen 80;
        server_name ssltunnel.net www.ssltunnel.net;
        rewrite ^ https://www.ssltunnel.net$request_uri? permanent;
}
server {
        listen 443 ssl spdy;
        server_name ssltunnel.net;
        rewrite ^ https://www.ssltunnel.net$request_uri? permanent;

        ssl on;
        ssl_certificate ssltunnel.net.crt;
        ssl_certificate_key ssltunnel.net.key;
}
server {
        listen 443 ssl spdy;
        server_name www.ssltunnel.net
...

RoldanLT

ssltunnel.net to https://www.ssltunnel.net = Works
www.ssltunnel.net to https://www.ssltunnel.net = Works
https://ssltunnel.net to https://www.ssltunnel.net = Works

#ssltunnel.net
server {
        listen 80;
        server_name ssltunnel.net www.ssltunnel.net;
        rewrite ^ https://www.ssltunnel.net$request_uri? permanent;
}
server {
        listen 443 ssl spdy;
        server_name ssltunnel.net;
        rewrite ^ https://www.ssltunnel.net$request_uri? permanent;

        ssl on;
        ssl_certificate ssltunnel.net.crt;
        ssl_certificate_key ssltunnel.net.key;
}
server {
        listen 443 ssl spdy;
        server_name www.ssltunnel.net
...
@RoldanLT

This comment has been minimized.

Show comment
Hide comment
@RoldanLT

RoldanLT Apr 20, 2014

Thanks vincentclee!
I'm using this now and it works :)

server {
listen 80;
server_name phcorner.net www.phcorner.net;
return 301 https://www.phcorner.net$request_uri;
}

server {
listen 443 ssl spdy;
server_name phcorner.net;
return 301 https://www.phcorner.net$request_uri;

    ssl on;
ssl_certificate /ssl-unified.crt;
ssl_certificate_key /net.key;

}

server {
listen 443 ssl spdy;
server_name www.phcorner.net;
...

Thanks vincentclee!
I'm using this now and it works :)

server {
listen 80;
server_name phcorner.net www.phcorner.net;
return 301 https://www.phcorner.net$request_uri;
}

server {
listen 443 ssl spdy;
server_name phcorner.net;
return 301 https://www.phcorner.net$request_uri;

    ssl on;
ssl_certificate /ssl-unified.crt;
ssl_certificate_key /net.key;

}

server {
listen 443 ssl spdy;
server_name www.phcorner.net;
...

@toddlahman

This comment has been minimized.

Show comment
Hide comment
@toddlahman

toddlahman Sep 13, 2014

The browser will only listen to the Strict-Transport-Security header if the connection was established via HTTPS. The first time the visitor connects to the website using HTTP, the visitor needs to be redirected using a 301 redirect. On subsequent requests, even it the connection uses HTTP, HSTS forces the browser to use HTTPS for connecting to a particular website instead of HTTP. Even if a user types in a http:// URL, the browser will automatically correct the URL and will connect to https://.

It appears the more correct way to implement HSTS in Nginx is as so:

map $scheme $hsts_header {
    https   max-age=31536000;
}

server {
    listen  80;
    listen  443 ssl;

    add_header Strict-Transport-Security $hsts_header;
}

http://trac.nginx.org/nginx/ticket/289

Most examples use the following to implement HSTS, which does not follow the RFC.

add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";

The browser will only listen to the Strict-Transport-Security header if the connection was established via HTTPS. The first time the visitor connects to the website using HTTP, the visitor needs to be redirected using a 301 redirect. On subsequent requests, even it the connection uses HTTP, HSTS forces the browser to use HTTPS for connecting to a particular website instead of HTTP. Even if a user types in a http:// URL, the browser will automatically correct the URL and will connect to https://.

It appears the more correct way to implement HSTS in Nginx is as so:

map $scheme $hsts_header {
    https   max-age=31536000;
}

server {
    listen  80;
    listen  443 ssl;

    add_header Strict-Transport-Security $hsts_header;
}

http://trac.nginx.org/nginx/ticket/289

Most examples use the following to implement HSTS, which does not follow the RFC.

add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";

@bbigras

This comment has been minimized.

Show comment
Hide comment
@bbigras

bbigras Sep 18, 2014

@toddlahman : Shouldn't we avoid to set the header for http (port 80) ?

An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.

https://tools.ietf.org/html/rfc6797

bbigras commented Sep 18, 2014

@toddlahman : Shouldn't we avoid to set the header for http (port 80) ?

An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.

https://tools.ietf.org/html/rfc6797

@tisdall

This comment has been minimized.

Show comment
Hide comment
@tisdall

tisdall Oct 15, 2014

In nginx >= 1.7.5 you should have (note the added "always"):

add_header Strict-Transport-Security max-age=31536000 always;

This forces the header to always be added regardless of the http code which is what is wanted for this type of header.

tisdall commented Oct 15, 2014

In nginx >= 1.7.5 you should have (note the added "always"):

add_header Strict-Transport-Security max-age=31536000 always;

This forces the header to always be added regardless of the http code which is what is wanted for this type of header.

@wyrmiyu

This comment has been minimized.

Show comment
Hide comment
@wyrmiyu

wyrmiyu Dec 2, 2014

I'm a bit curious, you seem to use ssl_trusted_certificate with the same wildcard-certificate that you use with ssl_certificate but Nginx documentation[1] specifies that ssl_trusted_certificate

Specifies a file with trusted CA certificates in the PEM format used to verify client certificates and OCSP responses if ssl_stapling is enabled.

The file should be a certificate bundle containing root and intermediate CAs.

[1] http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate

wyrmiyu commented Dec 2, 2014

I'm a bit curious, you seem to use ssl_trusted_certificate with the same wildcard-certificate that you use with ssl_certificate but Nginx documentation[1] specifies that ssl_trusted_certificate

Specifies a file with trusted CA certificates in the PEM format used to verify client certificates and OCSP responses if ssl_stapling is enabled.

The file should be a certificate bundle containing root and intermediate CAs.

[1] http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate

@chrisnew

This comment has been minimized.

Show comment
Hide comment
@chrisnew

chrisnew Dec 9, 2014

Use following ciphers to exclude RC4.

ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED";

chrisnew commented Dec 9, 2014

Use following ciphers to exclude RC4.

ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED";
@iamkingsleyf

This comment has been minimized.

Show comment
Hide comment
@iamkingsleyf

iamkingsleyf Jan 9, 2015

i am getting access denied error

i am getting access denied error

@paskal

This comment has been minimized.

Show comment
Hide comment
@paskal

paskal Jan 14, 2015

From my fork, you may want to include it as well:

# enabling Public Key Pinning Extension for HTTP (HPKP)
# https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning
# to generate use on of these:
# $ openssl rsa -in my-website.key -outform der -pubout | openssl dgst -sha256 -binary | base64
# $ openssl req -in my-website.csr -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | base64
# $ openssl x509 -in my-website.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | base64
add_header Public-Key-Pins 'pin-sha256="base64+info1="; max-age=31536000; includeSubDomains'; 

paskal commented Jan 14, 2015

From my fork, you may want to include it as well:

# enabling Public Key Pinning Extension for HTTP (HPKP)
# https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning
# to generate use on of these:
# $ openssl rsa -in my-website.key -outform der -pubout | openssl dgst -sha256 -binary | base64
# $ openssl req -in my-website.csr -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | base64
# $ openssl x509 -in my-website.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | base64
add_header Public-Key-Pins 'pin-sha256="base64+info1="; max-age=31536000; includeSubDomains'; 
@guedressel

This comment has been minimized.

Show comment
Hide comment
@guedressel

guedressel Mar 11, 2015

@chrisnew thanks for the RC4-less ciphers.

@chrisnew thanks for the RC4-less ciphers.

@doriancosentino

This comment has been minimized.

Show comment
Hide comment
@doriancosentino

doriancosentino Mar 16, 2015

hello!

more one question about this

when try access https://myip not redirect to my https://mysite.com

how to redirect my ip to domian name?

works http my ip to dimain but https not

thanks

hello!

more one question about this

when try access https://myip not redirect to my https://mysite.com

how to redirect my ip to domian name?

works http my ip to dimain but https not

thanks

@mhcerri

This comment has been minimized.

Show comment
Hide comment
@mhcerri

mhcerri Mar 18, 2015

If you add a single 'add_header' inside the server directive (in your case, for HSTS), it will override all the 'add_header's that you've defined outside.

mhcerri commented Mar 18, 2015

If you add a single 'add_header' inside the server directive (in your case, for HSTS), it will override all the 'add_header's that you've defined outside.

@bryfry

This comment has been minimized.

Show comment
Hide comment
@bryfry

bryfry Mar 24, 2015

EDH+aRSA is now considered weak http://imgur.com/C5fKr38

I updated my ciphers string to:

EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH RSA+AESGCM RSA+AES !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS

bryfry commented Mar 24, 2015

EDH+aRSA is now considered weak http://imgur.com/C5fKr38

I updated my ciphers string to:

EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH RSA+AESGCM RSA+AES !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS
@ericrange

This comment has been minimized.

Show comment
Hide comment

maybe you could add keepalive_timeout 70;
(http://nginx.org/en/docs/http/configuring_https_servers.html)

@tierra

This comment has been minimized.

Show comment
Hide comment
@tierra

tierra Jul 3, 2015

@bryfry - They're considered weak in your case because you're still using a common Diffie-Hellman group with your ciphers, not because the cipher itself is weak. You don't need to disable EDH+aRSA ciphers, you just need to generate a new unique DH group, and configure it with your web server:
https://weakdh.org/sysadmin.html

tierra commented Jul 3, 2015

@bryfry - They're considered weak in your case because you're still using a common Diffie-Hellman group with your ciphers, not because the cipher itself is weak. You don't need to disable EDH+aRSA ciphers, you just need to generate a new unique DH group, and configure it with your web server:
https://weakdh.org/sysadmin.html

@oliversalzburg

This comment has been minimized.

Show comment
Hide comment
@oliversalzburg

oliversalzburg Aug 14, 2015

Why are the unsafe- variants allowed in the CSP? Also, the URL for Google Analytics content is https://www.google-analytics.com now.

Why are the unsafe- variants allowed in the CSP? Also, the URL for Google Analytics content is https://www.google-analytics.com now.

@t1gor

This comment has been minimized.

Show comment
Hide comment
@t1gor

t1gor Sep 14, 2015

@oliversalzburg: +1 for google analytic url.

t1gor commented Sep 14, 2015

@oliversalzburg: +1 for google analytic url.

@AndrewBelt

This comment has been minimized.

Show comment
Hide comment
@AndrewBelt

AndrewBelt Oct 8, 2015

server_tokens off; should probably also go in the host on port 80, although I haven't checked if it actually responds with server tokens on the redirect.

server_tokens off; should probably also go in the host on port 80, although I haven't checked if it actually responds with server tokens on the redirect.

@rimhoffd

This comment has been minimized.

Show comment
Hide comment
@rimhoffd

rimhoffd Dec 25, 2015

More HSTS updates:
add_header Strict-Transport-Security "max-age=31622400; includeSubDomains; preload";

new requirements from Chromium team on getting in the preload list of browsers (and a core point of HSTS)
https://hstspreload.appspot.com/

More HSTS updates:
add_header Strict-Transport-Security "max-age=31622400; includeSubDomains; preload";

new requirements from Chromium team on getting in the preload list of browsers (and a core point of HSTS)
https://hstspreload.appspot.com/

@nkukard

This comment has been minimized.

Show comment
Hide comment
@nkukard

nkukard Jan 3, 2016

Having TLSv1 enabled fails the latest trustwave.com PCI scan btw.

nkukard commented Jan 3, 2016

Having TLSv1 enabled fails the latest trustwave.com PCI scan btw.

@lvjurz

This comment has been minimized.

Show comment
Hide comment
@lvjurz

lvjurz Feb 2, 2016

Hi,

you should not use '$host' variable in redirect, because it will redirect you to anything that user/attacker will ask for 'someevilhost.com' in this case. Instead make sure you set your server_name correctly (i.e. myhost.com) and then use '$server_name' instead. Alternatively you can use FQDN in redirect and append $request_uri ('return 301 https://myhost.com$request_uri;').

Below is example of what happens when $host is used.

Request

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /login HTTP/1.0
Host: someevilhost.com

Response:

HTTP/1.1 301 Moved Permanently
Server: nginx/1.4.6 (Ubuntu)
Date: Tue, 02 Feb 2016 12:19:59 GMT
Content-Type: text/html
Content-Length: 193
Connection: close
Location: http://someevilhost.com/login

lvjurz commented Feb 2, 2016

Hi,

you should not use '$host' variable in redirect, because it will redirect you to anything that user/attacker will ask for 'someevilhost.com' in this case. Instead make sure you set your server_name correctly (i.e. myhost.com) and then use '$server_name' instead. Alternatively you can use FQDN in redirect and append $request_uri ('return 301 https://myhost.com$request_uri;').

Below is example of what happens when $host is used.

Request

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /login HTTP/1.0
Host: someevilhost.com

Response:

HTTP/1.1 301 Moved Permanently
Server: nginx/1.4.6 (Ubuntu)
Date: Tue, 02 Feb 2016 12:19:59 GMT
Content-Type: text/html
Content-Length: 193
Connection: close
Location: http://someevilhost.com/login

@MichaeMimouni

This comment has been minimized.

Show comment
Hide comment
@MichaeMimouni

MichaeMimouni Feb 5, 2016

This param

Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits

ssl_dhparam /etc/nginx/ssl/dhparam.pem;
Set to 2048 is very cpu consumer.
On an octo-core Intel(R) Xeon(R) CPU X3450 @ 2.67GHz, 4 nginx workers all at 100% cpu.
When removing the ssl_dhparam, the cpu is used normally.
I think it could simply be lower than 2048.

This param

Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits

ssl_dhparam /etc/nginx/ssl/dhparam.pem;
Set to 2048 is very cpu consumer.
On an octo-core Intel(R) Xeon(R) CPU X3450 @ 2.67GHz, 4 nginx workers all at 100% cpu.
When removing the ssl_dhparam, the cpu is used normally.
I think it could simply be lower than 2048.

@ashabada

This comment has been minimized.

Show comment
Hide comment
@ashabada

ashabada Feb 17, 2016

It will only consume 100% cpu while generating 'dhparam.pem'.

Also "All versions of nginx as of 1.4.4 rely on OpenSSL for input parameters to Diffie-Hellman (DH). Unfortunately, this means that Ephemeral Diffie-Hellman (DHE) will use OpenSSL's defaults, which include a 1024-bit key for the key-exchange. Since we're using a 2048-bit certificate, DHE clients will use a weaker key-exchange than non-ephemeral DH clients. We need to generate a stronger DHE parameter."

There is a great post explaining various settings indepth https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

It will only consume 100% cpu while generating 'dhparam.pem'.

Also "All versions of nginx as of 1.4.4 rely on OpenSSL for input parameters to Diffie-Hellman (DH). Unfortunately, this means that Ephemeral Diffie-Hellman (DHE) will use OpenSSL's defaults, which include a 1024-bit key for the key-exchange. Since we're using a 2048-bit certificate, DHE clients will use a weaker key-exchange than non-ephemeral DH clients. We need to generate a stronger DHE parameter."

There is a great post explaining various settings indepth https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

@gertvdijk

This comment has been minimized.

Show comment
Hide comment
@gertvdijk

gertvdijk Feb 17, 2016

As indicated by @mhcerri, the add_header directives in the parent scope are not effective for HTTPS requests.

Please see my blog post for a further explanation: Nginx add_header configuration pitfall.

As indicated by @mhcerri, the add_header directives in the parent scope are not effective for HTTPS requests.

Please see my blog post for a further explanation: Nginx add_header configuration pitfall.

@jeremysears

This comment has been minimized.

Show comment
Hide comment
@jeremysears

jeremysears Mar 21, 2016

This configuration uses very weak protocols and cipher suites. I recommend using this generator to get the latest recommended NGINX configs:
https://mozilla.github.io/server-side-tls/ssl-config-generator/

This configuration uses very weak protocols and cipher suites. I recommend using this generator to get the latest recommended NGINX configs:
https://mozilla.github.io/server-side-tls/ssl-config-generator/

@wellington1993

This comment has been minimized.

Show comment
Hide comment
@wellington1993

wellington1993 Mar 30, 2016

Thanks in a lot!

Thanks in a lot!

@conradwt

This comment has been minimized.

Show comment
Hide comment
@conradwt

conradwt May 6, 2016

@ryankearney Should all these header statements be added to the sever block because it seems that they are causing issues for other server specific files?

conradwt commented May 6, 2016

@ryankearney Should all these header statements be added to the sever block because it seems that they are causing issues for other server specific files?

@wget

This comment has been minimized.

Show comment
Hide comment
@wget

wget May 10, 2016

@conradwt I'm experiencing the same issues. When either doing a test either with nginx -t -c /my/nginx.conf/ or by restarting nginx, the latter complains with error message like:

"server_tokens" directive is not allowed here in /my/nginx.conf
"add_header" directive is not allowed here in /my/nginx.conf

wget commented May 10, 2016

@conradwt I'm experiencing the same issues. When either doing a test either with nginx -t -c /my/nginx.conf/ or by restarting nginx, the latter complains with error message like:

"server_tokens" directive is not allowed here in /my/nginx.conf
"add_header" directive is not allowed here in /my/nginx.conf

@rgeraads

This comment has been minimized.

Show comment
Hide comment
@dscso

This comment has been minimized.

Show comment
Hide comment
@dscso

dscso Jun 10, 2016

You should also listen on IPv6...

listen [::]:443 ssl;

dscso commented Jun 10, 2016

You should also listen on IPv6...

listen [::]:443 ssl;
@fotonobile

This comment has been minimized.

Show comment
Hide comment
@Revokee

This comment has been minimized.

Show comment
Hide comment
@Revokee

Revokee Jun 30, 2016

@fotonobile actually you need to update your OpenSSL to fix that (I had to restart my server as well)

Revokee commented Jun 30, 2016

@fotonobile actually you need to update your OpenSSL to fix that (I had to restart my server as well)

@morsik

This comment has been minimized.

Show comment
Hide comment
@morsik

morsik Jul 2, 2016

Just included those values inside my nginx 1.11 config.
add_header doesn't works when inside http section (but docs says it should, and nginx doesn't tell me bad things about this, so strange). Moved into server section caused to serve those headers correctly.

Strange.

morsik commented Jul 2, 2016

Just included those values inside my nginx 1.11 config.
add_header doesn't works when inside http section (but docs says it should, and nginx doesn't tell me bad things about this, so strange). Moved into server section caused to serve those headers correctly.

Strange.

@sander

This comment has been minimized.

Show comment
Hide comment
@sander

sander Jul 15, 2016

@morsik did you have additional add_headers in server?

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.
http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header

sander commented Jul 15, 2016

@morsik did you have additional add_headers in server?

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.
http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header

@rxaviers

This comment has been minimized.

Show comment
Hide comment
@sgnl

This comment has been minimized.

Show comment
Hide comment
@sgnl

sgnl Oct 30, 2016

Internet Archive for http://tautt.com/best-nginx-configuration-for-security
here: http://web.archive.org/web/20151011140918/http://tautt.com/best-nginx-configuration-for-security

sgnl commented Oct 30, 2016

Internet Archive for http://tautt.com/best-nginx-configuration-for-security
here: http://web.archive.org/web/20151011140918/http://tautt.com/best-nginx-configuration-for-security

@snoek09

This comment has been minimized.

Show comment
Hide comment
@snoek09

snoek09 Dec 6, 2016

ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA and DES-CBC3-SHA are weak ciphers.

snoek09 commented Dec 6, 2016

ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA and DES-CBC3-SHA are weak ciphers.

@tardis40

This comment has been minimized.

Show comment
Hide comment
@tardis40

tardis40 Dec 26, 2016

why no HPKP? why no dual SSL boot? why no ECDSA certificate with ssl_ecdsa_curve? the set of suites are quite weak, I'm concerned about bulk block mod CBC here: ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA

here's what I have:

`# HTTPS server
server {
listen 443 ssl;

    server_name fortestingpurposesonly.xyz;
    ssl on;

    ssl_certificate      nginx_rsa.crt;
    ssl_certificate_key  server.key;

    ssl_certificate blablabla.crt;
    ssl_certificate_key ec.key;

    add_header Strict-Transport-Security max-age=63072000;
    add_header Public-Key-Pins 'pin-sha256=\"RyVHLvC/L9OizFGYr+ujdCAdQL2vpmbquWSVwagQOzE=\"; pin-sha256=\"EohwrK1N7rr3bRQphPj4j2cel+B2d0NNbM9PWHNDXpM=\"; max-age=25920; includeSubDomains';
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ecdh_curve secp384r1:secp256k1;

    ssl_protocols TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:!aNULL:!eNULL:!LOW:!3DES:!MD5:!CBC; `

And here's OCSP stapling, you know that im sure:
`
#OCSP Stapling

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /root/ssltest/ec.ca-bundle;
resolver 8.8.8.8 8.8.4.4; `

this is more like it, imo.
sorry my formatting

tardis40 commented Dec 26, 2016

why no HPKP? why no dual SSL boot? why no ECDSA certificate with ssl_ecdsa_curve? the set of suites are quite weak, I'm concerned about bulk block mod CBC here: ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA

here's what I have:

`# HTTPS server
server {
listen 443 ssl;

    server_name fortestingpurposesonly.xyz;
    ssl on;

    ssl_certificate      nginx_rsa.crt;
    ssl_certificate_key  server.key;

    ssl_certificate blablabla.crt;
    ssl_certificate_key ec.key;

    add_header Strict-Transport-Security max-age=63072000;
    add_header Public-Key-Pins 'pin-sha256=\"RyVHLvC/L9OizFGYr+ujdCAdQL2vpmbquWSVwagQOzE=\"; pin-sha256=\"EohwrK1N7rr3bRQphPj4j2cel+B2d0NNbM9PWHNDXpM=\"; max-age=25920; includeSubDomains';
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ecdh_curve secp384r1:secp256k1;

    ssl_protocols TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:!aNULL:!eNULL:!LOW:!3DES:!MD5:!CBC; `

And here's OCSP stapling, you know that im sure:
`
#OCSP Stapling

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /root/ssltest/ec.ca-bundle;
resolver 8.8.8.8 8.8.4.4; `

this is more like it, imo.
sorry my formatting

@petarov

This comment has been minimized.

Show comment
Hide comment
@petarov

petarov Jan 11, 2017

dhparam.pem file generation may take a decent amount of time, especially on RPis.

One solution is to use DSA rather than DH parameters, converted to DH format.

openssl dhparam -dsaparam -out /etc/nginx/ssl/dhparam.pem 2048

petarov commented Jan 11, 2017

dhparam.pem file generation may take a decent amount of time, especially on RPis.

One solution is to use DSA rather than DH parameters, converted to DH format.

openssl dhparam -dsaparam -out /etc/nginx/ssl/dhparam.pem 2048
@grandcat

This comment has been minimized.

Show comment
Hide comment
@grandcat

grandcat Jan 23, 2017

Regarding automatic redirect and IPv6:
Someone might want to add listen [::]:80 ipv6only=on; so Nginx correctly forwards its IPv6 visitors.

So, the complete redirect block would look like this:

# redirect all http traffic to https
server {
  listen 80;
  listen [::]:80 ipv6only=on;
  server_name .forgott.com;
  return 301 https://$host$request_uri;
}

Same applies to the other server block handling the real requests:

server {
  listen 443 ssl default deferred;
  listen [::]:443 ssl ipv6only=on;
  server_name .forgott.com;
  # ...
}

grandcat commented Jan 23, 2017

Regarding automatic redirect and IPv6:
Someone might want to add listen [::]:80 ipv6only=on; so Nginx correctly forwards its IPv6 visitors.

So, the complete redirect block would look like this:

# redirect all http traffic to https
server {
  listen 80;
  listen [::]:80 ipv6only=on;
  server_name .forgott.com;
  return 301 https://$host$request_uri;
}

Same applies to the other server block handling the real requests:

server {
  listen 443 ssl default deferred;
  listen [::]:443 ssl ipv6only=on;
  server_name .forgott.com;
  # ...
}
@C0nw0nk

This comment has been minimized.

Show comment
Hide comment
@C0nw0nk

C0nw0nk Mar 1, 2017

Why do you not enable http2

listen 443 ssl http2 default deferred; #IPv4
listen [::]:443 ssl http2 default deferred; #IPv6

C0nw0nk commented Mar 1, 2017

Why do you not enable http2

listen 443 ssl http2 default deferred; #IPv4
listen [::]:443 ssl http2 default deferred; #IPv6
@fedulovivan

This comment has been minimized.

Show comment
Hide comment
@fedulovivan

fedulovivan Mar 7, 2017

+1 for users @mhcerri @gertvdijk @sander who already highlighted above an issue with overriding add_headers directive

fedulovivan commented Mar 7, 2017

+1 for users @mhcerri @gertvdijk @sander who already highlighted above an issue with overriding add_headers directive

@jspiro

This comment has been minimized.

Show comment
Hide comment
@jspiro

jspiro Mar 15, 2017

Has anyone built a new version of this with the recommendations above?

jspiro commented Mar 15, 2017

Has anyone built a new version of this with the recommendations above?

@C0nw0nk

This comment has been minimized.

Show comment
Hide comment
@C0nw0nk

C0nw0nk Mar 23, 2017

OWASP recommendations are more up to date.

https://www.owasp.org/index.php/SCG_WS_nginx#SSL_Module

Also your resolver should be more like this.

resolver 8.8.8.8 8.8.4.4 valid=300s; #Cache resolver

C0nw0nk commented Mar 23, 2017

OWASP recommendations are more up to date.

https://www.owasp.org/index.php/SCG_WS_nginx#SSL_Module

Also your resolver should be more like this.

resolver 8.8.8.8 8.8.4.4 valid=300s; #Cache resolver
@plentz

This comment has been minimized.

Show comment
Hide comment
@plentz

plentz Jul 2, 2017

@jeremysears thanks, I will update the protocols and cipher suites. It's been a while since the last time I've updated this gist

Owner

plentz commented Jul 2, 2017

@jeremysears thanks, I will update the protocols and cipher suites. It's been a while since the last time I've updated this gist

@kravietz

This comment has been minimized.

Show comment
Hide comment
@kravietz

kravietz Jul 5, 2017

I believe https://cipherli.st/ is also quite an up to date resource on Nginx configuration.

kravietz commented Jul 5, 2017

I believe https://cipherli.st/ is also quite an up to date resource on Nginx configuration.

@benscobie

This comment has been minimized.

Show comment
Hide comment

Mozilla also provide an SSL config generator: https://mozilla.github.io/server-side-tls/ssl-config-generator/

@ks228

This comment has been minimized.

Show comment
Hide comment
@ks228

ks228 Aug 24, 2017

I have paid 99 dollars for this year on some expensive ssl from godaddy. I wish I knew this one earlier. Your tutorial along with let's crypt ssl will help me a lot now
https://infoginx.com/ig-securing-nginx-server-lets-encrypt-ubuntu/

ks228 commented Aug 24, 2017

I have paid 99 dollars for this year on some expensive ssl from godaddy. I wish I knew this one earlier. Your tutorial along with let's crypt ssl will help me a lot now
https://infoginx.com/ig-securing-nginx-server-lets-encrypt-ubuntu/

@ghprod

This comment has been minimized.

Show comment
Hide comment
@ghprod

ghprod Sep 4, 2017

This is great!

Thanks 👍

ghprod commented Sep 4, 2017

This is great!

Thanks 👍

@stepan-i

This comment has been minimized.

Show comment
Hide comment
@stepan-i

stepan-i Sep 15, 2017

Your blogpost link is broken! :( I've added this GIST to https://github.com/wallarm/awesome-nginx-security

Your blogpost link is broken! :( I've added this GIST to https://github.com/wallarm/awesome-nginx-security

@heisGarvit

This comment has been minimized.

Show comment
Hide comment
@heisGarvit

heisGarvit Oct 7, 2017

@MichaeMimouni try adding the -dsaparam option.

openssl dhparam -dsaparam -out /etc/ssl/private/dhparam.pem 4096

as answered here https://security.stackexchange.com/a/95184

@MichaeMimouni try adding the -dsaparam option.

openssl dhparam -dsaparam -out /etc/ssl/private/dhparam.pem 4096

as answered here https://security.stackexchange.com/a/95184

@1985a

This comment has been minimized.

Show comment
Hide comment
@1985a

1985a Nov 3, 2017

Hi there. Thanks you for sharing this. I wish I found this before. I was able to get this by using a configuration like this one.
img

1985a commented Nov 3, 2017

Hi there. Thanks you for sharing this. I wish I found this before. I was able to get this by using a configuration like this one.
img

@jult

This comment has been minimized.

Show comment
Hide comment

jult commented Nov 16, 2017

FYI, here's my include for TLS/SSL and nginx: https://gist.github.com/jult/395ad9fd3e9773a54a67aaf689beab27

@Rotzbua

This comment has been minimized.

Show comment
Hide comment
@Rotzbua

Rotzbua Nov 23, 2017

There is a mistake: includeSubdomains should be includeSubDomains
The header should not be processed case sensitive but to match the RFC https://tools.ietf.org/html/rfc6797 it would be good to use that form.

Rotzbua commented Nov 23, 2017

There is a mistake: includeSubdomains should be includeSubDomains
The header should not be processed case sensitive but to match the RFC https://tools.ietf.org/html/rfc6797 it would be good to use that form.

@knoxcard

This comment has been minimized.

Show comment
Hide comment
@knoxcard

knoxcard Nov 29, 2017

Look at this: https://scotthelme.co.uk/a-new-security-header-referrer-policy/

add this your nginx.conf file ...

Referrer Policy

add_header Referrer-Policy "no-referrer";

Update nginx configuration: nginx -s reload

Then test here: https://securityheaders.io/

knoxcard commented Nov 29, 2017

Look at this: https://scotthelme.co.uk/a-new-security-header-referrer-policy/

add this your nginx.conf file ...

Referrer Policy

add_header Referrer-Policy "no-referrer";

Update nginx configuration: nginx -s reload

Then test here: https://securityheaders.io/

@katguzmann

This comment has been minimized.

Show comment
Hide comment
@katguzmann

katguzmann Feb 2, 2018

How does the CSP for Google Analytics work on a separate file, how should be look like?
I'm having a hard time trying to figure this out, since a lot of sources say just to add 'unsafe-inline' on the script-src just to support Google Analytics, but it seems to be very unsafe.

Should I just create an analytics.conf and add the following:

script-src 'self' https://www.google-analytics.com;
img-src https://www.google-analytics.com www.google-analytics.com https://stats.g.doubleclick.net;
connect-src https://www.google-analytics.com www.google-analytics.com https://stats.g.doubleclick.net

and then, on the nginx.conf:
default-src 'none';script-src 'self' www.google-analytics.com;img-src www.google-analytics.com;

Also, I'm very confused about how the config file for nginx should look like, I have the present reference and this one:

<FilesMatch "\.(html|php)$">
Header set Content-Security-Policy "policy-definition"
</FilesMatch>

I'm not a back-end developer, and this is the first time that I create a CSP, so I'll be very grateful for any help!

How does the CSP for Google Analytics work on a separate file, how should be look like?
I'm having a hard time trying to figure this out, since a lot of sources say just to add 'unsafe-inline' on the script-src just to support Google Analytics, but it seems to be very unsafe.

Should I just create an analytics.conf and add the following:

script-src 'self' https://www.google-analytics.com;
img-src https://www.google-analytics.com www.google-analytics.com https://stats.g.doubleclick.net;
connect-src https://www.google-analytics.com www.google-analytics.com https://stats.g.doubleclick.net

and then, on the nginx.conf:
default-src 'none';script-src 'self' www.google-analytics.com;img-src www.google-analytics.com;

Also, I'm very confused about how the config file for nginx should look like, I have the present reference and this one:

<FilesMatch "\.(html|php)$">
Header set Content-Security-Policy "policy-definition"
</FilesMatch>

I'm not a back-end developer, and this is the first time that I create a CSP, so I'll be very grateful for any help!

@aptalca

This comment has been minimized.

Show comment
Hide comment
@aptalca

aptalca Feb 6, 2018

An nginx block only inherits add_header directives from the upper level if there are no add_header directives set in that block.

Once you add the add_header directive for the HSTS in the server block, it will ignore the four add_header directives you set in the http block above.

aptalca commented Feb 6, 2018

An nginx block only inherits add_header directives from the upper level if there are no add_header directives set in that block.

Once you add the add_header directive for the HSTS in the server block, it will ignore the four add_header directives you set in the http block above.

@colinmollenhour

This comment has been minimized.

Show comment
Hide comment
@colinmollenhour

colinmollenhour May 1, 2018

The ECDHE-RSA-CHACHA20-POLY1305 cipher is non-compliant with NIST and therefore HIPAA.

The ECDHE-RSA-CHACHA20-POLY1305 cipher is non-compliant with NIST and therefore HIPAA.

@AdrianLThomas

This comment has been minimized.

Show comment
Hide comment
@AdrianLThomas

AdrianLThomas May 31, 2018

A referrer policy should be added in to here too. Without this header, you will still get flagged up on securityheaders.com

https://scotthelme.co.uk/a-new-security-header-referrer-policy/

A referrer policy should be added in to here too. Without this header, you will still get flagged up on securityheaders.com

https://scotthelme.co.uk/a-new-security-header-referrer-policy/

@neilwashere

This comment has been minimized.

Show comment
Hide comment
@neilwashere

neilwashere Jun 6, 2018

Thanks for making this available. Very useful resource 👍

Thanks for making this available. Very useful resource 👍

@GaProgMan

This comment has been minimized.

Show comment
Hide comment
@GaProgMan

GaProgMan Jul 12, 2018

This is an absolutely amazing resource. Thank you for creating and sharing it 👍

This is an absolutely amazing resource. Thank you for creating and sharing it 👍

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