Skip to content

Instantly share code, notes, and snippets.

@philipstanislaus
Last active April 11, 2024 03:35
Show Gist options
  • Save philipstanislaus/654adafad91efb6de230845b5bdeae61 to your computer and use it in GitHub Desktop.
Save philipstanislaus/654adafad91efb6de230845b5bdeae61 to your computer and use it in GitHub Desktop.
Sample Nginx config with sane caching settings for modern web development
# Sample Nginx config with sane caching settings for modern web development
#
# Motivation:
# Modern web development often happens with developer tools open, e. g. the Chrome Dev Tools.
# These tools automatically deactivate all sorts of caching for you, so you always have a fresh
# and juicy version of your assets available.
# At some point, however, you want to show your work to testers, your boss or your client.
# After you implemented and deployed their feedback, they reload the testing page – and report
# the exact same issues as before! What happened? Of course, they did not have developer tools
# open, and of course, they did not empty their caches before navigating to your site.
#
# This gist provides you with a sample Nginx config with sane caching settings to prevent the
# above scenario.
#
# Specifically, it
# - deactivates caching by default,
# - allows storing of js and css files, but requires the browser to first check whether newer
# versions are available
# - activates caching for static assets such as images for 5 minutes
#
# Why to cache at all, you wonder?
# This gist should highlight the possibilities you have, and might even be used in a beta testing
# environment with many users yet regular hotfixes. Therefore, I included basic caching for you to
# be adjusted.
#
# Further readings:
# http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/
# https://www.mnot.net/cache_docs/
# http://www.ietf.org/rfc/rfc2616.txt (for a deep dive)
server {
# .domain.com will match both domain.com and anything.domain.com
server_name .example.com;
# It is best to place the root of the server block at the server level, and not the location level
# any location block path will be relative to this root.
root /usr/local/www/$server_name;
# It's always good to set logs, note however you cannot turn off the error log
# setting error_log off; will simply create a file called 'off'.
access_log /var/log/nginx/$host.access.log;
error_log /var/log/nginx/$host.error.log;
# This can also go in the http { } level
index index.html index.htm index.php;
# web app
location / {
try_files $uri $uri/ =404;
# The Expires HTTP header is a basic means of controlling caches; it tells all caches how long
# the associated representation is fresh for. After that time, caches will always check back with
# the origin server to see if a document is changed.
#
# "If a request includes the no-cache directive, it SHOULD NOT include min-fresh, max-stale, or max-age."
# (source: http://www.ietf.org/rfc/rfc2616.txt, p114)
#
# A negative value means that the response expires immediately.
# Nginx automatically sets the `Cache-Control: no-cache` header, if `expires` is negative
#
expires -1;
}
# this block will catch files that might need to change immediately (e. g. to deploy hotfixes), such as js or css
# The ?: prefix is a 'non-capturing' mark, meaning we do not require
# the pattern to be captured into $1 which should help improve performance
location ~* \.(?:css|js)$ {
access_log off;
log_not_found off;
# no-cache: forces caches to submit the request to the origin server for validation before releasing a
# cached copy, every time. This is useful to assure that authentication is respected
# (in combination with public), or to maintain rigid freshness, without sacrificing all of the
# benefits of caching.
#
# public: marks authenticated responses as cacheable; normally, if HTTP authentication is required,
# responses are automatically private.
#
# must-revalidate: tells caches that they must obey any freshness information you give them about a
# representation. HTTP allows caches to serve stale representations under special conditions;
# by specifying this header, you’re telling the cache that you want it to strictly follow
# your rules.
#
# proxy-revalidate: similar to must-revalidate, except that it only applies to proxy caches.
#
add_header Cache-Control "no-cache, public, must-revalidate, proxy-revalidate";
}
# This block will catch static file requests, such as images
# The ?: prefix is a 'non-capturing' mark, meaning we do not require
# the pattern to be captured into $1 which should help improve performance
location ~* \.(?:jpg|jpeg|gif|png|ico|xml|webp)$ {
access_log off;
log_not_found off;
# The Expires HTTP header is a basic means of controlling caches; it tells all caches how long
# the associated representation is fresh for. After that time, caches will always check back with
# the origin server to see if a document is changed.
#
# "If a request includes the no-cache directive, it SHOULD NOT include min-fresh, max-stale, or max-age."
# (source: http://www.ietf.org/rfc/rfc2616.txt, p114)
#
# Nginx automatically sets the `Cache-Control: max-age=t` header, if `expires` is present, where t is a time
# specified in the directive, in seconds. Shortcuts for time can be used, for example 5m for 5 minutes.
#
expires 5m;
# public: marks authenticated responses as cacheable; normally, if HTTP authentication is required,
# responses are automatically private.
#
add_header Cache-Control "public";
}
# This block will catch static file requests of fonts and allows fonts to be requested via CORS
# The ?: prefix is a 'non-capturing' mark, meaning we do not require
# the pattern to be captured into $1 which should help improve performance
location ~* \.(?:eot|woff|woff2|ttf|svg|otf) {
access_log off;
log_not_found off;
# The Expires HTTP header is a basic means of controlling caches; it tells all caches how long
# the associated representation is fresh for. After that time, caches will always check back with
# the origin server to see if a document is changed.
#
# "If a request includes the no-cache directive, it SHOULD NOT include min-fresh, max-stale, or max-age."
# (source: http://www.ietf.org/rfc/rfc2616.txt, p114)
#
# Nginx automatically sets the `Cache-Control: max-age=t` header, if `expires` is present, where t is a time
# specified in the directive, in seconds. Shortcuts for time can be used, for example 5m for 5 minutes.
#
expires 5m;
# public: marks authenticated responses as cacheable; normally, if HTTP authentication is required,
# responses are automatically private.
#
add_header Cache-Control "public";
# allow CORS requests
add_header Access-Control-Allow-Origin *;
types {font/opentype otf;}
types {application/vnd.ms-fontobject eot;}
types {font/truetype ttf;}
types {application/font-woff woff;}
types {font/x-woff woff2;}
types {image/svg+xml svg svgz;}
}
# this prevents hidden files (beginning with a period) from being served
location ~ /\. {
access_log off;
log_not_found off;
deny all;
}
}
@whitwhoa
Copy link

