Nginx is a fast, lightweight web server/reverse proxy and e-mail (IMAP/POP3) proxy. It is compatible with UNIX, GNU/Linux, BSD derivatives, Mac OS X, Solaris, and Microsoft Windows.  Nginx is one of a few servers created to address the C10K problem (The C10k problem is the problem of optimizing network sockets to handle a large number of clients at the same time). Nginx, unlike traditional servers, does not use threads to handle requests. Instead, it employs an event-driven (asynchronous) architecture that is far more scalable. Nginx is the backbone of several high-traffic websites.

Nginx Config file:

When nginx starts, the first file it reads is /etc/nginx/nginx.conf. This file is maintained by Nginx package maintainers, and administrators should avoid editing it unless they also follow changes made by upstream. User should use /etc/nginx/conf.d/ folder to create new config for nginx. Files inside /etc/nginx are given below:

Sample nginx file /etc/nginx/conf.d/sample.conf:

HTTP:

server{	
	listen 80;
	listen [::]:80;
    server_name website.com;
    
	#logging
	access_log /var/log/nginx/website.log;
	error_log /var/log/nginx/website.error.log warn;

	#reverse proxy
	location / {
		proxy_pass http://127.0.0.1:$port;
        #setting client body size default=1m
     	client_max_body_size 5m;
	}
}

HTTPS:

server {
    listen       80;
    listen       [::]:80;
    server_name  website.com www.website.com;
    return 301 https://www.website.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name  website.com www.website.com;
	
	#SSL
	ssl_certificate     /etc/letsencrypt/live/website/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/website/privkey.pem;
    
    #redirect non www to www with HTTPS
    if ($host = 'website.com') {
        return 301 https://www.website.com$request_uri;
    }
     #reverse proxy
     location / {
     	  proxy_pass http://127.0.0.1:$port;
          #setting client body size default=1m
     	  client_max_body_size 5m;
     }
}  

Check config in nginx:

/usr/local/nginx/sbin/nginx -t 
or
nginx -t

Sample Output:

the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
configuration file /usr/local/nginx/conf/nginx.conf test is successful

Nginx Security Headers:

HTTP security headers are a critical component of website security because they protect you from various types of attacks such as XSS, SQL injection, clickjacking, and so on. Below are some of the header to be using in config.

HTTP Strict Transport Security (HSTS)

This header, which is also declared by Strict-Transport-Security, instructs a user agent to only use HTTPs connections. This prevents web browsers from connecting to web servers using non-HTTPS connections. Currently, HTTP strict transport security is supported by all major web browsers.

add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';

Content-Security-Policy (CSP)

The Content-Security-Policy header improves on the X-XSS-Protection header and adds an extra layer of security. It is a very powerful header designed to protect against XSS and data injection attacks. CSP instructs browser to load only allowed content from the website. All major browsers currently offer full or partial support for content security policy.

set $DEFAULT "default-src 'self' *.example.com";
set $OBJECT "object-src 'self'";
set $BASE_URI "base-uri 'self'";
set $STYLE "style-src 'self' 'unsafe-inline' *.example.com";
set $IMG "img-src 'self' data: *.example.com";
set $CONNECT "connect-src 'self' *.googleapis.com";
set $SCRIPT "script-src 'unsafe-eval' 'unsafe-inline' *.example.com";
set $FORM "form-action 'self' *.facebook.com";
set $FRAME "frame-src 'self'";
set $FRAMEANS "frame-ancestors 'self'";
add_header Content-Security-Policy "${DEFAULT}; ${OBJECT}; ${BASE_URI}; ${STYLE}; ${IMG}; ${CONNECT}; ${SCRIPT}; ${FORM}; ${FRAME}; ${FRAMEANS};" always;

Access-Control-Allow-Origin

CORS is an HTTP-header-based mechanism that allows a server to specify any origins (domain, scheme, or port) other than its own from which a browser should allow resources to be loaded. CORS also makes use of a mechanism that allows browsers to send a "preflight" request to the server hosting the cross-origin resource to ensure that the server will allow the actual request. The browser sends headers indicating the HTTP method and headers that will be used in the actual request during that preflight.

add_header 'Access-Control-Allow-Origin' 'https://website.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;

X-XSS-Protection

The X-XSS header, also known as the Cross Site Scripting header, is used to prevent Cross-Site Scripting attacks. In modern web browsers such as Chrome, Internet Explorer, and Safari, the XSS Filter is enabled by default. When reflected cross-site scripting (XSS) attacks are detected, this header prevents pages from loading. Depending on your needs, you can implement XSS protection using one of three methods.

  1. X-XSS-Protection: 0 : Disables the filter entirely.
  2. X-XSS-Protection: 1 : Enables the filter but only sanitizes potentially malicious scripts.
  3. X-XSS-Protection: 1; mode=block : Enables the filter and completely blocks the page.
add_header X-XSS-Protection "1; mode=block";

X-Frame-Options

By disabling iframes on your site, the X-Frame-Options header protects your website from clickjacking attacks. All major web browsers currently support it. This header instructs the browser not to embed your web page in a frame/iframe. X-Frame-Options can be configured in three ways:

  1. DENY: Completely disables iframe features.
  2. SAMEORIGIN: An iframe can only be used by someone from the same origin.
  3. ALLOW-FROM: Allows pages to be loaded into iframes only from specific URLs.
add_header X-Frame-Options "SAMEORIGIN";

X-Content-Type-Options

The x-content-type header, also known as "Browser Sniffing Protection," instructs the browser to use the MIME types specified in the header. It's used to keep web browsers like Internet Explorer and Google Chrome from sniffing responses that aren't of the declared Content-Type. The nosniff header does not protect against all sniffing vulnerabilities. There is also no valid value for this header other than nosniff.

add_header X-Content-Type-Options nosniff;

Referrer-Policy

The Referrer-Policy security header field identifies the URL of the webpage that requested the current page. The new webpage can determine where the request originated by inspecting the referrer. The Referrer-Policy can be set to prevent the browser from sending any URL information to the destination site.

add_header Referrer-Policy "strict-origin";
add_header Referrer-Policy "no-referrer";
add_header Referrer-Policy "no-referrer-when-downgrade";
add_header Referrer-Policy "origin";
add_header Referrer-Policy "origin-when-cross-origin";
add_header Referrer-Policy "same-origin";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Referrer-Policy "unsafe-url";

Permissions-Policy

The Permissions-Policy header is a new header that allows the site to control which APIs or browser features can be used.

add_header Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()";

X-Permitted-Cross-Domain-Policies

This header has the effect of allowing malicious requests from Adobe Flash or PDF documents. Set the X-Permitted-Cross-Domain-Policies header in server responses to none unless the application requires Adobe products. The threat is an unauthorized Internet attacker.

add_header X-Permitted-Cross-Domain-Policies "none"
add_header X-Permitted-Cross-Domain-Policies "all"
add_header X-Permitted-Cross-Domain-Policies  master-only;

Set-Cookie

The Set-Cookie HTTP response header is used to send a cookie from the server to the user agent, so that the user agent can send it back to the server later. To send multiple cookies, multiple Set-Cookie headers should be sent in the same response.

//Using multiple attributes in one line
add_header Set-Cookie "Path=/; HttpOnly; Secure";

//Attributes
Expires=<date>
Max-Age=<number>
Domain=<domain-value>
Path=<path-value>
Secure
HttpOnly
SameSite=Strict
SameSite=Lax
SameSite=None; Secure

Security Patches:

This patches are written in /etc/nginx/nginx.conf:

server_tokens

The directive server tokens' is in charge of displaying the NGINX version number and operating system version on error pages as well as in the 'Server' HTTP response header field. This data should not be displayed.

http {
	server_tokens off;
}

Controlling Buffer Overflow Attacks

Edit and configure the buffer size limits for all clients as follows:

client_body_buffer_size  1K;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;
  1. client_body_buffer_size 1k – (default is 8k or 16k) The directive specifies the client request body buffer size.
  2. client_header_buffer_size 1k – Directive sets the headerbuffer size for the request header from client. For the overwhelming majority of requests a buffer size of 1K is sufficient. Increase this if you have a custom header or a large cookie sent from the client (e.g., wap client).
  3. client_max_body_size 1k– Directive assigns the maximum accepted body size of client request, indicated by the line Content-Length in the header of request. If size is greater the given one, then the client gets the error “Request Entity Too Large” (413). Increase this when you are getting file uploads via the POST method.
  4. large_client_header_buffers 2 1k – Directive assigns the maximum number and size of buffers for large headers to read from client request. By default the size of one buffer is equal to the size of page, depending on platform this either 4K or 8K, if at the end of working request connection converts to state keep-alive, then these buffers are freed. 2x1k will accept 2kB data URI. This will also help combat bad bots and DoS attacks.

You must also manage timeouts in order to improve server performance and reduce client traffic.

client_body_timeout   10;
client_header_timeout 10;
keepalive_timeout     5 5;
send_timeout          10;
  1. client_body_timeout 10; – Directive sets the read timeout for the request body from client. The timeout is set only if a body is not get in one readstep. If after this time the client send nothing, nginx returns error “Request time out” (408). The default is 60.
  2. client_header_timeout 10; – Directive assigns timeout with reading of the title of the request of client. The timeout is set only if a header is not get in one readstep. If after this time the client send nothing, nginx returns error “Request time out” (408).
  3. keepalive_timeout 5 5; – The first parameter assigns the timeout for keep-alive connections with the client. The server will close connections after this time. The optional second parameter assigns the time value in the header Keep-Alive: timeout=time of the response. This header can convince some browsers to close the connection, so that the server does not have to. Without this parameter, nginx does not send a Keep-Alive header (though this is not what makes a connection “keep-alive”).
  4. send_timeout 10; – Directive assigns response timeout to client. Timeout is established not on entire transfer of answer, but only between two operations of reading, if after this time client will take nothing, then nginx is shutting down the connection

Control Simultaneous Connections:

The NginxHttpLimitZone module can be used to limit the number of concurrent connections for the assigned session or, in a special case, from a single IP address. Modify nginx.conf as follows:

### Directive describes the zone, in which the session states are stored i.e. store in slimits. ###
### 1m can handle 32000 sessions with 32 bytes/session, set to 5m x 32000 session ###
limit_zone slimits $binary_remote_addr 5m;
 
### Control maximum number of simultaneous connections for one session i.e.restricts the amount of connections from a single ip address ###
limit_conn slimits 5;

Remote clients will be limited to no more than 5 concurrently "open" connections per remote IP address.

Rate Limit or DDOS Protection:

NGINX is intended to act as a "buffer" for your site or application. It offers a nonblocking, eventdriven architecture that can handle massive volumes of queries while consuming minimal resources. New network requests do not prevent NGINX from processing ongoing requests, implying that NGINX has the capacity to use the measures mentioned below to safeguard your site or application against attack.

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
 
server {
    location /admin/ {
        limit_req zone=mylimit burst=20 nodelay;
        
        proxy_pass http://my_upstream;
    }
}
  • ZONE : 16,000 IP addresses takes 1 megabyte so use zone accordingly so above zone can store 160,000 IP
  • RATE :  The maximum request rate is specified. The rate in the example cannot exceed 10 requests per second. Because NGINX tracks requests at the millisecond level, this restriction amounts to one request every 100 milliseconds (ms) rest is 503.
  • BURST : The burst parameter specifies how many queries a client can make in excess of the zone's rate. That is, if 21 requests arrive from the same IP address at the same time, NGINX immediately passes the first one to the upstream server group and queues the remaining 20. It then passes a pending request every 100ms and returns 503 to the client only if the number of queued requests exceeds 20.
  • NODELAY : With the nodelay argument, NGINX still allocates queue slots based on the burst parameter and enforces the set rate restriction, but not by spacing out queued request forwarding. Instead, when a request arrives "too soon," NGINX forwards it instantly as long as a place in the queue is available. It identifies that slot as "taken" and does not allow another request to utilize it until the proper period has passed (in our example, after 100ms).

Limit Available Methods

On the Internet, the most common methods are GET and POST. RFC 2616 defines web server methods. If a web server does not need to use all available methods, they should be disabled. The following will filter the methods and only allow GET, HEAD, and POST:

