nginx config

Updated . Posted . Visible to the public.

See production grade nginx and docker config here Show archive.org snapshot .

Some notes on this:

  • the PCI work was trial and error, we did set up, the external audit did penetration/probe testing, we adjusted settings until given a pass
  • we used to use hhvm until php 7 turned up. Boy was that a speed advantage back in the php 5 days
  • Note that we dont service any traffic unless it comes from CDN, protecting magneto php code from noise can really reduce load on you systems
  • we use plenty of rate limiting as this is a well known global site and we get attacks once to twice a month
    you may think its crazy but we use no DB server or load balancers or multiple nginx servers, its just not needed if you setup magneto well, magento is very talkative to the database, see the use of sockets instead of IP. DB and php using sokets on one server is faster that seperate DB and the web server.
  • pages have got slower over the years as marketing add more and more stuff
  • the rate limiting by x_forward_for is a good trick when using CDN. Be careful to not limit you CDN via the primary IP passed in requests
  • we use no varnish and we gave up on magento cache modules, you dont need them. Just use CDN, GC or AWS firewall and a 4-8 core 32gig mem ubuntu server with 100gig SSD hard disk, all image assets are on the SSD
  • we have a hacked up index.php for more protection. Can publish a copy of that if people interested, but its a bit ugly
  • Here we take care to not allow magneto folders to be visible, we rename the admin path something crazy hard that no one will guess, although I do think there may be some ways to display this path and should put in IP restriction to any admin
  • all config is in this file, we probably should break into bits and use site_enabled but if we get an attack late at night tired brains work better on one file
# xpractical nginx config, adapted from www.hypernode.com

user www-data;

# hypernode use 4 here, we have quad core
worker_processes 8;

pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {

    # This uses Nginx’s Map module to set a variable $fastcgi_https. 
    # The variable is set to “off” by default, and “on” for HTTPS connections. We then use the variable to set a FastCGI parameter.
    map $scheme $fastcgi_https {
        default off;
        https on;
    }

    # extra logging
    log_format timed_combined '$remote_addr - $remote_user [$time_local] '
       '"$http_x_forwarded_for" '
       '"$request" $status $body_bytes_sent '
       '"$http_referer" "$http_user_agent" '
       '$request_time $upstream_response_time $pipe $host $request_uri';

    access_log  /var/log/nginx/access.log  timed_combined;
    error_log   /var/log/nginx/error.log   notice;

    # basic settings
    disable_symlinks off; 
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;
    server_names_hash_bucket_size 64;

    # allows big media uploads
    client_max_body_size 120m;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # SSL global Settings
    #ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_protocols TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE , dropping TLSv1 for PCI compliance
    ssl_prefer_server_ciphers on;
    # pre trustwave PCI check
    #ssl_ciphers         HIGH:!aNULL:!MD5; 
    # post trustwave PCI check
    ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS;
    
    # GeoIP support is included in the Ubuntu 12.04 Nginx.
    # This enables logging, and the following:
    #    if ($geoip_country_code ~ (CN|ZW) ) {
    #      return 403;
    #    }
    # geoip_country         /usr/share/GeoIP/GeoIP.dat;
    
    # CloudFlare GeoIP Mapping 07-10-2016
        map $http_cf_ipcountry $country {
		default us;
		AU au; NZ au;
		CA ca;
		DE uk; GB uk; FR uk; AT uk; BE uk; HR uk; CY uk; CZ uk; DK uk; EE uk; FI uk; GR uk; HU uk; IS uk; IE uk; IT uk; LU uk; MD uk; MC uk; NL uk; NO uk; PL uk; PT uk; RO uk; RU uk; SK uk; SI uk; ES uk; SE uk; CH uk; UA uk; VA uk;
	}
    
    #gzip settings
        gzip on;
        gzip_disable "msie6";
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;
        gzip_min_length 1100;
        gzip_types
           text/plain
           text/css
           text/js
           text/xml
           text/javascript
           application/javascript
           application/x-javascript
           application/json
           application/xml
           application/xml+rss ;

    # Determine whether a request comes from a human, a search crawler or another bot.
    # [client] SOE bot is Screaming Frog SEO Spider/7.2
    map $http_user_agent $is_non_search_bot {
        default '';
        ~*(Screaming|Frog|dotMailer) '';
        ~*(google|pingdom|monitis.com|Zend_Http_Client) '';
        ~*(http|bing|crawler|spider|bot|search|ForusP|Wget/|Python-urllib|PHPCrawl|bGenius|LieBaoFast) 'bot';
        ~*(Mb2345Browser|zh-CN|MicroMessenger|zh_CN|HUAWEIFRD) 'bot';
    }
    
    # Rate limit bots (that are not search spiders) to one PHP request per second.
    # An empty '$limit_bots' would disable rate limiting for this requests

    # limit_req_zone $is_non_search_bot zone=bots:1m rate=1r/s;
    limit_req_zone $is_non_search_bot zone=bots:1m rate=6r/m;

    # need to use x_forward so we dont limit CDN
    limit_req_zone $http_x_forwarded_for zone=allusers:16m rate=5r/s;

    limit_req_log_level error;

    # As this is the first server definition it will be used if there is a good match to server_name OR
    # if there is no match to any server name
    server {

        server_name [client].com, [client]one.org;
        # if no server name all requests get here
        #server_name [client].com www.[client].com

        # single server that listens on both http and https
        listen              80;
        listen              443 default ssl;

        include /var/www/keyconfigfiles/cloudflare.whitelist;
        #deny all;
        # go daddy crts, not complete need to generate bundle
        #ssl_certificate     /home/[mag_user]/xp-demo.com/e7a321f0b88a7e89.crt;
        #ssl_certificate_key /home/[mag_user]/xp-demo.com/xp-demo.key;

        # may be able to use these SSL should be linked to domain and web server not the hardware or IP
        #ssl_certificate     /var/www/ssl/[client].com.crt;
        #ssl_certificate_key /var/www/ssl/[client].com.key;
	ssl_certificate     /var/www/ssl2018/[client].com.crt;
        ssl_certificate_key /var/www/ssl2018/[client].com.key;

        # openssl req -new -newkey rsa:2048 -nodes -keyout example.key -out example.csr
        # use the csr request to get the crt files
        # cat all crt's into one bundle file, make sure the main one is top of list
        #ssl_certificate     /home/[mag_user]/[client]one_org-ssl-bundle.crt;
        #ssl_certificate_key /home/[mag_user]/[client]one_org.key;
       
        # If the site is accessed via mydomain.com
        # automatically redirect to www.mydomain.com.
        #if ($host = 'mydomain.com' ) {
        #    rewrite ^/(.*)$ http://www.mydomain.com/$1permanent;
        #}

        # if you are using a load balancer uncomment these lines
        # header from the hardware load balancers
        #real_ip_header X-Forwarded-For;
        # trust this header from anything inside the subnet
        #set_real_ip_from X.X.X.1/24;
        # the header is a comma-separated list; the left-most IP is the end user
        #real_ip_recursive on;
        #listen      10.10.10.11:80;
        #set_real_ip_from 103.248.190.60;

        # use this to test pre-production magento folder, danger new code will update database
        set $my_path var/www/magento;
        if ($remote_addr ~ "^(110\.175\.187\.117)$" ) {
          # set $my_path var/www/temp-magento;
          set $my_path var/www/magento;
        }
        root /$my_path;
       
        error_page 500 502 503 504 /custom_50x.html;
        location = /custom_50x.html {
          root /usr/share/nginx/html;
          internal;
        }
 
        # magento specific
        set $storecode "default";
        index index.html index.php;
        autoindex off; # we don’t want users to see files in directories

        # security
        if ($http_user_agent = "") { return 444;}

        # sitemap files
        location ~ /sitemap.xml|/([a-z][a-z]|[a-z][a-z]-[a-z][a-z])/(.*)sitemap.xml {
           try_files /sitemaps/$1/sitemap.xml /sitemaps/sitemap.xml =404;
        }

	# allow view of sub folders and files here
        location /downloads/ { autoindex on; }

        # Denied locations require a "^~" to prevent regexes (such as the PHP handler below) from matching
        # http://nginx.org/en/docs/http/ngx_http_core_module.html#location
        location ^~ /app/                       { return 403; }
        location ^~ /shell/                     { return 403; }
        location ^~ /vendor/                    { return 403; }
        location ^~ /includes/                  { return 403; }
        location ^~ /media/downloadable/        { return 403; }
        location ^~ /pkginfo/                   { return 403; }
        location ^~ /report/config.xml          { return 403; }
        location ^~ /var/                       { return 403; }
        location ^~ /lib/                       { return 403; }
        location ^~ /dev/                       { return 403; }
        location ^~ /RELEASE_NOTES.txt          { return 403; }
        location ^~ /downloader/pearlib         { return 403; }
        location ^~ /downloader/template        { return 403; }
        location ^~ /downloader/Maged           { return 403; }
        location ~* ^/errors/.+\.xml            { return 403; }

        # Extra protection
        location ~* ^/rss/(order|catalog)/ { return 444; }
        location ~ /(dev/tests/|errors/local.xml|cron\.php) { deny all; }
        # note this will stop zip download  location ~ ^/.*\.(sh|pl|swp|phar|sql|conf|zip|tar|.+gz)$ { return 444; }
        location ~ ^/.*\.(sh|pl|swp|phar|sql|conf|tar|.+gz)$ { return 444; }
        location ~ /\.(svn|git|hg|htpasswd|bash|ssh) { return 444; }
        location ~* /(lib|media|shell|skin)/.*\.php$ { deny all; }
       
        # CVE-2015-3428 / AW_Blog vulnerability
        # Note the .+ at the start: We want to allow url's like
        # order=create_date, which would otherwise match.
        if ($arg_order ~* .+(select|create|insert|update|drop|delete|concat|alter|load)) {
           return 403;
        }

        # Don't skip .thumbs, this is a default directory where Magento places thumbnails
        # Nginx cannot "not" match something, instead the target is matched with an empty block
        # http://stackoverflow.com/a/16304073
        location ~ /\.thumbs {
        }

        # Skip .git, .htpasswd etc
        location ~ /\. {
            return 404;
        }
        
        # Disable all methods besides HEAD, GET and POST.
        if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 444; }
   
        # Try static files first, then pass to Magento handler
        location / {
         
          # stop specific IPs until fixed in CDN - added in to CF 24032020
          #deny 77.246.157.20;
 
          # enable limits
          limit_req zone=allusers burst=5 nodelay;
          limit_req zone=bots;
          #
          try_files $uri $uri/ @handler;
          access_log off; # do not log access to static files
          expires max;    # cache static files aggressively
        }

        # Pass paths to Magento entry point
        location @handler {
           rewrite / /index.php?$args;
        }   
    
        # Forward paths like /js/index.php/x.js to relevant handler
        location ~ \.php/ {
           rewrite ^(.*\.php)/ $1 last;
        }   

        # Run PHP files using FastCGI
        location ~ \.php$ {
           try_files $uri $uri/ =404;
           expires off; # no need to cache php executable files
           #try_files $uri $uri/ @handler; 
           #fastcgi_pass   127.0.0.1:9000;
           #fastcgi_pass unix:/var/run/hhvm/hhvm.sock;
           fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
           fastcgi_keep_conn on; # use persistent connects to backend
           fastcgi_param  HTTPS $fastcgi_https;   # needed for https
           fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
           fastcgi_param  MAGE_RUN_CODE $storecode;
           fastcgi_param  MAGE_RUN_TYPE store; ## or website;

            fastcgi_buffer_size 128k;
            fastcgi_buffers 256 16k;
            fastcgi_busy_buffers_size 256k;
            fastcgi_temp_file_write_size 256k;

           include        fastcgi_params; ## See /etc/nginx/fastcgi_params
       }   

       # [client]store Banner 30m browser expiry
       location = /media/bannerslider/s/w/[client]store_au_banner.jpg {
          expires 30m;
       }

       location = /media/bannerslider/s/w/[client]store_us_banner.jpg {
          expires 30m; # All other image expires
       }
       location ~* \.(?:csv|tsv|xml|txt)$ {
          expires 1m; # dont cache data feeds
       }

       location ^~ /blog/ {
	   proxy_intercept_errors off;
	   proxy_pass https://blog.[client].com/;
       }

       location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
          expires 5d; 
          log_not_found off;
       }

    }

    # add the other servers here, these only work if matched, they tell the browser to cache this redirect
    # once a browser has one of these 301 redirects it will always use it until cache is cleared

    server {
        server_name www.[client]store.co.uk [client]store.co.uk;
        return 301 $scheme://www.[client].com/uk$request_uri;
    }

    server {
        server_name www.[client]store.com.au [client]store.com.au www.[client].com.au [client].com.au;
        return 301 $scheme://www.[client].com/au$request_uri;
    }

    server {
        server_name www.[client]store.com [client]store.com www.[client]security.com [client]security.com;
        return 301 $scheme://www.[client].com/us$request_uri;
    }
    
}
kiatng
Last edit
kiatng
Posted by kiatng to OpenMage (2020-07-10 01:34)