Thank you for taking the time to post this. It is insanely useful!

@TemKaSD
Copy link

TemKaSD commented Mar 12, 2019

Спасибо.
Thank you.

@pkiula
Copy link

pkiula commented Aug 2, 2019

Thank you so much for this. Love the web for how people share their knowledge and time. Question:

expires  5m; 

Is that 5 minutes, or 5 months?

Secondly, for fonts, do we need must-revalidate or proxy-revalidate?

Thirdly, only for woff, I see people turn off gzipping. Is this needed?

    # woff fonts should not be zipped.
    location ~* \.(?:woff)$ {
      root $rootUrl;
      expires max;
      add_header Cache-Control public;
      access_log off;
    }

Thanks!

@philipstanislaus
Copy link
Author

@pkiula happy to hear that I could help.

Question:

expires  5m; 

Is that 5 minutes, or 5 months?

Good point, it is 5 minutes. I'll add that to the snippet.

Secondly, for fonts, do we need must-revalidate or proxy-revalidate?

No, we don't need it, but I would still add those directives. The reason for must-revalidate is that some clients might accept stale responses, but you generally want to prevent sending out things that became stale. Imagine you have your own font and update it to a newer version, without any naming convention for the new version. While this example might not be relevant to you, I'd rather play it safe than being sorry, especially if you copy paste this nginx configuration to different setups.

Thirdly, only for woff, I see people turn off gzipping. Is this needed?

    # woff fonts should not be zipped.
    location ~* \.(?:woff)$ {
      root $rootUrl;
      expires max;
      add_header Cache-Control public;
      access_log off;
    }

WOFF files are natively compressed, maybe that's the rationale behind that practice. The compression does not seem to be good though, see h5bp/server-configs-apache#42, so I'd leave gzipping on.

@pkiula
Copy link

pkiula commented Aug 5, 2019

Thank you so much. Very helpful!

@samishk87
Copy link

I have tried the above configuration file, but the issue is all my website images displayed broken, how can I fix the issue ? following is my configuration

