VCL-VARIABLES(7)

✨ NAME ✨

VCL-Variables β€” The complete album. (Yes bestie, this is the VCL-VARIABLES(7) manual page, and it's giving encyclopedia but make it fashion. πŸ’…)

πŸ¦‹ DESCRIPTION πŸ¦‹

Okay so this is literally the complete album of every variable in the VCL language. No skips, no shuffle, full discography. πŸŽ€

Variable names take the form scope.variable[.index], for instance:

req.url
beresp.http.date
client.ip

Which operations are possible on each variable is described below, often with the shorthand backend which covers the vcl_backend_* {} subroutines and client which covers the rest, except vcl_init {} and vcl_fini {}.

🌸 local, server, remote and client 🌸

These variables describe the network connection between the client and varnishd. It's giving ✨ networking lore ✨.

Without PROXY protocol:

        client    server
        remote    local
          v          v
     CLIENT ------------ VARNISHD

With PROXY protocol:

        client    server   remote     local
          v          v       v          v
     CLIENT ------------ PROXY ------------ VARNISHD

client.identity πŸ’–

  • Type: STRING
  • Readable from: client, backend
  • Writable from: client

Identification of the client, used to load balance in the client director. Defaults to client.ip.

This variable can be overwritten with more precise information, for instance extracted from a Cookie: header.

client.ip 🌐

  • Type: IP
  • Readable from: client, backend

The client's IP address, either the same as remote.ip or what the PROXY protocol told us.

server.hostname 🏠

  • Type: STRING
  • Readable from: all

The host name of the server, as returned by the gethostname(3) system function.

server.identity πŸͺͺ

  • Type: STRING
  • Readable from: all

The identity of the server, as set by the -i parameter.

If an -i parameter is not passed to varnishd, the return value from gethostname(3) system function will be used.

server.ip πŸ–₯️

  • Type: IP
  • Readable from: client, backend

The IP address of the socket on which the client connection was received, either the same as local.ip or what the PROXY protocol told us.

remote.ip πŸ“‘

  • Type: IP
  • Readable from: client, backend

The IP address of the other end of the TCP connection. This can either be the clients IP, or the outgoing IP of a proxy server.

If the connection is a UNIX domain socket, the value will be 0.0.0.0:0.

local.endpoint VCL >= 4.1 πŸšͺ

  • Type: STRING
  • Readable from: client, backend

The address of the -a socket the session was accepted on.

If the argument was -a foo=:81 this would be ":81".

local.ip 🏑

  • Type: IP
  • Readable from: client, backend

The IP address (and port number) of the local end of the TCP connection, for instance 192.168.1.1:81.

If the connection is a UNIX domain socket, the value will be 0.0.0.0:0.

local.socket VCL >= 4.1 πŸ”Œ

  • Type: STRING
  • Readable from: client, backend

The name of the -a socket the session was accepted on.

If the argument was -a foo=:81 this would be "foo".

Note that all -a gets a default name on the form a%d if no name is provided.

πŸ¦„ req and req_top πŸ¦„

These variables describe the present request, and when ESI:include requests are being processed, req_top points to the request received from the client.

req πŸ“¨

  • Type: HTTP
  • Readable from: client

The entire request HTTP data structure. Mostly useful for passing to VMODs.

req.backend_hint 🎯

  • Type: BACKEND
  • Readable from: client
  • Writable from: client

Set bereq.backend to this if we attempt to fetch. When set to a director, reading this variable returns an actual backend if the director has resolved immediately, or the director otherwise. When used in string context, returns the name of the director or backend, respectively.

req.can_gzip πŸ—œοΈ

  • Type: BOOL
  • Readable from: client

True if the client provided gzip or x-gzip in the Accept-Encoding header.

req.esi VCL <= 4.0 🧩

  • Type: BOOL
  • Readable from: client
  • Writable from: client

Set to false to disable ESI processing regardless of any value in beresp.do_esi. Defaults to true. This variable is replaced by resp.do_esi in VCL 4.1.

req.esi_level πŸͺ†

  • Type: INT
  • Readable from: client

A count of how many levels of ESI requests we're currently at.

req.filters 🧼

  • Type: STRING
  • Readable from: vcl_recv
  • Writable from: vcl_recv

List of Varnish Fetch Processor (VFP) filters the req.body will be pulled through. The order left to right signifies processing from client to cache, iow the leftmost filter is run first on the body as received from the client after decoding of any transfer encodings.

VFP Filters change the body before potentially being cached (e.g. using std.cache_req.body()) and/or being handled by the backend side, where it may get processed again by bereq.filters.

Trying to set req.filters after processing the request body (again, for example with std.cache_req.body()) triggers a VCL error.

req.grace ⏳

  • Type: DURATION
  • Readable from: client
  • Writable from: client
  • Unsettable from: client

Upper limit on the object grace.

During lookup the minimum of req.grace and the object's stored grace value will be used as the object's grace.

When reading the unset value, it is returned as -1.

req.hash #️⃣

  • Type: BLOB
  • Readable from: vcl_hit, vcl_miss, vcl_pass, vcl_purge, vcl_deliver, vcl_synth, vcl_pipe

The hash key of this request. Mostly useful for passing to VMODs, but can also be useful for debugging hit/miss status.

req.hash_always_miss 🎲

  • Type: BOOL
  • Readable from: client
  • Writable from: client
  • Default: false.

Force a cache miss for this request, even if perfectly good matching objects are in the cache.

This is useful to force-update the cache without invalidating existing entries in case the fetch fails.

req.hash_ignore_busy 🚧

  • Type: BOOL
  • Readable from: client
  • Writable from: client
  • Default: false.

Ignore any busy object during cache lookup.

You only want to do this when you have two server looking up content sideways from each other to avoid deadlocks.

req.hash_ignore_vary πŸ™ˆ

  • Type: BOOL
  • Readable from: client
  • Writable from: client
  • Default: false.

Ignore objects vary headers during cache lookup.

This returns the very first match regardless of the object compatibility with the client request. This is useful when variants are irrelevant to certain clients, and differences in the way the resource is presented don't change how the client will interpret it.

Use with caution.

req.http.* πŸ“¬

  • Type: HEADER
  • Readable from: client
  • Writable from: client
  • Unsettable from: client

The headers of request, things like req.http.date.

The RFCs allow multiple headers with the same name, and both set and unset will remove all headers with the name given.

The header name * is a VCL symbol and as such cannot, for example, start with a numeral. To work with valid header that can't be represented as VCL symbols it is possible to quote the name, like req.http."grammatically.valid". None of the HTTP headers present in IANA registries need to be quoted, so the quoted syntax is discouraged but available for interoperability.

Some headers that cannot be tampered with for proper HTTP fetch or delivery are read-only.

req.http.content-length πŸ“

  • Type: HEADER
  • Readable from: client

The content-length header field is protected, see protected_headers.

req.http.transfer-encoding πŸ”„

  • Type: HEADER
  • Readable from: client

The transfer-encoding header field is protected, see protected_headers.

req.is_hitmiss 🎯

  • Type: BOOL
  • Readable from: vcl_miss, vcl_deliver, vcl_pass, vcl_synth

If this request resulted in a hitmiss.

req.is_hitpass 🎯

  • Type: BOOL
  • Readable from: vcl_pass, vcl_deliver, vcl_synth

If this request resulted in a hitpass.

req.method πŸ“

  • Type: STRING
  • Readable from: client
  • Writable from: client

The request method (e.g. "GET", "HEAD", ...).

req.proto VCL <= 4.0 πŸ“‘

  • Type: STRING
  • Readable from: client
  • Writable from: client

The HTTP protocol version used by the client, usually "HTTP/1.1" or "HTTP/2.0".

req.proto VCL >= 4.1 πŸ“‘

  • Type: STRING
  • Readable from: client

The HTTP protocol version used by the client, usually "HTTP/1.1" or "HTTP/2.0".

req.restarts πŸ”

  • Type: INT
  • Readable from: client

A count of how many times this request has been restarted.

req.storage πŸ’Ύ

  • Type: STEVEDORE
  • Readable from: client
  • Writable from: client

The storage backend to use to save this request body.

req.time ⏰

  • Type: TIME
  • Readable from: client

The time when the request was fully received, remains constant across restarts.

req.trace πŸ”

  • Type: BOOL
  • Readable from: client
  • Writable from: client

Controls if VCL_trace VSL records are emitted for the current request, see vsl(7).

Defaults to the setting of the feature trace parameter, see varnishd(1). Does not get reset by a rollback.

req.transport 🚚

  • Type: STRING
  • Readable from: client

The transport protocol which brought this request.

req.ttl ⏲️

  • Type: DURATION
  • Readable from: client
  • Writable from: client
  • Unsettable from: client

Upper limit on the object age for cache lookups to return hit.

When reading the unset value, it is returned as -1.

req.url πŸ”—

  • Type: STRING
  • Readable from: client
  • Writable from: client

The requested URL, for instance "/robots.txt".

req.xid πŸ†”

  • Type: INT
  • Readable from: client

Unique ID of this request.

req_top.http.* πŸ“¬

  • Type: HEADER
  • Readable from: client

HTTP headers of the top-level request in a tree of ESI requests. Identical to req.http. in non-ESI requests.

See req.http for general notes.

req_top.method πŸ“

  • Type: STRING
  • Readable from: client

The request method of the top-level request in a tree of ESI requests. (e.g. "GET", "HEAD"). Identical to req.method in non-ESI requests.

req_top.proto πŸ“‘

  • Type: STRING
  • Readable from: client

... the HTTP protocol version of the top-level request in a whole tree of ESI requests. Same thing as req.proto when there's no ESI in the picture. πŸ’«

req_top.time

  • Type: TIME
  • Readable from: client

The moment the top-level request was fully received. It stays the same even across restarts, bestie β€” it does not move. πŸ•°οΈ

req_top.url

  • Type: STRING
  • Readable from: client

The URL that was requested for the top-level request in a tree of ESI requests. Identical to req.url in non-ESI requests. Same URL, no drama. 🌸


πŸ¦‹ bereq

Okay so this one is the request we send to the backend β€” it gets built out of the client's req.* fields, except all the "per-hop" fields get filtered out because they have no business travelling along (Connection:, Range: and friends). 🚫

And listen β€” slightly more fields are allowed through for pass fetches than for miss fetches, for instance Range. iykyk.

bereq

  • Type: HTTP
  • Readable from: backend

The entire backend request HTTP data structure. Mostly it's useful as an argument to VMODs. πŸ“¦

bereq.backend

  • Type: BACKEND
  • Readable from: vcl_pipe, backend
  • Writable from: vcl_pipe, backend

This is the backend β€” or the director β€” we're trying to fetch from. When you set it to a director, reading this variable gives you back an actual backend if that director resolved immediately, or the director itself otherwise. And when you use it in string context, it returns the name of the director or the backend, respectively. ✨

bereq.between_bytes_timeout

  • Type: DURATION
  • Readable from: backend
  • Writable from: backend
  • Unsettable from: vcl_pipe, backend
  • Default: the .between_bytes_timeout attribute from the backend_definition, which defaults to the between_bytes_timeout parameter, see varnishd(1).

How long (in seconds) we're willing to wait between each byte we receive from the backend. Heads up: this is not available in pipe mode. 🚧

bereq.body

  • Type: BODY
  • Unsettable from: vcl_backend_fetch

The request body.

Setting it to unset also removes bereq.http.content-length. bye bestie πŸ’€

bereq.connect_timeout

  • Type: DURATION
  • Readable from: vcl_pipe, backend
  • Writable from: vcl_pipe, backend
  • Unsettable from: vcl_pipe, backend
  • Default: the .connect_timeout attribute from the backend_definition, which defaults to the connect_timeout parameter, see varnishd(1).

How long (in seconds) we wait for a backend connection to actually get established. ⏳

bereq.filters

  • Type: STRING
  • Readable from: vcl_backend_fetch
  • Writable from: vcl_backend_fetch

The list of VDP filters that bereq.body gets pushed through when we send the body off to the backend. 🧺

bereq.first_byte_timeout

  • Type: DURATION
  • Readable from: backend
  • Writable from: backend
  • Unsettable from: vcl_pipe, backend
  • Default: the .first_byte_timeout attribute from the backend_definition, which defaults to the first_byte_timeout parameter, see varnishd(1).

How long (in seconds) we wait to get that first byte back from the backend. Also not available in pipe mode. 🚧

bereq.hash

  • Type: BLOB
  • Readable from: vcl_pipe, backend

The hash key for this request β€” literally a copy of req.hash. πŸ”‘

bereq.http.*

  • Type: HEADER
  • Readable from: vcl_pipe, backend
  • Writable from: vcl_pipe, backend
  • Unsettable from: vcl_pipe, backend

The headers that get sent to the backend.

See req.http for the general notes. πŸ“¬

bereq.http.content-length

  • Type: HEADER
  • Readable from: backend

This header field is protected, see protected_headers. πŸ›‘οΈ

bereq.http.transfer-encoding

  • Type: HEADER
  • Readable from: backend

Also protected, see protected_headers. πŸ›‘οΈ

bereq.is_bgfetch

  • Type: BOOL
  • Readable from: backend

True for fetches where the client got a hit on an object that was in grace, and this fetch got kicked off in the background to grab a fresh copy. main character fetch energy πŸ’…

bereq.is_hitmiss

  • Type: BOOL
  • Readable from: backend

True if this backend request was caused by a hitmiss. πŸ‘€

bereq.is_hitpass

  • Type: BOOL
  • Readable from: backend

True if this backend request was caused by a hitpass. πŸ‘€

bereq.method

  • Type: STRING
  • Readable from: vcl_pipe, backend
  • Writable from: vcl_pipe, backend

The request type (e.g. "GET", "HEAD").

Regular (non-pipe, non-pass) fetches are always "GET". no cap 🫢

bereq.proto VCL <= 4.0

  • Type: STRING
  • Readable from: vcl_pipe, backend
  • Writable from: vcl_pipe, backend

The HTTP protocol version β€” "HTTP/1.1" unless a pass or pipe request has "HTTP/1.0" in req.proto.

bereq.proto VCL >= 4.1

  • Type: STRING
  • Readable from: vcl_pipe, backend

The HTTP protocol version β€” "HTTP/1.1" unless a pass or pipe request has "HTTP/1.0" in req.proto. (Same story, just not writable anymore. ✨)

bereq.retries

  • Type: INT
  • Readable from: backend

A count of how many times this request has been retried. πŸ”

bereq.task_deadline

  • Type: DURATION
  • Readable from: vcl_pipe
  • Writable from: vcl_pipe
  • Unsettable from: vcl_pipe

The deadline for pipe sessions, defaults to 0s, which falls back to the pipe_task_deadline parameter, see varnishd(1). ⏰

bereq.time

  • Type: TIME
  • Readable from: vcl_pipe, backend

The time when we started preparing the first backend request β€” and it stays constant across retries. frozen in time, iconic 🧊

bereq.trace

  • Type: BOOL
  • Readable from: backend
  • Writable from: backend

Controls whether VCL_trace VSL records get emitted for the current request, see vsl(7).

It inherits the value of req.trace when the backend request is created. And ngl, it does not get reset by a rollback. πŸ™ˆ

bereq.uncacheable

  • Type: BOOL
  • Readable from: backend

Tells you whether this request is uncacheable because of a pass on the client side, or because of a hit on an hit-for-pass object. 🚫

bereq.url

  • Type: STRING
  • Readable from: vcl_pipe, backend
  • Writable from: vcl_pipe, backend

The requested URL β€” copied straight from req.url. πŸ“‹

bereq.xid

  • Type: INT
  • Readable from: vcl_pipe, backend

The unique ID of this request. πŸ†”


🌈 beresp

This is the response we got back from the backend. On cache misses, the stored object gets built out of beresp. πŸ—οΈ

beresp

  • Type: HTTP
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The entire backend response HTTP data structure β€” super useful as an argument to VMOD functions. πŸ“¦

beresp.age

  • Type: DURATION
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: Age header, or zero.

The age of the object. πŸ‘΅

beresp.backend

  • Type: BACKEND
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

This is the backend we actually fetched from. If bereq.backend was set to a director, then this is the backend that the director selected. And in string context, it returns its name. ✨

beresp.backend.ip VCL <= 4.0

  • Type: IP
  • Readable from: vcl_backend_response

The IP of the backend this response was fetched from. 🌐

beresp.backend.name

  • Type: STRING
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The name of the backend this response was fetched from. Same as beresp.backend. 🏷️

beresp.body

  • Type: BODY
  • Writable from: vcl_backend_error

For producing a synthetic body. ✍️

beresp.do_esi

  • Type: BOOL
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: false.

Set it to true to parse the object for ESI directives. You need this for later ESI processing on the client side. If beresp.do_esi is false when an object enters the cache, then client side ESI processing won't be possible at all (obj.can_esi will be false). 😭

It is a VCL error to use beresp.do_esi after setting beresp.filters. 🚨

beresp.do_gunzip

  • Type: BOOL
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: false.

Set it to true to gunzip the object while we're storing it in the cache. 🫧

If http_gzip_support is disabled, setting this variable has no effect. (it's giving unbothered)

It is a VCL error to use beresp.do_gunzip after setting beresp.filters. 🚨

beresp.do_gzip

  • Type: BOOL
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: false.

Set it to true to gzip the object while storing it. πŸ—œοΈ

If http_gzip_support is disabled, setting this variable has no effect.

It is a VCL error to use beresp.do_gzip after setting beresp.filters. 🚨

beresp.do_stream

  • Type: BOOL
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: true.

Deliver the object to the client while we're still fetching the whole object into varnish. 🌊

For uncacheable objects, storage for the parts of the body that have already been sent to the client may get freed early β€” it depends on which storage engine you're using.

This variable has no effect if beresp.do_esi is true, or when the response body is empty. πŸ’€

beresp.filters

  • Type: STRING
  • Readable from: vcl_backend_response
  • Writable from: vcl_backend_response

A list of Varnish Fetch Processor (VFP) filters that beresp.body will be pulled through. The order left to right is the processing order from backend to cache β€” iow the leftmost filter runs first on the body as received from the backend, after decoding of any transfer encodings. 🧚

VFP filters change the body before it goes into the cache and/or before it's handed to the client side, where it may get processed again by resp.filters.

The following VFP filters exist in varnish-cache:

  • gzip: compress a body using gzip
  • testgunzip: Test if a body is valid gzip and refuse it otherwise
  • gunzip: Uncompress gzip content
  • esi: ESI-process plain text content
  • esi_gzip: Save gzipped snippets for efficient ESI-processing

This filter enables stitching together ESI from individually gzipped fragments, saving processing power for re-compression on the client side at the expense of some compression efficiency. ✨

Additional VFP filters are available from VMODs.

By default, beresp.filters is constructed as follows:

  • gunzip gets added for gzipped content if beresp.do_gunzip or beresp.do_esi are true. πŸ’…
  • esi_gzip gets added if beresp.do_esi is true together with beresp.do_gzip or the content is already compressed.
  • esi gets added if beresp.do_esi is true.
  • gzip gets added for uncompressed content if beresp.do_gzip is true.
  • testgunzip gets added for compressed content if beresp.do_gunzip is false.
okay so listen 😭 the second you set beresp.filters, touching any of those beresp.do_* switches up there is a VCL error. Like, pick a lane bestie. Not both.

πŸ•°οΈ beresp.grace

  • Type: DURATION
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: the Cache-Control stale-while-revalidate directive, or the default_grace parameter.

Set it to a period to turn grace on. ✨


πŸ“¨ beresp.http.*

  • Type: HEADER
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Unsettable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

These are literally the HTTP headers the server handed back to us. 🫢

Check req.http for the general notes, they apply here too.


🚧 beresp.http.content-length

  • Type: HEADER
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The content-length header field is protected, see protected_headers. Hands off bestie πŸ™ˆ


🚧 beresp.http.transfer-encoding

  • Type: HEADER
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The transfer-encoding header field is protected too, see protected_headers. Also off limits πŸ’€


πŸ’Ύ beresp.keep

  • Type: DURATION
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: the default_keep parameter.

Set it to a period to enable conditional backend requests.

The keep time is cache lifetime on top of the ttl, so like, ttl then keep, stacked 🫢

Objects whose ttl has expired but which still have keep time left can be used to fire off conditional (If-Modified-Since / If-None-Match) requests to the backend to refresh them. Very main character energy ✨


πŸ”Œ beresp.proto β€” VCL <= 4.0

  • Type: STRING
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The HTTP protocol version the backend replied with.


πŸ”Œ beresp.proto β€” VCL >= 4.1

  • Type: STRING
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The HTTP protocol version the backend replied with.

notice how the newer one is read-only, bestie? the glow-up is real πŸ’…

πŸ’¬ beresp.reason

  • Type: STRING
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The HTTP status message the server sent back. You know, the little vibe description next to the number.


πŸ”’ beresp.status

  • Type: INT
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The HTTP status code the server sent back.

More info lives in the HTTP response status section. πŸ‘‘


πŸ—„οΈ beresp.storage

  • Type: STEVEDORE
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

The storage backend we're gonna use to save this object. If you don't set one, Varnish picks a storage backend in a round-robin fashion β€” or the Transient backend if the object is short-lived. she's indecisive but she's doing her best πŸ¦‹


⚠️ beresp.storage_hint β€” VCL <= 4.0

  • Type: STRING
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
Deprecated since varnish 5.1 and discontinued since VCL 4.1 (varnish 6.0). Use beresp.storage instead. it's giving legacy, bestie 😭

A hint to Varnish that you want this object saved to one particular storage backend.


⏰ beresp.time

  • Type: TIME
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

When the backend headers were fully received, right before vcl_backend_response {} was entered β€” or when vcl_backend_error {} was entered.


🌊 beresp.transit_buffer

  • Type: BYTES
  • Readable from: vcl_backend_response
  • Writable from: vcl_backend_response
  • Default: the transit_buffer parameter, see varnishd(1).

The max number of bytes the client can be ahead of the backend during a streaming pass, if beresp is uncacheable. Also check the transit_buffer parameter docs in varnishd(1). 🫧


⏳ beresp.ttl

  • Type: DURATION
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Default: the Cache-Control s-maxage or max-age directives, or a value computed from the Expires header's deadline, or the default_ttl parameter.

The object's remaining time to live, in seconds. her clock is ticking ⏰


🚫 beresp.uncacheable

  • Type: BOOL
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh
  • Writable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

Inherited from bereq.uncacheable, go read that one.

Setting this variable makes the object uncacheable.

This may produce a hit-for-miss object in the cache.

Clearing the variable does nothing β€” it has no effect β€” and it will log the warning "Ignoring attempt to reset beresp.uncacheable". so like, don't bother bestie πŸ™ˆ

🩹 beresp.was_304

  • Type: BOOL
  • Readable from: vcl_backend_response, vcl_backend_error, vcl_backend_refresh

When true, this means we got a 304 response to our conditional fetch from the backend and turned that into beresp.status = 200. glow-up of the century honestly ✨


🧟 obj_stale

This is the stale object we had sitting in cache. It cannot be modified. Look, don't touch 🫢


🧟 obj_stale.age

  • Type: DURATION
  • Readable from: vcl_backend_refresh

The age of the stale object.


🧟 obj_stale.can_esi

  • Type: BOOL
  • Readable from: vcl_backend_refresh

Whether the stale object can be ESI processed β€” that is, if setting resp.do_esi or adding esi to resp.filters inside vcl_deliver {} would cause the response body to be ESI processed.


🧟 obj_stale.grace

  • Type: DURATION
  • Readable from: vcl_backend_refresh

The stale object's grace period, in seconds.


🧟 obj_stale.hits

  • Type: INT
  • Readable from: vcl_backend_refresh

The count of cache-hits on this stale object.

In vcl_deliver a value of 0 indicates a cache miss. πŸ’€


🧟 obj_stale.http.*

  • Type: HEADER
  • Readable from: vcl_backend_refresh

The HTTP headers stored in the stale object.

See req.http for the general notes.


🧟 obj_stale.keep

  • Type: DURATION
  • Readable from: vcl_backend_refresh

The stale object's keep period, in seconds.


🧟 obj_stale.proto

  • Type: STRING
  • Readable from: vcl_backend_refresh

The HTTP protocol version stored in the stale object.


🧟 obj_stale.reason

  • Type: STRING
  • Readable from: vcl_backend_refresh

The HTTP reason phrase stored in the stale object.


🧟 obj_stale.status

  • Type: INT
  • Readable from: vcl_backend_refresh

The HTTP status code stored in the stale object.

More info in the HTTP response status section. πŸ‘‘


🧟 obj_stale.storage

  • Type: STEVEDORE
  • Readable from: vcl_backend_refresh

The storage backend where this stale object lives.


🧟 obj_stale.time

  • Type: TIME
  • Readable from: vcl_backend_refresh

The time the stale object was created, from the perspective of the server that generated it. This will roughly be equivalent to now - obj.age. πŸ•°οΈ


🧟 obj_stale.ttl

  • Type: DURATION
  • Readable from: vcl_backend_refresh

The stale object's time to live, in seconds.


🧟 obj_stale.uncacheable

  • Type: BOOL
  • Readable from: vcl_backend_refresh

Whether the stale object is uncacheable β€” so that's pass, hit-for-pass or hit-for-miss. 🚫


🧟 obj_stale.is_valid

  • Type: BOOL
  • Readable from: vcl_backend_refresh

Whether the stale object is still valid. vibe check passed or not, that's the whole thing βœ…


πŸ† obj

This is the object we found in cache. It cannot be modified. she's a finished product, bestie, no edits πŸ’…


πŸ† obj.age

  • Type: DURATION
  • Readable from: vcl_hit, vcl_deliver

The age of the object.


πŸ† obj.can_esi

  • Type: BOOL
  • Readable from: vcl_hit, vcl_deliver

If the object can be ESI processed β€” that is, if setting resp.do_esi or adding esi to resp.filters inside vcl_deliver {} would cause the response body to be ESI processed.


πŸ† obj.grace

  • Type: DURATION
  • Readable from: vcl_hit, vcl_deliver

The object's grace period, in seconds.


πŸ† obj.hits

  • Type: INT
  • Readable from: vcl_hit, vcl_deliver

The count of cache-hits on this object.

In vcl_deliver a value of 0 indicates a cache miss. 😭


πŸ† obj.http.*

  • Type: HEADER
  • Readable from: vcl_hit

The HTTP headers stored in the object.

See req.http for the general notes.


πŸ† obj.keep

  • Type: DURATION
  • Readable from: vcl_hit, vcl_deliver

The object's keep period, in seconds.


πŸ† obj.proto

  • Type: STRING
  • Readable from: vcl_hit

The HTTP protocol version stored in the object.


πŸ† obj.reason

  • Type: STRING
  • Readable from: vcl_hit

The HTTP reason phrase stored in the object.


πŸ† obj.status

  • Type: INT
  • Readable from: vcl_hit

The HTTP status code stored in the object.

More info in the HTTP response status section. πŸ‘‘


πŸ† obj.storage

  • Type: STEVEDORE
  • Readable from: vcl_hit, vcl_deliver

The storage backend where this object is stored.


πŸ† obj.time

  • Type: TIME
  • Readable from: vcl_hit, vcl_deliver

The time the object was created, from the perspective of the server that generated it. This will roughly be equivalent to now - obj.age. ⏰


πŸ† obj.ttl

  • Type: DURATION
  • Readable from: vcl_hit, vcl_deliver

The object's remaining time to live, in seconds.


πŸ† obj.uncacheable

  • Type: BOOL
  • Readable from: vcl_deliver

Whether the object is uncacheable β€” pass, hit-for-pass or hit-for-miss. 🚫


πŸ“¬ resp

This is the response we send to the client. It gets built from either beresp (pass/miss), obj (hits), or created from whole cloth (synth). she's custom-made, she's bespoke, she's iconic ✨

With the exception of resp.body, all resp.* variables are available in both vcl_deliver{} and vcl_synth{} β€” for symmetry, because we're fair like that πŸ’–

🫧 resp.body

  • Type: BODY
  • Writable from: vcl_synth

Bestie, this is how you hand-build a synthetic response body β€” like when you're making a cute lil custom error page instead of whatever the backend was gonna give. πŸ’…


🫧 resp.do_esi VCL >= 4.1

  • Type: BOOL
  • Readable from: vcl_deliver, vcl_synth
  • Writable from: vcl_deliver, vcl_synth
  • Default: obj.can_esi

okay so this one lets you selectively turn ESI processing off, even though the ESI parsing already went down during fetch (peep beresp.do_esi for that part). When does this slay? When Varnish caches are peering with each other. 🌸

🚨 Real talk: it is a VCL error to use resp.do_esi after you've set resp.filters. Don't do it. Pick a lane.

🫧 resp.filters

  • Type: STRING
  • Readable from: vcl_deliver, vcl_synth
  • Writable from: vcl_deliver, vcl_synth

This is the list of VDP filters that resp.body is gonna get pushed through. It's giving glow-up pipeline. ✨

Before you set resp.filters, reading it gives you the default filter list β€” the one varnish picked out based on resp.do_esi and the request headers.

But the moment you set resp.filters? That's it, locked in. Changing any of the conditions that would otherwise decide the filter selection does literally nothing after that. And using resp.do_esi once resp.filters is set is an error. periodt.


🫧 resp.http.*

  • Type: HEADER
  • Readable from: vcl_deliver, vcl_synth
  • Writable from: vcl_deliver, vcl_synth
  • Unsettable from: vcl_deliver, vcl_synth

The HTTP headers that are gonna be sent back. πŸ“¬ Head over to req.http for the general notes, iykyk.


🫧 resp.http.content-length

  • Type: HEADER
  • Readable from: vcl_deliver, vcl_synth

The content-length header field is protected β€” see protected_headers. πŸ”’


🫧 resp.http.transfer-encoding

  • Type: HEADER
  • Readable from: vcl_deliver, vcl_synth

The transfer-encoding header field is protected too β€” see protected_headers. πŸ”’


🫧 resp.is_streaming

  • Type: BOOL
  • Readable from: vcl_deliver, vcl_synth

Returns true when the response is gonna be streamed while it's being fetched from the backend. πŸ¦‹


🫧 resp.proto VCL <= 4.0

  • Type: STRING
  • Readable from: vcl_deliver, vcl_synth
  • Writable from: vcl_deliver, vcl_synth

The HTTP protocol version to use for the response. πŸŽ€


🫧 resp.proto VCL >= 4.1

  • Type: STRING
  • Readable from: vcl_deliver, vcl_synth

The HTTP protocol version to use for the response. (No writing in this era, bestie β€” read only. πŸ™ˆ)


🫧 resp.reason

  • Type: STRING
  • Readable from: vcl_deliver, vcl_synth
  • Writable from: vcl_deliver, vcl_synth

The HTTP status message that's gonna be returned. The lil text part after the number. πŸ’¬


🫧 resp.status

  • Type: INT
  • Readable from: vcl_deliver, vcl_synth
  • Writable from: vcl_deliver, vcl_synth

The HTTP status code that's gonna be returned. More info lives in the HTTP response status section down below. πŸ‘‡

Here's the tea though: resp.status 200 will get changed into 304 by core code after a return(deliver) from vcl_deliver for conditional requests to cached content β€” if validation succeeds. ⭐

How's the validation go? Two rounds:

  1. First, req.http.If-None-Match gets compared against resp.http.Etag. If they compare equal according to the rules for weak validation (see RFC7232), a 304 gets sent.
  2. Then, req.http.If-Modified-Since gets compared against resp.http.Last-Modified β€” or, if that's unset or weak, against the point in time when the object was last modified, based on the Date and Age headers that came in with the backend response which created the object. If the object hasn't been modified based on that comparison, a 304 gets sent.

🫧 resp.time

  • Type: TIME
  • Readable from: vcl_deliver, vcl_synth

The time when we started preparing the response β€” right before entering vcl_synth {} or vcl_deliver {}. ⏰


✨ Special variables ✨

🫧 now

  • Type: TIME
  • Readable from: all

The current time, in seconds since the UNIX epoch. πŸ•°οΈ

When it gets converted to STRING in expressions, it returns a formatted timestamp like Tue, 20 Feb 2018 09:30:31 GMT.

And here's the iconic part: now stays stable for the whole duration of any built-in VCL subroutine. Why? So time-based calculations stay predictable and edge cases don't come for us. πŸ›‘οΈ

In other words β€” even if a considerable amount of time gets spent in VCL, now will always represent the point in time when the respective built-in VCL subroutine was entered. So now is not suitable for any kind of time measurements. No cap. If you wanna measure, go see std.timestamp(), std.now() and std.timed_call() in vmod_std(3). πŸ’œ


πŸŽ€ sess

A session corresponds to the "conversation" that Varnish has with a single client connection β€” and over that one convo, one or more request/response transactions may take place. It might be the traffic over an HTTP/1 keep-alive connection, or the multiplexed traffic over an HTTP/2 connection. πŸ¦‹

🫧 sess.idle_send_timeout

  • Type: DURATION
  • Readable from: client
  • Writable from: client
  • Unsettable from: client

Send timeout for individual pieces of data on client connections. Defaults to the idle_send_timeout parameter, see varnishd(1).

🫧 sess.send_timeout

  • Type: DURATION
  • Readable from: client
  • Writable from: client
  • Unsettable from: client

Total timeout for ordinary HTTP1 responses. Defaults to the send_timeout parameter, see varnishd(1).

🫧 sess.timeout_idle

  • Type: DURATION
  • Readable from: client
  • Writable from: client
  • Unsettable from: client

Idle timeout for this session. Defaults to the timeout_idle parameter, see varnishd(1).

🫧 sess.timeout_linger

  • Type: DURATION
  • Readable from: client
  • Writable from: client
  • Unsettable from: client

Linger timeout for this session. Defaults to the timeout_linger parameter, see varnishd(1).

🫧 sess.xid VCL >= 4.1

  • Type: INT
  • Readable from: client, backend

Unique ID of this session. πŸ’«


πŸ“ storage

🫧 storage.<name>.free_space

  • Type: BYTES
  • Readable from: client, backend
  • Default: 0

Free space available in the named stevedore. Only available for the malloc stevedore. 🌈

🫧 storage.<name>.happy

  • Type: BOOL
  • Readable from: client, backend
  • Default: true

Health status for the named stevedore. Not available in any of the current stevedores. (sad, honestly 😭)

🫧 storage.<name>.used_space

  • Type: BYTES
  • Readable from: client, backend
  • Default: 0

Used space in the named stevedore. Only available for the malloc stevedore. πŸ”₯


πŸ‘‘ Runtime parameters

Some of the global runtime parameters related to VCL transactions are readable anywhere in VCL. Heads up: the value of a parameter can change between accesses β€” it is not cached for the duration of a VCL task. πŸ‘€

🫧 param.backend_idle_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_backend_idle_timeout.

🫧 param.backend_wait_limit

  • Type: INT
  • Readable from: all

Global parameter ref_param_backend_wait_limit.

🫧 param.backend_wait_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_backend_wait_timeout.

🫧 param.between_bytes_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_between_bytes_timeout.

🫧 param.connect_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_connect_timeout.

🫧 param.default_grace

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_default_grace.

🫧 param.default_keep

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_default_keep.

🫧 param.default_ttl

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_default_ttl.

🫧 param.first_byte_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_first_byte_timeout.

🫧 param.idle_send_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_idle_send_timeout.

🫧 param.max_esi_depth

  • Type: INT
  • Readable from: all

Global parameter ref_param_max_esi_depth.

🫧 param.max_restarts

  • Type: INT
  • Readable from: all

Global parameter ref_param_max_restarts.

🫧 param.max_retries

  • Type: INT
  • Readable from: all

Global parameter ref_param_max_retries.

🫧 param.pipe_task_deadline

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_pipe_task_deadline.

🫧 param.pipe_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_pipe_timeout.

🫧 param.send_timeout

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_send_timeout.

🫧 param.shortlived

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_shortlived.

🫧 param.timeout_idle

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_timeout_idle.

🫧 param.transit_buffer

  • Type: BYTES
  • Readable from: all

Global parameter ref_param_transit_buffer.

🫧 param.uncacheable_ttl

  • Type: DURATION
  • Readable from: all

Global parameter ref_param_uncacheable_ttl.


πŸ”’ Protected header fields

The content-length and transfer-encoding headers are read-only. They have to be preserved so HTTP/1 framing stays consistent and so we keep a proper request and response synchronization with both clients and backends. Do not touch, bestie. πŸ™…

That said β€” VMODs can still update these headers when there's an actual reason to change the framing, like a transformation of a request or response body. So it's not never, it's not by you in VCL. 😌


🌈 HTTP response status

A HTTP status code has 3 digits XYZ where X must be between 1 and 5 included. And since it's genuinely not uncommon to see HTTP clients or servers relying on non-standard or even invalid status codes, Varnish can work with any status between 100 and 999. Flexible icon. πŸ’…

Within VCL code it's even possible to use status codes in the form VWXYZ β€” as long as the overall value is lower than 65536. But only the XYZ part will actually get sent to the client, and by that time the X must also have become non-zero. ⭐

The VWXYZ form of status codes can communicate extra information in resp.status and beresp.status to return(synth(...)) and return(error(...)), to indicate which synthetic content to produce:

sub vcl_recv {
    if ([...]) {
        return synth(12404);
    }
}

sub vcl_synth {
    if (resp.status == 12404) {
        [...]       // this specific 404
    } else if (resp.status % 1000 == 404) {
        [...]       // all other 404's
    }
}

The obj.status variable will inherit the VWXYZ form, but in a ban expression only the XYZ part will be available. And the VWXYZ form is strictly limited to VCL execution. 🚫 That's the boundary, don't try to take it out of the club.

Assigning an HTTP standardized code to resp.status or beresp.status will also set resp.reason or beresp.reason to the corresponding status message. two-for-one, bestie πŸ’…


304 handling πŸ•΅οΈβ™€οΈ

so for a 304 response, Varnish core code amends beresp before it calls vcl_backend_response:

  • If the gzip status changed, Content-Encoding is unset and any Etag is weakened
  • Any headers not present in the 304 response are copied from the existing cache object. Content-Length is copied if present in the existing cache object and discarded otherwise.
  • The status gets set to 200.

beresp.was_304 marks that this conditional response processing has happened. she's flagging it, your honor 🚩

Note: Backend conditional requests are independent of client conditional requests, so clients may receive 304 responses no matter if a backend request was conditional. yes really, they don't talk to each other, it's giving parallel universes 🌈

beresp.ttl / beresp.grace / beresp.keep πŸ•°οΈβœ¨

Before calling vcl_backend_response, core code sets beresp.ttl based on the response status and the response headers Age, Cache-Control or Expires and Date as follows:

  • If present and valid, the value of the Age header is effectively deduced from all ttl calculations.
  • For status codes 200, 203, 204, 300, 301, 304, 404, 410 and 414:
    • If Cache-Control contains an s-maxage or max-age field (in that order of preference), the ttl is set to the respective non-negative value or 0 if negative.
    • Otherwise, if no Expires header exists, the default ttl is used.
    • Otherwise, if Expires contains a time stamp before Date, the ttl is set to 0.
    • Otherwise, if no Date header is present or the Date header timestamp differs from the local clock by no more than the clock_skew parameter, the ttl is set to
      • 0 if Expires denotes a past timestamp or
      • the difference between the local clock and the Expires header otherwise.
    • Otherwise, the ttl is set to the difference between Expires and Date
  • For status codes 302 and 307, the calculation is identical except that the default ttl is not used and -1 is returned if neither Cache-Control nor Expires exists. special little guys, different rules πŸŽ€
  • For all other status codes, ttl -1 is returned.

beresp.grace defaults to the default_grace parameter.

For a non-negative ttl, if Cache-Control contains a stale-while-revalidate field value, beresp.grace is set to that value if non-negative or 0 otherwise.

beresp.keep defaults to the default_keep parameter.


✨ SEE ALSO ✨


πŸ“œ HISTORY πŸ“œ

VCL was developed by Poul-Henning Kamp in cooperation with Verdens Gang AS, Redpill Linpro and Varnish Software. This manual page is written by Per Buer, Poul-Henning Kamp, Martin Blix Grydeland, Kristian LyngstΓΈl, Lasse Karstensen and others. legends, every single one πŸ‘‘


This document is licensed under the same license as Varnish itself. See LICENSE for details.

  • Copyright (c) 2006 Verdens Gang AS
  • Copyright (c) 2006-2021 Varnish Software AS

and that's the whole page bestie, we did it 🫢 VCL-VARIABLES(7) β€” go forth and cache responsibly, mwah πŸ’‹πŸŒΈ

πŸ“¦ where this page comes from
  • fedora 44: varnish 8.0.2-1.fc44 Β· BSD-2-Clause AND (BSD-2-Clause-FreeBSD AND BSD-3-Clause AND LicenseRef-Fedora-Public-Domain AND Zlib)
  • fedora 44: varnish 8.0.2-1.fc44 Β· BSD-2-Clause AND (BSD-2-Clause-FreeBSD AND BSD-3-Clause AND LicenseRef-Fedora-Public-Domain AND Zlib)
  • πŸ“œ the page's license: BSD-2-Clause
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms