Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with Varnish Cache and different browsers #561

Open
rsmith4321 opened this issue Dec 6, 2024 · 3 comments
Open

Issue with Varnish Cache and different browsers #561

rsmith4321 opened this issue Dec 6, 2024 · 3 comments

Comments

@rsmith4321
Copy link

rsmith4321 commented Dec 6, 2024

CloudPanel version(s) affected

2.5.0

Description

The varnish cache settings cause issues where Chrome and Safari create separate cache. You can see this in the x-cache-age header times being different for both.

How to reproduce

Visit cached pages in different browsers

Possible Solution

This change to the default.vcl file seems to fix the issue. Please check it out and possibly merge the change.

Just replace the block if (req.http.Accept-Encoding) { if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf|flv)$") { # No point in compressing these unset req.http.Accept-Encoding; } elsif (req.http.Accept-Encoding ~ "gzip") { set req.http.Accept-Encoding = "gzip"; } elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") { set req.http.Accept-Encoding = "deflate"; } else { # unknown algorithm unset req.http.Accept-Encoding; } } with if (req.http.Accept-Encoding) { # Files that don't benefit from additional compression: if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf|flv)$") { # No point in compressing these further unset req.http.Accept-Encoding; } else { # Otherwise, force gzip if supported, unset if not if (req.http.Accept-Encoding ~ "gzip") { set req.http.Accept-Encoding = "gzip"; } else { unset req.http.Accept-Encoding; } } } and Chrome and Safari will use the same page cache. You can check the x-cache-age headers and they sync up. It might even be better to enable br support instead of just gzip but I didn't want to complicate things, I know br isn't natively supported by Varnish.

Additional Context

No response

@rsmith4321
Copy link
Author

rsmith4321 commented Dec 7, 2024

Actually I've done a lot more testing and working on the code. My previous fix didn't really seem to work. Based on the Wordpress recommendation from Varnish I've come up with this final version that seems to fix all the caching issues. Could you please look into implementing this in Cloudpanel?

import std;

backend default {
    .host = "127.0.0.1";
    .port = "8080";
    .first_byte_timeout = 600s;
}

acl purge {
    "localhost";
    "127.0.0.1";
    "172.17.0.1";
}

sub vcl_recv {
    # Remove empty query strings like ? at the end
    if (req.url ~ "\?$") {
        set req.url = regsub(req.url, "\?$", "");
    }

    # Strip port from Host header
    set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");

    # Sort query parameters to normalize the URL for caching
    set req.url = std.querysort(req.url);

    # Remove the proxy header (httpoxy vulnerability)
    unset req.http.proxy;

    # Set X-Forwarded-Proto based on server port
    if (!req.http.X-Forwarded-Proto) {
        if(std.port(server.ip) == 443 || std.port(server.ip) == 8443) {
            set req.http.X-Forwarded-Proto = "https";
        } else {
            set req.http.X-Forwarded-Proto = "http";
        }
    }

    # PURGE logic for the Proxy Cache Purge plugin
    if(req.method == "PURGE") {
        if(!client.ip ~ purge) {
            return(synth(405,"PURGE not allowed for this IP address"));
        }
        if (req.http.X-Purge-Method == "regex") {
            ban("obj.http.x-url ~ " + req.url + " && obj.http.x-host == " + req.http.host);
            return(synth(200, "Purged"));
        }
        ban("obj.http.x-url == " + req.url + " && obj.http.x-host == " + req.http.host);
        return(synth(200, "Purged"));
    }

    # Allow only specific HTTP methods to avoid issues
    if (
        req.method != "GET" &&
        req.method != "HEAD" &&
        req.method != "PUT" &&
        req.method != "POST" &&
        req.method != "PATCH" &&
        req.method != "TRACE" &&
        req.method != "OPTIONS" &&
        req.method != "DELETE"
    ) {
        return (pipe);
    }

    # Remove tracking parameters (utm_*, gclid, etc.)
    if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
        set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "");
        set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?");
        set req.url = regsub(req.url, "\?&", "?");
        set req.url = regsub(req.url, "\?$", "");
    }

    # Only cache GET and HEAD
    if (req.method != "GET" && req.method != "HEAD") {
        set req.http.X-Cacheable = "NO:REQUEST-METHOD";
        return(pass);
    }

    # Identify static files and remove cookies for them
    if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
        set req.http.X-Static-File = "true";
        unset req.http.Cookie;
    }

    # WordPress/WooCommerce dynamic pages and logged-in users: don't cache
    if (
        req.http.Cookie ~ "wordpress_(?!test_)[a-zA-Z0-9_]+|wp-postpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+|wordpress_logged_in_|comment_author|PHPSESSID" ||
        req.http.Authorization ||
        req.url ~ "add_to_cart" ||
        req.url ~ "edd_action" ||
        req.url ~ "nocache" ||
        req.url ~ "^/addons" ||
        req.url ~ "^/bb-admin" ||
        req.url ~ "^/bb-login.php" ||
        req.url ~ "^/bb-reset-password.php" ||
        req.url ~ "^/cart" ||
        req.url ~ "^/checkout" ||
        req.url ~ "^/control.php" ||
        req.url ~ "^/login" ||
        req.url ~ "^/logout" ||
        req.url ~ "^/lost-password" ||
        req.url ~ "^/my-account" ||
        req.url ~ "^/product" ||
        req.url ~ "^/register" ||
        req.url ~ "^/register.php" ||
        req.url ~ "^/server-status" ||
        req.url ~ "^/signin" ||
        req.url ~ "^/signup" ||
        req.url ~ "^/stats" ||
        req.url ~ "^/wc-api" ||
        req.url ~ "^/wp-admin" ||
        req.url ~ "^/wp-comments-post.php" ||
        req.url ~ "^/wp-cron.php" ||
        req.url ~ "^/wp-login.php" ||
        req.url ~ "^/wp-activate.php" ||
        req.url ~ "^/wp-mail.php" ||
        req.url ~ "^\?add-to-cart=" ||
        req.url ~ "^\?wc-api=" ||
        req.url ~ "^/preview=" ||
        req.url ~ "^/\.well-known/acme-challenge/"
    ) {
        set req.http.X-Cacheable = "NO:Logged in/Got Sessions";
        if(req.http.X-Requested-With == "XMLHttpRequest") {
            set req.http.X-Cacheable = "NO:Ajax";
        }
        return(pass);
    }

    # Normalize and unify headers to prevent separate caching for Chrome/Safari
    if (req.http.Accept-Encoding) {
        if (req.http.Accept-Encoding ~ "br" || req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
        } else {
            unset req.http.Accept-Encoding;
        }
    }

    # Remove headers that vary by browser
    unset req.http.User-Agent;
    unset req.http.Accept;
    unset req.http.Accept-Language;
    unset req.http.Sec-Fetch-Mode;
    unset req.http.Sec-Fetch-Site;
    unset req.http.Sec-Fetch-User;
    unset req.http.Upgrade-Insecure-Requests;

    # Remove any remaining cookies
    unset req.http.Cookie;

    return(hash);
}