## Only allow these request methods ##
     if ($request_method !~ ^(GET|HEAD|POST)$ ) {
         return 444;
     }
## Do not accept DELETE, SEARCH and other methods ##

Deny Certain User-Agents

You can easily block user-agents that may be abusing your server, such as scanners, bots, and spammers. Block the msnbot and scrapbot robots:

## Block download agents ##
     if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
            return 403;
     }
##
## Block some robots ##
     if ($http_user_agent ~* msnbot|scrapbot) {
            return 403;
     }
##

Block Referral Spam

Referer spam is dengerouns. It can harm your SEO ranking through web-logs (if published) because the referrer field refers to their spammy site. With these lines, you can deny access to referer spammers.

## Deny certain Referers ###
     if ( $http_referer ~* (babes|forsale|girl|jewelry|love|nudit|organic|poker|porn|sex|teen) )
     {
         # return 404;
         return 403;
     }
##

Stop Image Hotlinking

Image or HTML hotlinking occurs when someone links to your site but displays one of your images on their own site. As a result, you will be charged for bandwidth and the content will appear to be part of the hijacker's site. This is typically done on message boards and blogs. I strongly advise you to block and stop image hotlinking at the server level.

# Stop deep linking or hot linking
location /images/ {
  valid_referers none blocked www.example.com example.com;
   if ($invalid_referer) {
     return   403;
   }
}
location /images/ {
  valid_referers blocked www.example.com example.com;
  if ($invalid_referer) {
  rewrite ^/images/uploads.*\.(gif|jpg|jpeg|png)$ http://www.examples.com/banned.jpg last
 }
}

Directory Restrictions

You can set access control for a specified directory. All web directories should be configured on an individual basis, allowing access only where it is required.

Limiting Access By Ip Address

You can limit access to directory by ip address to /files/ directory:

location /files/ {
  ## block one workstation
  deny    192.168.1.1;

  ## allow anyone in 192.168.1.0/24
  allow   192.168.1.0/24;

  ## drop rest of the world
  deny    all;
}

Password Protect The Directory

First create the password file and add a user called kxitiz:

mkdir /usr/local/nginx/conf/.htpasswd/
htpasswd -c /usr/local/nginx/conf/.htpasswd/passwd kxitiz

Add authentication to config:

### Password Protect /myimages/ and /confidential/ directories ###
location ~ /(myimages/.*|confidential/.*) {
  auth_basic  "Restricted";
  auth_basic_user_file   /usr/local/nginx/conf/.htpasswd/passwd;
}

Once file is generated use following command to generate new user:

 htpasswd -s /usr/local/nginx/conf/.htpasswd/passwd userName

Linux /etc/sysctl.conf Hardening

/etc/sysctl.conf allows you to control and configure the Linux kernel and networking settings.

# Avoid a smurf attack
net.ipv4.icmp_echo_ignore_broadcasts = 1
 
# Turn on protection for bad icmp error messages
net.ipv4.icmp_ignore_bogus_error_responses = 1
 
# Turn on syncookies for SYN flood attack protection
net.ipv4.tcp_syncookies = 1
 
# Turn on and log spoofed, source routed, and redirect packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
 
# No source routed packets here
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
 
# Turn on reverse path filtering
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
 
# Make sure no one can alter the routing tables
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
 
# Don't act as a router
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
 
 
# Turn on execshild
kernel.exec-shield = 1
kernel.randomize_va_space = 1
 
# Tuen IPv6
net.ipv6.conf.default.router_solicitations = 0
net.ipv6.conf.default.accept_ra_rtr_pref = 0
net.ipv6.conf.default.accept_ra_pinfo = 0
net.ipv6.conf.default.accept_ra_defrtr = 0
net.ipv6.conf.default.autoconf = 0
net.ipv6.conf.default.dad_transmits = 0
net.ipv6.conf.default.max_addresses = 1
 
# Optimization for port usefor LBs
# Increase system file descriptor limit
fs.file-max = 65535
 
# Allow for more PIDs (to reduce rollover problems); may break some programs 32768
kernel.pid_max = 65536
 
# Increase system IP port limits
net.ipv4.ip_local_port_range = 2000 65000
 
# Increase TCP max buffer size setable using setsockopt()
net.ipv4.tcp_rmem = 4096 87380 8388608
net.ipv4.tcp_wmem = 4096 87380 8388608
 
# Increase Linux auto tuning TCP buffer limits
# min, default, and max number of bytes to use
# set max to at least 4MB, or higher if you use very high BDP paths
# Tcp Windows etc
net.core.rmem_max = 8388608
net.core.wmem_max = 8388608
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_window_scaling = 1

Sample:

This is just a sample file edit before using in production as required. *Edit as Needed.

upstream app-ws {
  zone app-ws 64k;
  server 127.0.0.1:<port>;
  keepalive 2;
}
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server{
        server_name www.website.com;

        # security headers
        add_header X-Frame-Options           "SAMEORIGIN" always;
        add_header X-XSS-Protection          "1; mode=block" always;
        add_header X-Permitted-Cross-Domain-Policies   master-only;
        add_header X-Download-Options        noopen;
        add_header X-Content-Type-Options    "nosniff" always;
        add_header Referrer-Policy           "no-referrer-when-downgrade" always;
        set $DEFAULT "default-src 'self' *.example.com";
        set $OBJECT "object-src 'self'";
        set $BASE_URI "base-uri 'self'";
        set $STYLE "style-src 'self' 'unsafe-inline' *.example.com";
        set $IMG "img-src 'self' data: *.example.com";
        set $CONNECT "connect-src 'self' *.googleapis.com";
        set $SCRIPT "script-src 'unsafe-eval' 'unsafe-inline' *.example.com";
        set $FORM "form-action 'self' *.facebook.com";
        set $FRAME "frame-src 'self'";
        set $FRAMEANS "frame-ancestors 'self'";
        add_header Content-Security-Policy "${DEFAULT}; ${OBJECT}; ${BASE_URI}; ${STYLE}; ${IMG}; ${CONNECT}; ${SCRIPT}; ${FORM}; ${FRAME}; ${FRAMEANS};" always;
        add_header Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()";
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
        add_header 'Access-Control-Allow-Origin' 'https://website.com' always;
        add_header Access-Control-Max-Age 3600;
        add_header Access-Control-Expose-Headers Content-Length;
        add_header Access-Control-Allow-Headers Range;

        # logging
        access_log /var/log/nginx/website.com.log;
        error_log /var/log/nginx/website.com.error.log warn;

        # reverse proxy
        location / {
        		# Edit Request as required
        		if ($request_method !~ ^(GET|POST|DELETE|PUT)$ ) {
                      return 444;
             	}
              	if ( $http_referer ~* (babes|forsale|girl|jewelry|love|nudit|organic|poker|porn|sex|teen) )
                     {
                         return 403;
                     }
                 valid_referers none blocked www.website.com website.com;
            	 if ($invalid_referer) {
               return   403;
            	 }
                limit_req zone=mylimit burst=20 nodelay;
                proxy_pass http://127.0.0.1:<port>;
                client_max_body_size 10m;
                proxy_http_version 1.1;
                proxy_cache_bypass $http_upgrade;
                proxy_buffering off;
                # Proxy headers
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Host $host;
                proxy_set_header X-Forwarded-Port $server_port;
                # Proxy timeouts
                proxy_connect_timeout 60s;
                proxy_send_timeout 60s;
                proxy_read_timeout 60s;
        }
    
    #Remove if you dont use websocket proxy
    location /appwebsocketpath {
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection "upgrade";
     
           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header Forwarded $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header X-Forwarded-Proto $scheme;
     
           proxy_pass http://app-ws;
     }
     #To view status of nginx
     #location /nginx_status {
     #     stub_status on;
     #     allow 127.0.0.1;
     #     deny all;
     #}

    listen [::]:443 ssl;
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/www.website.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.website.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

}
server{
    if ($host = www.website.com) {
      return 301 https://$host$request_uri;
    }


        listen 80;
        listen [::]:80;
        server_name www.website.com;
    return 404;


}