Vasos Koupparis

Vasos Koupparis

Full Stack Web Developer – Devops Engineer

Steps to Improve Performance and Security of Nginx Web Server

Intoduction

Nginx is one of the most popular and fastest growing open source web servers in the world.
Compared to apache is more resource-friendly, it can be used as a web server, reverse proxy, load balancer, mail proxy and HTTP cache.

According to Gartner, misconfigurations is the reason for most breaches. Is critical to understand how to provision securely web servers as it will prevent misconfigurations and potential breaches or outages.

For this guide, we’ll go through several steps to improve Nginx security and performance.

Before we get started, I assume you have an Ununtu 16.0.4 server running with nginx installed.
If you do not, then follow this guide: Nginx getting started – Install 

I will also provide an easier way to play around with nginx security and performance for the purpose of this guide with Docker (here).

For your convenience, the final configuration file is (here).

Testing Environment

  • Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-1066-aws x86_64)
  • Nginx version: nginx/1.10.3

Keep Nginx up to date

$ sudo apt-get -y update
$ sudo apt-get -y upgrade
$ sudo apt-get -y update nginx

If you are using Docker:

 RUN apt-get -y update && apt-get -y upgrade && $ apt-get -y update nginx

Remove Unnecessary Modules

WIP

Remove Unnecessary backup files

WIP

Disable server_tokens Directive

Syntax: server_tokens on | off | build | string;
Default: server_tokens on;
Context: http, server, location

This nginx directive enables or disables emitting nginx version on:

  • error pages and
  • in the “Server” response header field.

In order to prevent attacks on our web server caused by known vulnerabilities of our version, we should not share this information with the world.

So, since the default is on , we should set it to off in the http, server or location block.

http {
  include    conf/mime.types;
  index    index.html index.htm index.php;

  default_type application/octet-stream;
  log_format   main '$remote_addr - $remote_user [$time_local]  $status '
    '"$request" $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';
  access_log   logs/access.log  main;
  sendfile     on;
  tcp_nopush   on;

  # don't send the nginx version number in error pages and Server header
  server_tokens off;

  server { # php/fastcgi
    listen       80;
    server_name  domain1.com www.domain1.com;
    access_log   logs/domain1.access.log  main;
    #server_tokens off;
    root         html;

    location ~ \.php$ {
      fastcgi_pass   127.0.0.1:1025;
      #server_tokens off;
    }
  }

NOTE: Restart nginx to verify the changes.

$ sudo nginx -s stop
$ sudo nginx -s start
or 
$ sudo nginx -s reload

Deny HTTP User Agents/ Bots

## Deny certain User-Agents (case insensitive)
## The ~* makes it case insensitive as opposed to just a ~
if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
    return 403;
}
# Add/ Remove agents/bots from the list below depending your preference
map $http_user_agent $limit_bots {
  default 0;
  ~*(google|Googlebot|bing|yandex|msnbot) 1;
  ~*(AltaVista|Slurp|BlackWidow|Bot|ChinaClaw|Custo|DISCo|Download|Demon|eCatch|EirGrabber|EmailSiphon|EmailWolf|SuperHTTP|Surfbot|WebWhacker) 1;
  ~*(Express|WebPictures|ExtractorPro|EyeNetIE|FlashGet|GetRight|GetWeb!|Go!Zilla|Go-Ahead-Got-It|GrabNet|Grafula|HMView|Go!Zilla|Go-Ahead-Got-It) 1;
  ~*(rafula|HMView|HTTrack|Stripper|Sucker|Indy|InterGET|Ninja|JetCar|Spider|larbin|LeechFTP|Downloader|tool|Navroad|NearSite|NetAnts|tAkeOut|WWWOFFLE) 1;
  ~*(GrabNet|NetSpider|Vampire|NetZIP|Octopus|Offline|PageGrabber|Foto|pavuk|pcBrowser|RealDownload|ReGet|SiteSnagger|SmartDownload|SuperBot|WebSpider) 1;
  ~*(Teleport|VoidEYE|Collector|WebAuto|WebCopier|WebFetch|WebGo|WebLeacher|WebReaper|WebSauger|eXtractor|Quester|WebStripper|WebZIP|Wget|Widow|Zeus) 1;
  ~*(Twengabot|htmlparser|libwww|Python|perl|urllib|scan|Curl|email|PycURL|Pyth|PyQ|WebCollector|WebCopy|webcraw) 1;
}

