Nginx webserver configuration

Moving this from docs.silverstripe.org to a more community maintained location

Nginx

The prerequisite is that you have already installed Nginx and you are
able to run PHP files via the FastCGI-wrapper from Nginx.

Now you need to set up a virtual host in Nginx with configuration settings
that are similar to those shown below.

If you don't fully understand the configuration presented here, consult the [nginx documentation](http://nginx.org/en/docs/).

Especially be aware of accidental php-execution when extending the configuration.

Caveats about the sample configuration below

  • It uses the new filesystem layout (with public directory) introduced in SilverStripe 4.1.0 (see upgrading guide)
  • The regular expression for allowed file types must be manually updated if the File.allowed_extensions list is updated.
  • The error pages for 502 (Bad Gateway) and 503 (Service Unavailable) need to be manually created and published in the CMS (assuming use of the silverstripe/errorpage module).
  • If you are not using Let’s Encrypt, you can remove the acme-challenge location.
  • This example is a template; you have to replace all the ##TOKENS## with valid information.
  • If you are not using IPv6, remove the IPv6 listen directives.
# nginx configuration for ##CANONICAL_DOMAIN##
worker_processes  auto;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65s;

    # Let PHP manage this
    client_max_body_size 0;

    # Secure TLS from BEAST, POODLE, etc.
    # Source: https://cipherli.st (modified)
    ssl_protocols TLSv1.3 TLSv1.2;# Requires nginx >= 1.13.0 else use TLSv1.2
    ssl_prefer_server_ciphers on;
    ssl_dhparam /usr/local/etc/nginx/dhparam.pem;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
    ssl_session_timeout  10m;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off; # Requires nginx >= 1.5.9
    ssl_stapling on; # Requires nginx >= 1.3.7
    ssl_stapling_verify on; # Requires nginx => 1.3.7
    resolver 1.1.1.1 1.0.0.1 valid=300s;
    resolver_timeout 5s;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options sameorigin;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    # /

    ssl_certificate "##CERTIFICATE_PATH##";
    ssl_certificate_key "##KEY_PATH##";

    gzip  on;
    gzip_types text/css text/xml text/plain application/javascript application/json application/atom+xml application/rss+xml;
    gzip_proxied expired no-cache no-store private auth;
    gzip_disable msie6;

    server_tokens off;

    # Force HTTPS & answer ACME challenges
    server {
        listen      80;
        listen [::]:80;
        server_name _;
        location / {
            return 301 https://##CANONICAL_DOMAIN##$request_uri;
        }
        location ~ ^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$ {
            default_type text/plain;
            return 200 "$1.##ACME_THUMBPRINT##";
        }
    }

    # HTTPS canonicalization
    server {
        listen      443 ssl http2;
        listen [::]:443 ssl http2;
        server_name ##ALTERNATIVE_DOMAIN##;

        return 301 https://##CANONICAL_DOMAIN##$request_uri;
    }

    # Config for SilverStripe
    server {
        server_name ##CANONICAL_DOMAIN##;
        root ##PATH_TO##/docroot/public;

        # Defend against SS-2015-013 -- http://www.silverstripe.org/software/download/security-releases/ss-2015-013
        if ($http_x_forwarded_host) {
            return 400;
        }

        error_page 404 /assets/error-404.html;
        error_page 500 /assets/error-500.html;
        error_page 502 /assets/error-502.html;
        error_page 503 /assets/error-503.html;

        # SSL
        listen      443 ssl http2 accept_filter=dataready;
        listen [::]:443 ssl http2 accept_filter=dataready;

        location / {
            if (-f $realpath_root/maintenance-sentinel) { return 503; }
            try_files $uri /index.php?$query_string;
        }

        location ~ /\.(htaccess|method)$ {
            return 403;
        }

        location ~ ^/assets/.protected/ {
            return 403;
        }

        location ~ ^/assets/.*\.(?i:css|js|ace|arc|arj|asf|au|avi|bmp|bz2|cab|cda|csv|dmg|doc|docx|dotx|flv|gif|gpx|gz|hqx|ico|jpeg|jpg|kml|m4a|m4v|mid|midi|mkv|mov|mp3|mp4|mpa|mpeg|mpg|ogg|ogv|pages|pcx|pdf|png|pps|ppt|pptx|potx|ra|ram|rm|rtf|sit|sitx|tar|tgz|tif|tiff|txt|wav|webm|wma|wmv|xls|xlsx|xltx|zip|zipx)$ {
            sendfile on;
            try_files $uri /index.php?$query_string;
        }

        location ~ ^/assets/error-\d\d\d\.html$ {
            try_files $uri =404;
        }

        location ~ ^/assets/ {
            return 404;
        }

        location /index.php {
            fastcgi_buffer_size 32k;
            fastcgi_busy_buffers_size 64k;
            fastcgi_buffers 4 32k;
            fastcgi_keep_conn on;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            include        fastcgi_params;
        }
    }
}

NOTE: Nginx does not support .htaccess files. Any .htaccess requirements of SilverStripe will need to be manually converted into Nginx configurations instead.