sub vcl_hash {
    if(req.http.X-Forwarded-Proto) {
        hash_data(req.http.X-Forwarded-Proto);
    }
}

sub vcl_backend_response {
    # Inject URL & Host for purge/banning
    set beresp.http.x-url = bereq.url;
    set beresp.http.x-host = bereq.http.host;

    # Default TTL if no Cache-Control
    if (!beresp.http.Cache-Control) {
        set beresp.ttl = 1h;
        set beresp.http.X-Cacheable = "YES:Forced";
    }

    # Longer TTL for static files
    if (bereq.http.X-Static-File == "true") {
        unset beresp.http.Set-Cookie;
        set beresp.http.X-Cacheable = "YES:Forced";
        set beresp.ttl = 1d;
    }

    # Remove Wordfence cookies
    if (beresp.http.Set-Cookie ~ "wfvt_|wordfence_verifiedHuman") {
        unset beresp.http.Set-Cookie;
    }

    if (beresp.http.Set-Cookie) {
        set beresp.http.X-Cacheable = "NO:Got Cookies";
    } elseif(beresp.http.Cache-Control ~ "private") {
        set beresp.http.X-Cacheable = "NO:Cache-Control=private";
    }

    # Remove Accept-Language from Vary to unify caching
    if (beresp.http.Vary) {
        set beresp.http.Vary = regsub(beresp.http.Vary, "(,?\s*Accept-Language\s*)", "");
        set beresp.http.Vary = regsub(beresp.http.Vary, "^,\s*|\s*,\s*$", "");
        if (beresp.http.Vary == "") {
            unset beresp.http.Vary;
        }
    }
}

sub vcl_deliver {
    # Add X-Cache-Age based on Age
    if (resp.http.Age) {
        set resp.http.X-Cache-Age = resp.http.Age;
        unset resp.http.Age;
    }

    # Indicate HIT or MISS
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }

    # Debug header
    if(req.http.X-Cacheable) {
        set resp.http.X-Cacheable = req.http.X-Cacheable;
    } elseif(obj.uncacheable) {
        if(!resp.http.X-Cacheable) {
            set resp.http.X-Cacheable = "NO:UNCACHEABLE";
        }
    } elseif(!resp.http.X-Cacheable) {
        set resp.http.X-Cacheable = "YES";
    }

    # Cleanup internal headers
    unset resp.http.x-url;
    unset resp.http.x-host;
}

@af1
Copy link

af1 commented Feb 21, 2025

Same issue here with /etc/varnish/default.vcl adding this to the mix, which messes up caching:


    # Avoid being cached by the browser.
     if (resp.http.Cache-Control !~ "private") {
       set resp.http.Pragma = "no-cache";
       set resp.http.Expires = "-1";
       set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
     }

@rsmith4321 did a much deeper dive, but I stumbled across this troubleshooting the cache and wanted to add support for this fix.

@cloudpanel-io
Copy link
Owner

Hi @rsmith4321 ,

i can't reproduce it. I have setup a wordpress site and i get the same "x-cache-age" value in chrome and safari.

Would you mind getting in touch with me in discord?

Our discord server: https://discord.cloudpanel.io

Get in touch with stefan.wieczorek there.

Thanks in advance.

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

No branches or pull requests

3 participants