if ($limit_bots = 1) {
  return 403;
}

Setup Monitor Logs

# Format to use in log files
# $remote_addr        The remote host
# $remote_user        The authenticated user (if any)
# $time_local         The time of the access
# $request            The first line of the request
# $status             The status of the request
# $body_bytes_sent    The size of the server's response, in bytes
# $http_referer       The referrer URL, taken from the request's headers
# $http_user_agent    The user agent, taken from the request's headers
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

# Default log file
# (this is only used when you don't override access_log on a server{} level)
access_log /var/log/nginx/nginx.access.log main;
# Default error log file
# (this is only used when you don't override error_log on a server{} level)
error_log /var/log/nginx/nginx.error.log warn;

Prevent Image and other files Hotlinking

# Prevent hotlinking - To prevent people hotlinking to your files. One aspect is security:
# say you have sensitive material that you only want your direct visitors to see.

location ~ .(gif|png|jpg|doc|xls|pdf|html|htm|xlsx|docx|mp4|mov)$ {
  valid_referers none blocked ~.google. ~.bing. ~.yahoo. yourdomain.com *.yourdomain.com;
  if ($invalid_referer) {
    return   403;
}

Avoid Clickjacking Attack

# 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;

Enable the Cross-site scripting (XSS) filter

# 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";

Disable content-type sniffing on some browsers

# 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;

Mitigating Slow HTTP DoS Attack – Limit the Number of Connections by IP

## Reset lingering timed out connections, freeing ram. Deflect DDoS.
reset_timedout_connection on;

# Directive describes the zone, in which used to limit the number of connections per the
# defined key,in particular, the number of connections from a single IP address
# 1m can handle 32000 sessions with 32 bytes/session, set to 10m x 32000 session
limit_conn_zone $binary_remote_addr zone=perip:10m;

# Control maximum number of simultaneous connections for one session i.e. ###
# restricts the amount of connections from a single ip address ###
limit_conn perip 10;

Buffer Overflow Protection Set Buffer Size Limitations

## Start: Size Limits & Buffer Overflows ##
client_body_buffer_size       1K;
client_header_buffer_size     1k;
client_max_body_size          4g;
large_client_header_buffers 2 1k;
## END: Size Limits & Buffer Overflows ##

Allow Access To Specified Domain Only

## Only requests to our Host are allowed i.e. yourdomain.com
if ($host !~ ^(www.yourdomain.com|api.yourdomain.com)$ ) {
    return 444;
}

Limit IP clients access

Deny or allow a single IP:

deny 1.2.3.4; or
allow 1.2.3.4;

Deny or allow IP range:

deny 1.2.3.4/24; or
allow 1.2.3.4/24;

Deny All:

deny all;

Combined for specific location(admin area):

location ^~ /admin { 
    deny all; 
    allow 1.2.3.4; # home ip address
    allow 9.8.7.6; # office ip address
}

Disable Unwanted HTTP Methods

add_header Allow "GET, POST, HEAD" always;
if ( $request_method !~ ^(GET|POST|HEAD)$ ) {
return 405; 
}

SSL/TLS Configuration – Lets Encrypt

WIP

Disable SSL and only Enable TLS

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Disable weak cipher suites

# cipher suite for backwards compatibility (IE6/WinXP)
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
# means that the server will prefer to use ciphers specified in the ssl_ciphers
# directive over the ciphers preferred by clients.
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;

For backwards compatibility (IE6/WinXP) use:

ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA: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:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

Enable OCSP Stapling

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

ssl_trusted_certificate /etc/letsencrypt/domain/live/chain.pem

Redirect HTTP traffic to HTTPS

# 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
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

Implement Mod Security WAF

WIP