Git Product home page Git Product logo

nginx-craft's Introduction

nginx-craft

An Nginx virtual host configuration for Craft CMS that implements a number of best-practices.

Overview

What it handles

The Nginx-Craft configuration handles:

  • Redirecting from HTTP to HTTPS
  • Canonical domain rewrites from www.SOMEDOMAIN.com to SOMEDOMAIN.com
  • 301 Redirect URLs with trailing /'s as per https://webmasters.googleblog.com/2010/04/to-slash-or-not-to-slash.html
  • Setting PATH_INFO properly via php-fpm -> PHP
  • Setting HTTP_HOST to mitigate HTTP_HOST Security Issues
  • "Far-future" Expires headers
  • Enable serving of static gzip files via gzip_static
  • Adding XSS and other security headers
  • Gzip compression
  • Filename-based cache busting for static resources
  • IPv4 and IPv6 support
  • http2 support
  • Reasonable SSL cipher suites and TLS protocols
  • Localized sites
  • Server-side includes
  • Optionally includes Dotenvy generated .env files

Assumptions made

The following are assumptions made in this configuration:

  • The site is https
  • The SSL certificate is from LetsEncrypt.com
  • The canonical domain is SOMEDOMAIN.com (no www.)
  • Nginx is version 1.9.5 or later (and thus supports http2)
  • Paths are standard Ubuntu, change as needed
  • You're using php7.1 via php-fpm
  • You have 'omitScriptNameInUrls' => true, in your craft/general.php

If any of these assumptions are invalid, make the appropriate changes.

Note: We disable TLSv1.0 because it is insecure, but IE 8, 9 & 10 need to have support for TLSv1.1 manually enabled or they will not be able to connect.

What's included

This Nginx configuration comes in two parts:

  • sites-available/somedomain.com.conf - an Nginx virtual host configuration file tailored for Craft CMS; it will require some minor customization for your domain
  • nginx-partials - some Nginx configuration partials used by all of the virtual hosts, logically segregated. These don't need to be changed, but can be selectively disabled by changing the suffix to .off (or anything other than .conf)

Using Nginx-Craft

  1. Obtain an SSL certificate for your domain via LetsEncrypt.com (or via other certificate authorities). LetsEncrypt.com is free, and it's automated. You will need a basic server up and running that responds to port 80 to do this, LetsEnecrypt/Nginx tutorial
  2. Create a dhparam.pem via sudo openssl dhparam -out /etc/nginx/dhparams.pem 2048
  3. Download your Issuer certificate via mkdir /etc/nginx/ssl; sudo wget -O /etc/nginx/ssl/lets-encrypt-x3-cross-signed.pem "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem"
  4. Upload the entire nginx-partials folder to /etc/nginx/
  5. Rename the somedomain.com.conf file to yourdomain.com.conf
  6. Do a search & replace in yourdomain.com.conf to change SOMEDOMAIN -> yourdomain
  7. Tweak any paths that may need changing on your server
  8. Change the fastcgi_pass unix:/var/run/php/php7.1-fpm.sock; line to reflect whatever version of PHP you're running
  9. Restart nginx via sudo nginx -s reload

If you're using Forge, it takes care of a number of these things for you, but still needs tuning.

The same applies for CloudWays, ServerPilot, Homestead, MAMP, etc.

A Forge Template is provided in forge-templates/NginxTemplate.conf that you can use to automate setting up your Forge servers.

For this to work, you must clone the repo into /home/forge via:

git clone https://github.com/nystudio107/nginx-craft.git /home/forge

For further information on TLS optimization, see the How to properly configure your nginx for TLS article.

Forge & opcache

N.B.: Forge now has opcache functionality baked-in, you can enable it via the Server settings, so this information is largely deprecated.

If you're using Forge, understand that opcache is off by default. To enable it, go to your server in Forge, click on Edit Files and choose Edit PHP FPM Configuration and search on opcache. Here are the defaults I use; tweak them to suit your needs:

[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1

; Determines if Zend OPCache is enabled for the CLI version of PHP
;opcache.enable_cli=0

; The OPcache shared memory storage size.
opcache.memory_consumption=256

; The amount of memory for interned strings in Mbytes.
opcache.interned_strings_buffer=16

; The maximum number of keys (scripts) in the OPcache hash table.
; Only numbers between 200 and 100000 are allowed.
opcache.max_accelerated_files=8000

; If disabled, all PHPDoc comments are dropped from the code to reduce the
; size of the optimized code.
opcache.save_comments=0

More about tweaking opcache can be found in the Fine-Tune Your Opcache Configuration to Avoid Caching Suprises article. The Best Zend OpCache Settings/Tuning/Config article is very useful as well.

Local Development

While all of the configuration in the somedomain.com.conf will work fine in local development as well, some people might want a simpler setup for local development.

There is a basic_localdev.com.conf that you can use for a basic Nginx configuration that will work with Craft without any of the bells, whistles, or optimizations found in the somedomain.com.conf.

While this is suitable for getting up and running quickly for local development, do not use it in production. There are a number of performance optimizations missing from it.

Brought to you by nystudio107

nginx-craft's People

Contributors

connor-baer avatar engram-design avatar iparr avatar khalwat avatar leevigraham avatar mildlygeeky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nginx-craft's Issues

Question: Why is SSI include active?

Question

Your production conf file has server side include activated. Is there a specific reason for this, because I guess this also creates some overhead not needed for a Craft project?

Problems with localised Craft installation

We have a setup like this for our site:

dev.domain.com/de-de/ <- default
dev.domain.com/gb-en/

and try to use your Forge example configuration. The frontend seems working without problems, but the backend (Craft CMS Control panel) is only accessible via

dev.domain.com/de-de/admin
dev.domain.com/gb-en/admin

thus producing errors with loading the needed css/js for the control panel.

On an apache webserver with the same site, the Craft CMS control panel is accessible via:

dev.domain.com/admin

and running without any problems.

Any idea what's going wrong?

Separate pool for frontend & backend

Hi, I want to separate pool for frontend and backend of Craftcms using php-fpm and nginx. It would be beneficial if this kind of configuration will be added here.

Thanks in advance.

TLSv1.3

Have you tried allowing TLSv1.3 with your configuration?

FR: Some extra headers

don't send the nginx version number in error pages and Server header

server_tokens off;

Add Content-Security-Policy HTTP response header. Helps reduce XSS risks on modern browsers by declaring what dynamic resources are allowed to load via a HTTP Header
Need some tweaking, but something like this:

add_header Content-Security-Policy "default-src https: data: 'unsafe-inline' 'unsafe-eval'" always;

Location Directive Not Allowed, Letsencrypt challenge

Attempting to merge the existing Forge config file with the one provided here and getting this error message:

"location" directive is not allowed here in /etc/nginx/forge-conf/DOMAINREPLACED.com/server/letsencrypt_challenge.conf:1

Looks like it has something to do with an ACME challenge?

letsencrypt_challenge.conf:

location /.well-known/acme-challenge {
auth_basic off;
allow all;
alias /home/forge/.letsencrypt;
}

No idea what's going on :-/

Trailing slash directive not on forge example

If I'm not mistaken, these appear to be missing from the forge example, and are useful! There in general seems to be a schism between the sites-available/somedomain.com.conf and forge-example/NginxConfiguration.conf, and a lack of explanation for that schism, so I'm not sure if there's a reason or if it was potentially overlooked.

# 301 Redirect URLs with trailing /'s as per https://webmasters.googleblog.com/2010/04/to-slash-or-not-to-slash.html
rewrite ^/(.*)/$ /$1 permanent;

# Change // -> / for all URLs, so it works for our php location block, too
merge_slashes off;
rewrite (.*)//+(.*) $1/$2 permanent;

Adding trailing slash causes an unnecessary 307 internal redirect

I have found an issue where Nginx tries to add trailing slash to the URIs, then there happens double redirect. The default redirect for adding trailing slash does the redirect in HTTP protocol and then does another redirect to HTTPS which causes an extra unnecessary "internal redirect 307" status in the process.

For example:
https://somedomain.com/some-article --> http://somedomain.com/some-article/ -301 moved permanently
http://somedomain.com/some-article/ --> https://somedomain.com/some-article/ -307 internal redirect
https://somedomain.com/some-article/ --> 200 status OK

I'm not expert in Nginx configurations so I'm wondering if this could be solved by some code tweak or this is a code bug and should be patched.

Force file downloads

It seems the current setup when accessing a url with.zip it tries to execute as a page. What would be a proper directive to force download of this file type?

Duplicate `server_tokens` directive in vhost conf

Describe the bug

The server_tokens directive appears twice in nginx-craft/sites-available/somedomain.com.conf in the 'Primary virtual host server block'.

To reproduce

  • Add the vhost configuration to nginx
  • Run nginx -t
  • Receive error: nginx: [emerg] "server_tokens" directive is duplicate in /etc/nginx/sites-enabled/<name>:90

Expected behaviour

This seems like an inadvertent error because both the server_tokens directives have the same value (off), so the expected behaviour would be to only have one directive and not fail nginx's config validation.

Screenshots

Error observed in CLI

Versions

  • Plugin version: N/A
  • Craft version: N/A

SSI on but critical cookie if/else not resolved

Question

Hey Andrew, I implemented the criticalcss etc. like you:

{# -- Critical CSS -- #}
{#
# Use Nginx Server Sider Includes (SSI) to render different HTML depending on
# the value in the `critical-css` cookie. ref: http://nginx.org/en/docs/http/ngx_http_ssi_module.html
#}
{% set cssHash = craft.vite.getCssHash("src/js/app.ts") %}
{#
# If the `critical-css` cookie is set, the client already has the CSS file download,
# so don't include the critical CSS, and load the full stylesheet(s) synchronously
#}
<!-- # if expr="$HTTP_COOKIE=/critical\-css\={{ cssHash }}/" -->
{{ craft.vite.script("src/js/app.ts", false) }}
<!-- # else -->
{#
# If the cookie is not set, set the cookie, then include the critical CSS for this page,
# and load the full stylesheet(s) asychronously
#}
<script>
  Cookie.set("critical-css", "{{ cssHash }}", {expires: "7D", secure: true});
</script>
{{ craft.vite.includeCriticalCssTags(criticalPath) }}
{{ craft.vite.script("src/js/app.ts", true) }}
<!-- # endif -->

I also have ssi on; on my forge nginx config.
But my HTML looks all the time like this:
image

It's live here: https://stage.baukasten.io

Is there something what I miss on config side? 🤔
Looked at on devmode.fm and there are no SSI snippets to see. What means it works on your site :D

Difficulty redirecting .php files

I'm migrating from an old site with a lot of .php links littered across the domain.

I want to redirect all of these to the URLs without the .php extension.

I'm trying to use the Retour plugin to help me do this, but I've also tried modifying this config.

I just get "File not found." Help!

This is very helpful, otherwise!

Security headers should have `always` parameter added.

Headers in the security.conf partial should have the always parameter added to ensure that they are added regardless of the response code, see here: https://nginx.org/en/docs/http/ngx_http_headers_module.html

This came up recently on a site that I had penetration tested. I originally had missed off the always parameter and the pen tester reported it as a potential vulnerability as the HSTS headers weren't sent with error responses. Adding the always parameter as shown below resolved the issue.

add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

Allow access of admin via locales

With this setup and using the suggested method for mutli-locale how do you also allow for multi locale access of the admin. Currently when a user activation link is sent out, if it is sent out in a locale other than the default the activation url does't resolve.

Usage of add_header in location block

I will try to do my best to describe the problem with my weird english ;)

In the file sites-available/somedomain.com.conf, line 87 there is an include of all nginx partials conf files (include /etc/nginx/nginx-partials/*.conf;) in a server block.

The file nginx-partials/security.conf contains several add_header directives to secure the website.

Later in the file sites-available/somedomain.com.conf, line 135-136 in location block there is two add_header directives too.

This is a problem because those add_header directives in a location block undoes all previous add_header directives in server or http blocks.

From nginx doc

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

The result are the add_header() directives in nginx-partials/security.conf are absent in the final HTTP response.

To test this behavior, add a custom header like add_header Server "Follow the white rabbit"; in nginx-partials/security.conf, request a page, no rabbit. Remove those add_header in location block and, voilà, follow the funny bunny !

To add headers inside location, you need to have:

  • Headers More module built with Nginx and using more_set_headers instead of 'add_header
    OR
  • Include nginx-partials/security.conf in the location block too

I hope I'm clear :)

client_max_body_size

it would be good to include client_max_body_size to match craft's default of 16M

Brotli Compression

Is your feature request related to a problem? Please describe.

As Brotli compression has better performance than gzip and has 95% browser support now, it would be really nice to have it available in this config.

Describe alternatives you have considered

Obviously I can modify the config here to set it up myself, but since a lot of people use this config, I'm guessing it would be a welcome improvement to the base config here.

duplicate "client_max_body_size" in somedomain.com.conf

"client_max_body_size" directive is duplicate in /etc/nginx/sites-available/somedomain.com.conf:174

# Disable limits on the maximum allowed size of the client request body
 :50  | client_max_body_size 0;
# Misc settings
sendfile off;
:174  | client_max_body_size 100m;
}

so it should be 0 or 100m; ?

Forge example with Fastcgi

Hi, any chance of an example with the Fast cgi caching setup with Forge?

I've combined the older Forge example on NyStudio with the newer forge example found in this repo. It all works fine on the front end with caching working. The problem i'm having however is that i cannot login to the admin. The login page just refreshes. I can log in with a simpler config set up though.

Any pointers appreciated. Thanks.

Forge Config Bug SSL

This two lines seems to be wrong?

ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_trusted_certificate /etc/nginx/ssl/lets-encrypt-x3-cross-signed.pem;

I've changed it to:

ssl_dhparam /etc/nginx/dhparams.pem;
# ssl_trusted_certificate /etc/nginx/ssl/lets-encrypt-x3-cross-signed.pem; ### I removed it

The error code when I try to restart nginx:

forge@absolut /etc/nginx/nginx-partials $ sudo nginx -s reload
nginx: [emerg] SSL_CTX_load_verify_locations("/etc/nginx/ssl/lets-encrypt-x3-cross-signed.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/ssl/lets-encrypt-x3-cross-signed.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file error:0B084002:x509 certificate routines:X509_load_cert_crl_file:system lib)

NGINX error: rewrite or internal redirection cycle while internally redirecting to "/index.php"

This is happening for the first time and never had this before, but on a forge server I'm getting the following error:

rewrite or internal redirection cycle while internally redirecting to "/index.php"

And the Nginx Config as used in your forge-example.

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/dedansacademie.be/before/*;

# Bots to ban via user agent
map $http_user_agent $limit_bots {
    default 0;
    ~*(AhrefsBot|Baiduspider|PaperLiBot) 1;
}

server {
    # Listen for both IPv4 & IPv6 requests on port 443 with http2 enabled
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # General virtual host settings
    server_name dedansacademie.be;
    root /home/forge/dedansacademie.be/current/public;
    index index.html index.htm index.php;
    charset utf-8;

    # Ban certain bots from crawling the site
    if ($limit_bots = 1) {
        return 403;
    }

    # 404 error handler
    error_page 404 /index.php?$query_string;

    # 301 Redirect URLs with trailing /'s as per https://webmasters.googleblog.com/2010/04/to-slash-or-not-to-slash.html
    rewrite ^/(.*)/$ /$1 permanent;

    # Change // -> / for all URLs, so it works for our php location block, too
    merge_slashes off;
    rewrite (.*)//+(.*) $1/$2 permanent;

    # Handle Do Not Track as per https://www.eff.org/dnt-policy
    location /.well-known/dnt-policy.txt {
        try_files /dnt-policy.txt /index.php?p=/dnt-policy.txt;
    }

    # For WordPress bots/users
    location ~ ^/(wp-login|wp-admin|wp-config|wp-content|wp-includes|(.*)\.exe) {
        return 301 https://wordpress.com/wp-login.php;
    }

    # Access and error logging
    access_log off;
    error_log  /var/log/nginx/dedansacademie.be-error.log error;
    # If you want error logging to go to SYSLOG (for services like Papertrailapp.com), uncomment the following:
    #error_log syslog:server=unix:/dev/log,facility=local7,tag=nginx,severity=error;

    # FORGE SSL (DO NOT REMOVE!)
    # ssl_certificate;
    # ssl_certificate_key;
    ssl_certificate /etc/nginx/ssl/dedansacademie.be/292771/server.crt;
    ssl_certificate_key /etc/nginx/ssl/dedansacademie.be/292771/server.key;

    # SSL/TLS configuration, with TLSv1.0 disabled because it is insecure; note that IE 8, 9 & 10 support
    # TLSv1.1, but it's not enabled by default clients using those browsers will not be able to connect
    ssl_protocols TLSv1.2 TLSv1.1;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;
    ssl_ciphers 'ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5';
    ssl_buffer_size 4k;
    ssl_session_timeout 4h;
    ssl_session_cache shared:SSL:40m;
    ssl_stapling on;
    ssl_stapling_verify on;
    #ssl_trusted_certificate /etc/nginx/ssl/lets-encrypt-x3-cross-signed.pem;

    # FORGE CONFIG (DOT NOT REMOVE!)
    include forge-conf/dedansacademie.be/server/*;

    # Load configuration files from nginx-partials
    include /etc/nginx/nginx-partials/*.conf;

    # Root directory location handler
    location / {
        try_files $uri/index.html $uri $uri/ /index.php?$query_string;
    }

    # Localized sites, hat tip to Johannes -- https://gist.github.com/johanneslamers/f6d2bc0d7435dca130fc

    # If you are creating a localized site as per: https://craftcms.com/docs/localization-guide
    # the directives here will help you handle the locale redirection so that requests will
    # be routed through the appropriate index.php wherein you set the `CRAFT_LOCALE`

    # Enable this by un-commenting it, and changing the language codes as appropriate
    # Add a new location @XXrewrites and location /XX/ block for each language that
    # you need to support

    #location @enrewrites {
    #    rewrite ^/en/(.*)$ /en/index.php?p=$1? last;
    #}
    #
    #location /en/ {
    #    try_files $uri $uri/ @enrewrites;
    #}

    # Craft-specific location handlers to ensure AdminCP requests route through index.php
    # If you change your `cpTrigger`, change it here as well
    location ^~ /admin {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ^~ /cpresources {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # php-fpm configuration
    location ~ [^/]\.php(/|$) {
        try_files $uri $uri/ /index.php?$query_string;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # Change this to whatever version of php you are using
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param HTTP_PROXY "";

        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
    }

    location ~ /\.ht {
        deny all;
    }
}

# FORGE CONFIG (DOT NOT REMOVE!)
include forge-conf/dedansacademie.be/after/*;

Maybe you should look into this? Since this is a really weird issue ...

NginxTemplate.conf security include url wrong?

Describe the bug

Just a quick one, but the in the php fpm location block, on line 141 of NginxTemplate.conf, the url for the security include is listed as:

include /home/forge/nginx-partials/security.conf;

But further up the page, the include location is in an nginx-craft folder as so which would've been created in the git clone:

include /home/forge/nginx-craft/nginx-partials/*.conf;

Documentation on adding brotli

Would be great to get some documentation on adding Brotli compression to Nginx on forge. Or maybe included in the template just commented out with install instructions?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.