Vasos Koupparis

Steps to Improve Performance and Security of Nginx Web Server


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).

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

If you are using Docker:
$ apt-get -y update && apt-get -y upgrade && $ apt-get -y update nginx

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;
    access_log   logs/domain1.access.log  main;
    #server_tokens off;
    root         html;

    location ~ \.php$ {
      #server_tokens off;
NOTE: Restart nginx to verify the changes.


$ sudo nginx -s stop
$ sudo nginx -s start
$ sudo nginx -s reload
## 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;
# 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 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 *;
  if ($invalid_referer) {
    return   403;
# config to don't allow the browser to render the page inside an frame or iframe
# and avoid clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
add_header X-Frame-Options SAMEORIGIN;
# 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.
add_header X-XSS-Protection "1; mode=block";
# 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.
# currently suppoorted in IE > 8
# 'soon' on Firefox
add_header X-Content-Type-Options nosniff;
## 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;

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

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

Deny or allow a single IP:


deny; or

Deny or allow IP range:

deny; or

Deny All:

deny all;

Combined for specific location(admin area):

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

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

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

# cipher suite for backwards compatibility (IE6/WinXP)
# 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:

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

ssl_trusted_certificate /etc/letsencrypt/domain/live/chain.pem
# config to enable HSTS(HTTP Strict Transport Security)
# to avoid ssl stripping
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

Follow Me

Recent Post