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;
}
}
Posted by kiatng to OpenMage (2020-07-10 01:34)