WooCommerce and Nginx’s fastcgi_cache

If you are using Nginx’s fastcgi_cache and WooCommerce, then you may run into issues if you end up caching your cart/checkout pages!

Following are some ways to tweak Nginx config so cache won’t cost you business!

Please be careful while using them. Using them all together with other rules might lead to side-effects!

Skip cache on WooCommerce pages

Depending on your WooCommerce settings, you may need to change some values below:

if ($request_uri ~* "/store.*|/cart.*|/my-account.*|/checkout.*|/addons.*") {
         set $skip_cache 1;
}

Skip cache for WooCommerce query string

We generally shouldn’t cache output of query strings but in case you are already caching, you can use this fragment

if ( $arg_add-to-cart != "" ) { 
      set $skip_cache 1;
}

Skip cache when WooCommerce cart is not empty

If you have a heavy site, you may to cache store pages for fresh visitors. Its always good to have fast-loading store as it might impact conversion.

WooCommerce uses a cookie to track number of items a user has added to cart.

if ( $cookie_woocommerce_items_in_cart != "0" ) { 
    set $skip_cache 1;
}

Please note, if you use above rule only Nginx will skip cache for entire site as soon as a visitor adds a product in cart. You may want to limit cache to store section only.

Fastcgi Cache Issue

Replace following:

  location ~ \.php$ {

        try_files $uri =404;

By:

  location ~ \.php$ {
        set $rt_session "";

        if ($http_cookie ~* "wc_session_cookie_[^=]*=([^%]+)%7C") {
                    set $rt_session wc_session_cookie_$1;
            }   

        if ($skip_cache = 0 ) {
            more_clear_headers "Set-Cookie*";
            set $rt_session "";
        }

            fastcgi_cache_key "$scheme$request_method$host$request_uri$rt_session";

        try_files $uri =404;

 

Config Links

Below are links to complete WordPress-Nginx fastcgi config:

30 responses to “WooCommerce and Nginx’s fastcgi_cache”

  1. I already had added these exceptions but they look slightly different than yours. HEre is mine:

    if ($request_uri ~* "(/wp-admin/|/cart/|/checkout/|/account/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
    set $no_cache 1;
    

    }

    What is the difference between /cart.* and /cart/ ? I do understand * is a wild card but wouldn’t /cart/whatever not match /cart/ too? Should I change mine to reflect yours?

    Regarding the other two IF statements, can I simply add them somewhere after/before the other if statements in that config file?

    • /cart/ will not match /cart/whatever

      /cart.* will match /cart, /cart/, /cart/whatever. Also some unwanted pattern /cart2, /cart-foo etc

      I will recommend using /cart.* or /cart/.*. Though if current config is working, no need to bother about it.

  2. this is exactly what i was looking for! thanks : )

    minor typo in the first bit of code, i think remove the bold bracket, right?:

    if ($request_uri ~* “(/store.|/cart.|/my-account.|/checkout.|/addons.*”) {
    set $skip_cache 1;
    }

  3. btw. in the tutorial I got started with this I remember you using: set $no_cache 1; but here you are using set $skip_cache 1; – is there a difference?

  4. oh and another question as you guys confused me with the brackets mentioned earlier so please can you tell me if the brackets here are correct before I screw up? And do you spot anything unneccessary or wrong here?

    # Don't cache uris containing the following segments
    if ($request_uri ~* ("/wp-admin.*|/cart.*|/checkout.*|/account.*|/myaccount.*|/addond.*|/store.*|/shop.*|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-ocations.php|sitemap(_index)?.xml|a-z0-9_-]+-sitemap([0-9]+)?.xml)") { set $no_cache 1; }
    • In regex, brackets are used for grouping symbols/patterns.

      If a bracket is seriously misplaced, Nginx will refuse to start.

      Surrounding patterns with extra brackets usually do not harm. Always test config after you reload for expected and unexpected cases.

      As far your line above, bracket before opening " and after closing " is redundant. I think I have placed such redundant brackets in some of my article unintentionally. I will try to remove them soon to avoid confusion but as I said they are harmless!

  5. Great tutorials and great plugins. Thank you for all your contributions to the community. So much for congeniality, here comes the confrontation. ๐Ÿ˜‰

    How are you avoiding session collisions? I have a very similar nginx cacheing configuration as you present in your tutorials as well as the additions listed above yet I end up with cart contents being shared across visitors. Woocommerce sets the session cookie on every request (even if you have no cart contents), so what ends up happening is that all of the pages that are cached contain a session cookie which subsequently gets sent to the next visitor giving them access to a session that is not theirs. I haven’t figured out how you can avoid this without using fastcgi_hide_header. Since fastcgi_hide_header can’t be inside an “if” it’s tricky to block cookies on only some of the pages using “location” blocks. Which essentially leads to a site that has little to no cacheing. Also leads me to believe that there are probably no high volume e-commerce sites running woocommerce.

    Is there something I’ve missed in your tutorials that addresses this issue?

    Thanks!

    • @Victor

      We are not caching woocommerce pages so session cookie is working properly.

      I noticed that woocommerce sends session cookie for woocommerce pages only. For other sites area, like this page, there is no wc_session_cookie_*

      • Quick test shows woocommerce is sending the session cookie for your home page:

        curl -I http://rtcamp.com
        HTTP/1.1 200 OK
        Server: nginx
        Date: Wed, 23 Oct 2013 13:39:36 GMT
        Content-Type: text/html; charset=UTF-8
        Connection: keep-alive
        Vary: Accept-Encoding
        Set-Cookie: PHPSESSID=81r93h76vcovj4kb4npl1tlq00; path=/
        Expires: Thu, 19 Nov 1981 08:52:00 GMT
        Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
        Pragma: no-cache
        Set-Cookie: wc_session_cookie_8e1219dc8c43e03cb3e7e361edcca6db=2uEomZYXHUVZTzOskCk54WmjrdmRjXiY%7C%7C1382705816%7C%7C1382702216%7C%7Cd83e3442da474fd4851abd056816ef8e; expires=Fri, 25-Oct-2013 12:56:56 GMT; Max-Age=172800; path=/; domain=rtcamp.com; httponly
        Set-Cookie: woocommerce_items_in_cart=0; expires=Wed, 23-Oct-2013 11:56:56 GMT; Max-Age=-3600; path=/; domain=rtcamp.com
        Set-Cookie: woocommerce_cart_hash=0; expires=Wed, 23-Oct-2013 11:56:56 GMT; Max-Age=-3600; path=/; domain=rtcamp.com
        X-Pingback: http://rtcamp.com/xmlrpc.php
        X-Powered-By: EasyEngine
        rt-Fastcgi-Cache: HIT
        • Yep. Noticed it just now.

          I am not sure if something has changed about woocoomerce because last time I checked, on homepage and other area there were only woocommerce_items_in_cart and woocommerce_cart_hash cookies!

          Not sure why but in reality, no user has ever reported any issue with store-section. Just 2-days back we recorded highest sales!

          I digged deeper and I found even though for other site areas wc_session_cookie getting cached, store ares (explicitly non-cached) is returning unique wc_session_cookie values.

          By the way, I just checked woocommerce docs and found this – http://docs.woothemes.com/document/configuring-caching-plugins/

          So may be we can ignore wc_session_cookie

          If you have any suggestions or thoughts, feel free to share.

        • Update:

          I just checked that PHP SessionID are also cached.

          Now, caching and session are not conflicting because as soon as we have something useful inside session (logged-in user, product in cart, etc), we stop’s caching.

          I guess this saves us from any side-effect of session-getting-cached! This makes sense because we don’t need sessions in the first place for cacheable areas of site.

          • I see what you are saying but in practice this is not what is happening. I just reproduced a session collision on your site.

            I think the reason you are not hearing about issues on your site is because the timing of the sales is such that the cache purge timeout is reached and the cached page which includes the cookie is deleted before your next customer.

            I wonder if using unique fastcgi_cache locations for HTTP and HTTPS would solve this problem.

            What’s the bet way to get in direct contact with you, Rahul? You have my email address so feel free to email me. I would like to discuss this issue further. Also I made some modifications to your nginx-helper plugin and wanted to get your input before creating a pull request.

            Thanks!

          • I think its about 2-sides of coins rather than timing. I cache very aggressively but as store comes under non-cacheable area, session-collision might not resulting in any loss.

  6. basket problem!

    using fastcgi_cache i had a problem with basket in header of my site!

    is there a way to solve?

  7. Hi Rahul, trying to adapt this for MarketPress, I ran into:
    nginx: [emerg] unknown directive "more_clear_headers"
    What am I missing?

    • OK, strike that. I did not have the ngx_headers_more module… Fixed ๐Ÿ™‚

  8. Hi Rahul,

    Trying to adapt this for MarketPress (part2) I changed

    if ($http_cookie ~* "wc_session_cookie_[^=]*=([^%]+)%7C") {
    set $rt_session wc_session_cookie_$1;
    }

    to

    if ($http_cookie ~* "mp_globalcart_[^=]*=([^%]+)%7C") {
    set $rt_session mp_globalcart_$1;
    }

    but I’m not clear on what that actually does. I have the impression that it aims to cache sessions where a cart cookie IS set. But that’s not what I want (yet).

    First, I need to make it cache requests that do NOT have a cart cookie set. But as far as I can figure, a start_session() on each request (what MarketPress does) blocks all caching. IS that true? If so, I need to prevent the start_session() in the MarketPress plugin to not block caching…

    Is that even possible from the nginx.conf side? Or do I need to hack the plugin?

    • I am not sure about marketplace addon.

      On WooCommerce site, that codes server cached content to the user as long as he hasn’t added anything to cart OR not yet visited store.

  9. Hey sorry if this is a dumb question, but what files are these changes made in? I’m not 100 percent and a bit confused, thanks!

    • These changes need to be made in the nginx config file. In case you’re using EE3, the file location will be the location conf inside /etc/nginx/sites-available/sitename.tld. However, if you are using the latest version of EasyEngine, it will not support Nginx’s fastcgi_cache.

  10. When I include this I don’t get any pages cached:

    if ($query_string != “”) {
    set $skip_cache 1;
    set $skip_reason “query”;
    }

    Is it save to remove this rule, or what side effects would it cause?