Nginx webserver configuration

Nginx web server comes with the default website. But there will be business cases where you have to host more than one website or sub domain on your Nginx web server. In many cases, you might want to configure nginx as reverse proxy for multiple website that are hosted on your upstream server, such as Apache. This article will guide you with common configurations that tune your nginx server’s performance and offer first line of security. This article assumes that you have nginx installed on Ubuntu 16.04 LTS.


Modify your nginx.conf’s http { } block with the attributes below.

user www-data;
worker_processes auto;
pid /run/;
include /etc/nginx/modules-enabled/*.conf;
events {
        worker_connections 10000;
        multi_accept on;
http {

    # Basic
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    types_hash_max_size 2048; 
    # worker connections
    worker_processes 1;
    worker_connections 1024;

    # keepalive
    keepalive_requests 500
    keepalive_timeout 65;
    # buffers
    client_body_buffer_size 100K;
    client_header_buffer_size 1k;
    client_max_body_size 25m;
    large_client_header_buffers 4 16k;

        fastcgi_buffers 8 16k;
        fastcgi_buffer_size 32k;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;

    # timeouts
    client_body_timeout 10;
    client_header_timeout 10;
    send_timeout 10;
    server_names_hash_bucket_size 64;
    # server_name_in_redirect off;
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    # Logging
    access_log /var/www/nginx_logs/access.log; # or off
    # access_log off;
    error_log /var/www/nginx_logs/error.log;

    # purge cache
    map $request_method $purge_method {
        PURGE 1;
        default 0;
    # disable bots
    if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
        return 403; 
    # restrict header types
    add_header Allow "GET, POST, HEAD" always;
    if ($request_method !~ ^(GET|HEAD|POST)$ )
        return 444;

    # security headers
    server_tokens off;
    # add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Content-Security-Policy "default-src 'self';";
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
    # Limit requests
    limit_conn_zone $binary_remote_addr zone=global_limit_conn_zone:10m;
    limit_req_zone $binary_remote_addr zone=global_limit_req_zone:10m rate=50r/s;

    # proxy cache
    proxy_cache_path /tmp/nginx levels=1:2 keys_zone=global_cache_zone:20m max_size=500m inactive=60m use_temp_path=off purger=on;
    proxy_cache_key "$scheme$request_method$host$proxy_host$request_uri";

    # SSL Settings
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 10m;
    # gzip
    gzip             on;
    gzip_disable "msie6";
    gzip_comp_level  6;
    gzip_min_length  1000;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_vary on;
    gzip_proxied     expired no-cache no-store private auth;
    gzip_types        text/plain application/x-javascript text/xml text/css application/xml application/json application/javascript application/xml+rss text/javascript;

    # Virtual Hosts
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*.conf;

    # ..

Virtual Hosts

You can create individual configuration files for individual websites/subdomain sites in /etc/nginx/sites-available/.

$ sudo nano /etc/nginx/sites-available/

Your site configuration file will typically have only server { } block. Below is the typical configuration that you can use. The configuration blocks have comments to make you understand what they mean.

server {
    listen 80;

    root /var/www/;
    index index.php index.html index.htm;

    location / {
        # try_files $uri $uri/ /index.php;

        # ddos protection
        limit_req zone=global_limit_req_zone burst=20 nodelay;
        limit_req_log_level warn;
        limit_req_status 444;
        limit_conn conn_limit_per_ip 10;

        # deny IPs
        # deny;

        # proxy cache
        add_header X-Proxy-Cache $upstream_cache_status;
        proxy_cache global_cache_zone;
        proxy_cache_min_uses 5;
        proxy_cache_bypass  $http_cache_control;
        proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;
        proxy_cache_methods GET HEAD POST;
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404      1m;
        proxy_cache_valid any 5m;
        proxy_no_cache $http_pragma $http_authorization;
        proxy_cache_purge $purge_method;
        # reverse proxy
        include proxy_params;

    location ~ \.php$ {
        # include snippets/fastcgi-php.conf;
            # fastcgi_pass unix:/run/php/php7.4-fpm.sock;

    location ~ /\.ht {
        deny all;
    # security headers
    server_tokens off;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Content-Security-Policy "default-src 'self';";
    # enables server-side protection from BEAST attacks
    ssl_prefer_server_ciphers on;
    # gzip responses
    gzip on;
    gzip_disable "msie6";
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 50000;
    gzip_proxied no-cache no-store private expired auth;

    # Expire rules for static content

    # cache.appcache, your document html and data
    location ~* \.(?:manifest|appcache|html?)$ {
      expires -1;
      # access_log logs/static.log; # I don't usually include a static log

    # Feed
    location ~* \.(?:rss|atom)$ {
      expires 1h;
      add_header Cache-Control "public";

    # Media: images, icons, video, audio, HTC
    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|otf|ttf|eot|woff)$ {
        expires 1M;
        access_log off;
        tcp_nodelay off;
        add_header Vary Accept-Encoding;
        add_header Cache-Control "public";
        ## Set the OS file cache.
        open_file_cache max=3000 inactive=120s;
        open_file_cache_valid 45s;
        open_file_cache_min_uses 2;
        open_file_cache_errors off;

    # CSS and Javascript
    location ~* \.(?:css|js)$ {
        expires 1w;
        access_log off;
        add_header Cache-Control "public";

Once the virtual host file is added, link it to sites-available and restart the nginx server

$ sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
$ sudo service nginx restart