Task: Configure Varnish caching

Configure Varnish caching

Ворклоги

Static resource caching configured via Varnish

Proxying of static resources via Varnish with a 7-day TTL has been configured for the gorodskie-bani.ru website. JS, CSS, fonts, images, icons, and /styles/ resources from the tileserver pass through the cache. Dynamic pages are intentionally not cached and continue to be processed directly by the application (return (pass)).

Why this is needed

The main goal of caching is to serve immutable and rarely changing resources not from the Node.js application and tileserver on every request, but directly from the Varnish memory/cache.

This provides the site with several practical benefits:

  • Static delivery time is reduced. After the first request, the resource enters the cache, and subsequent requests are handled by Varnish without contacting the backend.
  • Load on the application and tileserver is reduced. JS, CSS, images, fonts, and map styles do not create recurring load on the backend containers.
  • Stability under load is increased. During simultaneous user visits, the majority of requests for static assets are handled at the caching proxy level.
  • Page loading speeds up for users. Fast delivery of CSS, JS, fonts, and images reduces the wait time for resources required to render the page.
  • A positive technical effect for SEO is created. Loading speed and user performance metrics, including those related to Core Web Vitals, are part of the site's technical quality. Varnish does not directly change search rankings, but it helps reduce network latency and origin load, creating more stable conditions for fast loading and crawling by search bots.

Varnish configuration

Two backends are configured:

  • the main application backend — gorodskie-bani--agent-app-1:3000;
  • a separate tileserver backend — gorodskie-bani-ru-tileserver-1:80.
vcl 4.1;

backend default {
    .host = "gorodskie-bani--agent-app-1";
    .port = "3000";
}

backend tileserver {
    .host = "gorodskie-bani-ru-tileserver-1";
    .port = "80";
}

In vcl_recv, requests to /styles/ are sent to the tileserver. For cacheable resources, Cookies are stripped so that user cookies do not fragment the cache and prevent the reuse of a single object by different requests.

sub vcl_recv {
    if (req.url ~ "^/styles/") {
        set req.backend_hint = tileserver;
        unset req.http.Cookie;
        return (hash);
    }
    if (req.url ~ "\.(js|css|woff2?|ttf|eot|svg|ico|png|jpg|jpeg|gif|webp|avif)(\?.*)?$") {
        unset req.http.Cookie;
        return (hash);
    }
    return (pass);
}

All other requests go through pass, meaning HTML and dynamic application responses are not cached by this rule. This reduces the risk of serving outdated personalized or dynamic content.

The cache key is built from the URL and host:

sub vcl_hash {
    hash_data(req.url);
    hash_data(req.http.host);
    return (lookup);
}

This allows resources from different URLs to be stored separately and prevents cache mixing between hosts. The query string remains part of req.url, so asset versions with cache-busting parameters receive separate cache entries.

A 7-day TTL is set for /styles/ and static files, and Set-Cookie is removed from cacheable backend responses:

sub vcl_backend_response {
    if (bereq.url ~ "^/styles/") {
        set beresp.ttl = 7d;
        unset beresp.http.Set-Cookie;
    }
    if (bereq.url ~ "\.(js|css|woff2?|ttf|eot|svg|ico|png|jpg|jpeg|gif|webp|avif)(\?.*)?$") {
        set beresp.ttl = 7d;
        unset beresp.http.Set-Cookie;
    }
}

A 7-day TTL allows serving repeated requests from Varnish for a long time, while the standard approach using versioned filenames or query parameters makes it possible to fetch a new version of a resource after deployment without waiting for the old cache to expire.

Service headers X-Cache and X-Cache-TTL have been added for diagnostics:

sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
    set resp.http.X-Cache-TTL = obj.ttl;
}

These allow you to quickly verify the actual operation of the cache: the first request is usually returned as MISS, subsequent ones as HIT, and X-Cache-TTL shows the remaining time-to-live of the object.

Traefik routing

To ensure that the required requests actually end up in Varnish, separate high-priority routers have been added to Traefik.

Static files:

gorodskie-bani.ru-static:
  rule: 'Host(`gorodskie-bani.ru`) && PathRegexp(`^.*\.(js|css|woff2?|ttf|eot|svg|ico|png|jpg|jpeg|gif|webp|avif)$`)'
  entryPoints:
    - websec
  service: gorodskie-bani.ru-varnish
  tls:
    certResolver: letsencrypt
  priority: 200

/styles resources:

gorodskie-bani.ru-styles:
  rule: "Host(`gorodskie-bani.ru`) && PathPrefix(`/styles`)"
  entryPoints:
    - websec
  middlewares: []
  service: gorodskie-bani.ru-varnish
  tls:
    certResolver: letsencrypt
  priority: 200

Thus, Traefik separates cacheable requests at the entrance and forwards them to the Varnish service, after which Varnish either serves the object from the cache or fetches it from the corresponding backend and stores it for 7 days.

Summary for the website and SEO

As a result, the static part of gorodskie-bani.ru is served through a separate caching layer. This reduces the number of requests to the application and tileserver, accelerates the reloading of resources, and makes response times more stable under traffic growth.

For SEO, this is primarily useful as an infrastructure performance optimization: the browser receives critical CSS/JS/fonts/images faster, the backend competes less for resources with static requests, and the site maintains normal response speeds under load more reliably. Collectively, this benefits the site's technical quality and user experience, which are important for search visibility and organic traffic efficiency.