proxy_cache_path   /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=9000g inactive=30d;
proxy_temp_path    /var/cache/nginx/tmp;
server {
    listen 80;
   # listen [::]:80 ipv6only=on default_server;
    server_name XXXXXXXXX; #our domain
   # location / {

        location /  {

        expires 30d;
    #   add_header Cache-Control "public, no-transform";

         proxy_cache my_cache;

                       proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

                       proxy_ignore_headers Set-Cookie;

                       proxy_set_header     Host          $host;

                       proxy_set_header     X-Real-IP         $remote_addr;




       proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       # rewrite ^(.*)$ /$1 break;
        proxy_pass http://127.0.0.1:8080/;
        set $do_not_cache 0;

                if ($request_uri ~* ^(/secure/admin|/plugins|/secure/project)) {
                        set $do_not_cache 1;
                }

                proxy_cache_key         "$scheme://$host$request_uri";
                proxy_cache_bypass      $do_not_cache;
                proxy_cache_valid       1440m;
                proxy_cache_min_uses    1;



        client_max_body_size 10M;
    }
  location ~* \.(?:css|js)$ {
    access_log        off;
    log_not_found     off;
    add_header        Cache-Control "no-cache, public, must-revalidate, proxy-revalidate";
        }




        location ~* \.(?:jpg|jpeg|gif|png|ico|xml)$     {
        proxy_cache my_cache;
    access_log        off;
    log_not_found     off;
    expires           365d;
    add_header        Cache-Control "public";
       }

        location ~* \.(?:eot|woff|woff2|ttf|svg|otf) {
                proxy_cache my_cache;
    access_log        off;
    log_not_found     off;
    expires           30d;
    add_header        Cache-Control "public, no-transform";
        add_header        Access-Control-Allow-Origin *;

        }
        location ~ /\. {
    access_log        off;
    log_not_found     off;
    deny              all;
  }

   }

@philipstanislaus
Copy link
Author

@samishk87 your configuration looks fine to me. Have you looked at the logs from nginx? Otherwise, this would be a good question for StackOverflow.

@meness
Copy link

meness commented Jul 5, 2020

Since you used .domain.com to support all subdomains including the main domain, you should use $server_name for root, access_log, and error_log as well. This makes your configuration more dynamic.

root /usr/local/www/$server_name;

access_log /var/log/nginx/$server_name.access.log;
error_log /var/log/nginx/$server_name.error.log;

@philipstanislaus
Copy link
Author

Since you used .domain.com to support all subdomains including the main domain, you should use $server_name for root, access_log, and error_log as well. This makes your configuration more dynamic.

root /usr/local/www/$server_name;

access_log /var/log/nginx/$server_name.access.log;
error_log /var/log/nginx/$server_name.error.log;

Good point, updated. Thanks for the contribution!

@meness
Copy link

meness commented Jul 7, 2020

@philipstanislaus It seems the only variable that can be used with access_log and error_log is $host, but $server_name works perfectly with root.

@philipstanislaus
Copy link
Author

Thanks @meness, updated!

@sweoggy
Copy link

sweoggy commented Dec 14, 2020

@philipstanislaus I had to add types {image/svg+xml svg;} to the SVG location block, in order for SVGs to still be served as images (otherwise Nginx served them as octet-stream).

@philipstanislaus
Copy link
Author

Noted, thanks @sweoggy

@tomwojcik
Copy link

@sweoggy I just run into this issue and won't admit how much time I spent debugging this issue. @philipstanislaus, please edit this gist and add missing svg type. 🙏

@philipstanislaus
Copy link
Author

Updated the snippet, thanks for the reminder @tomwojcik

@DiedeGu
Copy link

DiedeGu commented Jul 7, 2021

Hi @philipstanislaus

Thanks for making this file, it good!

I have an optional addition for the image files, it adds webp format. webp is now supported by Apple since november of 2020 from BigSur onwards so it could be ok to add on default I think.

location ~* \.(?:jpg|jpeg|gif|png|ico|xml|webp)$ {

Thanks again for writing the file !

@philipstanislaus
Copy link
Author

philipstanislaus commented Jul 7, 2021

Added, thanks for the contribution @DiedeGu 👍

@slaguiban
Copy link

Thank you

@whittlem
Copy link

This is great, thanks.

@mrkrash
Copy link

mrkrash commented Jun 27, 2023

wow, thank you. Only one question: $server_name var where is defined?

@philipstanislaus
Copy link
Author

wow, thank you. Only one question: $server_name var where is defined?

Sure, it is defined in line 33 of the snippet: https://gist.github.com/philipstanislaus/654adafad91efb6de230845b5bdeae61#file-sane-caching-nginx-conf-L33

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment