Using VCL

Fastly VCL is a domain specific language derived from the Varnish proxy cache, which is part of Fastly's platform architecture. It's intentionally limited in range, which allows us to run it extremely fast, make it available to all requests that pass through Fastly, and maintain the security of the Fastly network. With VCL, you can do anything from adding a cookie or setting a Cache-Control header to implementing a complete paywall solution.

VCL services on Fastly do not provide a single entry point for your application code. Instead, we expose a number of "hooks", in the form of built-in subroutines, which are called at significant moments in the lifecycle of each HTTP request that passes through your service. As a result, code that you upload to a Fastly VCL service is known as a configuration, not an application.

The VCL request lifecycle

The following subroutines are triggered by Fastly, in this order:

RECVHASHHITPASSMISSFETCHERRORDELIVERLOGFetch nodeDefault pathAlternative pathErrorRestart
NameTrigger pointDefault return stateAlternative return states
vcl_recvClient request receivedlookuprecv-notepass, error, restart
vcl_hashA cache key will be calculatedhash hash-note
vcl_hitAn object has been found in cachedeliverpass, error, restart
vcl_missNothing was found in the cache, preparing backend fetchfetchdeliver_stale, pass, error
vcl_passCache passed, preparing backend fetchpass pass-noteerror
vcl_fetchOrigin response headers receiveddeliver fetch-notedeliver_stale, pass, error, restart
vcl_errorError triggered (explicitly or by Fastly)deliverrestart
vcl_deliverPreparing to deliver response to clientdeliverrestart
vcl_logFinished sending response to clientdeliverlog-note

The return state of a subroutine determines the next action taken by the Fastly cache server. This is better illustrated in a flow diagram:

Adding VCL to your service configuration

Everything that your service does is powered by VCL, including any high level features that you enable in the web interface or via the API, so we need to be able to combine your own VCL with code generated by these features. To support combining your own VCL logic with Fastly's generated VCL code, we include 'macros' in the VCL program, one in each subroutine, such as #FASTLY recv.

The VCL generated by Fastly if you don't upload your own code also includes a number of small tweaks which help our default behaviors match expectations or avoid problems. When you use your own VCL these are not included, so if you want them, you need to add them yourself.

This adds up to a number of different options for you to choose from to get VCL into your configuration. Let's look at these in increasing order of control:

  1. Use VCL generative objects: Using the web interface or API, add high level objects like headers, responses and conditions. VCL will be generated for you.
  2. Use VCL snippets: By adding your custom VCL code using snippets, you can insert code into VCL subroutines without having to accommodate Fastly-generated code. Your code snippets will be added at the end of the subroutine you select.
  3. Use Custom VCL: Custom VCL allows you to upload a full VCL source file, which will entirely replace the one that would otherwise be generated by Fastly. In order that features you select in the web interface can still work, we require that custom VCL files include Fastly's code macros, one in each subroutine.

If you choose option 3, we recommend that you start from the following 'boilerplate' when writing your own VCL code.

sub vcl_recv {
#FASTLY recv
# Normally, you should consider requests other than GET and HEAD to be uncacheable
# (to this we add the special FASTLYPURGE method)
if (req.method != "HEAD" && req.method != "GET" && req.method != "FASTLYPURGE") {
return(pass);
}
# If you are using image optimization, insert the code to enable it here
# See https://developer.fastly.com/reference/io/ for more information.
return(lookup);
}
sub vcl_hash {
set req.hash += req.url;
set req.hash += req.http.host;
#FASTLY hash
return(hash);
}
sub vcl_hit {
#FASTLY hit
return(deliver);
}
sub vcl_miss {
#FASTLY miss
return(fetch);
}
sub vcl_pass {
#FASTLY pass
return(pass);
}
sub vcl_fetch {
#FASTLY fetch
# Unset headers that reduce cacheability for images processed using the Fastly image optimizer
if (req.http.X-Fastly-Imageopto-Api) {
unset beresp.http.Set-Cookie;
unset beresp.http.Vary;
}
# Log the number of restarts for debugging purposes
if (req.restarts > 0) {
set beresp.http.Fastly-Restarts = req.restarts;
}
# If the response is setting a cookie, make sure it is not cached
if (beresp.http.Set-Cookie) {
return(pass);
}
# By default we set a TTL based on the `Cache-Control` header but we don't parse additional directives
# like `private` and `no-store`. Private in particular should be respected at the edge:
if (beresp.http.Cache-Control ~ "(private|no-store)") {
return(pass);
}
# If no TTL has been provided in the response headers, set a default
if (!beresp.http.Expires && !beresp.http.Surrogate-Control ~ "max-age" && !beresp.http.Cache-Control ~ "(s-maxage|max-age)") {
set beresp.ttl = 3600s;
# Apply a longer default TTL for images processed using Image Optimizer
if (req.http.X-Fastly-Imageopto-Api) {
set beresp.ttl = 2592000s; # 30 days
set beresp.http.Cache-Control = "max-age=2592000, public";
}
}
return(deliver);
}
sub vcl_error {
#FASTLY error
return(deliver);
}
sub vcl_deliver {
#FASTLY deliver
return(deliver);
}
sub vcl_log {
#FASTLY log
}

Writing your own VCL

Many common use cases for VCL are explored in our code examples gallery. The best practices guide also helps you understand how to avoid pitfalls and write safer, more secure edge code. Our fiddle tool also allows you to interactively write and execute VCL code without logging into Fastly, giving you space to experiment and test your ideas.

This section summarises some of the most common VCL use cases.

Manipulating headers

The set and unset statements allow for setting and unsetting HTTP headers on requests and responses. For example, in vcl_fetch, you could write:

sub vcl_fetch { ... }
Fastly VCL
set beresp.http.Cache-Control = "public, max-age=3600";
unset beresp.http.x-goog-request-id;

The {OBJ-NAME}.http.{HEADER-NAME} pattern is available for req, bereq, resp, beresp, and obj. See VCL variables for details of where each of these is available, but in general:

To add/remove headers on......use thisExample use cases
Client requestreq.http.{NAME} in vcl_recvRemove cookie header to strip credentials
Store data to refer to later in VCL
Backend requestbereq.http.{NAME} in vcl_miss and vcl_passAdd authentication headers
Backend responseberesp.http.{NAME} in vcl_fetchSet browser cache TTL
Remove superfluous origin response headers
Client responseresp.http.{NAME} in vcl_deliverSet cookies
Synthetic responseobj.http.{NAME} in vcl_errorSet the content-type of the synthetic response

URLs and query strings

The req.url variable contains the URL (path and query) being requested by the client, and is copied into bereq.url when making a request to a backend. The path and query can be separately accessed as req.url.path and req.url.qs. Consider using querystring.get and querystring.set to manipulate query parameters. querystring.filter can remove unwanted query params:

Using regular expressions on the URL path is a common way to route requests to different backends, by setting req.backend:

Cookies

Since the Cookie header is a comma-delimited list of individual cookies, you can access a named cookie using subfield accessor syntax. Often this is usefully combined with a regular expression match to extract parts of a structured cookie value. For example, if you have a cookie called "auth", which has a value such as "52b93cff.165826435.d783dad8-ebb9-4475-b6fb-68ce83f90f12", you could use the following VCL to isolate the auth cookie, and then extract the various parts of it into distinct HTTP headers:

sub vcl_recv { ... }
Fastly VCL
if (req.http.cookie:auth ~ "^([0-9a-f]+).(\d+).([\w-]+)$") {
set req.http.Auth-SessionID = re.group.1;
set req.http.Auth-CreditCount = re.group.2;
set req.http.Auth-DisplayName = re.group.3;
}

To write cookies, construct a Set-Cookie header on the client response, normally in vcl_deliver. Using set will overwrite any existing header with the same name, so if you may be setting multiple cookies in the same response, use add instead. It's also wise, when setting cookies on a response, to prevent the client or any downstream entity from caching it.

sub vcl_deliver { ... }
Fastly VCL
add resp.http.set-cookie = "auth=52b93cff.165826435.d783dad8-ebb9-4475-b6fb-68ce83f90f12; max-age=86400; path=/";
set resp.http.cache-control = "private, no-store";

Logging

Fastly supports logging data to a variety of specific vendors and generic endpoints. In VCL, you can emit a log message from anywhere in your VCL code using the log statement:

log "syslog " + req.service_id + " my-log-endpoint :: " + req.url;

All log statements in VCL take the form log "syslog {service_id} {log_endpoint_name} :: {log_message}. For more information on configuring log endpoints, and how to use them, see our Logging overview.

Controlling the cache

Fastly respects freshness-related HTTP headers sent in origin responses, such as Cache-Control, Last-Modified, and Expires. You can override this behavior using VCL in vcl_fetch, by setting the values of beresp.ttl, beresp.stale_while_revalidate, and beresp.stale_if_error.

sub vcl_fetch { ... }
Fastly VCL
set beresp.ttl = 30m;

Regardless of HTTP headers or explicit instructions in VCL, the cache may be disabled if the response has an HTTP status that does not support caching. A 200 (OK) response is considered cacheable, while a 500 (Internal Server Error) is not. You can change this decision by setting beresp.cacheable. For more information read our Freshness overview.

IMPORTANT: Setting the value of headers such as Cache-Control using VCL will not have any affect on whether or for how long the response is cached by Fastly (use beresp.ttl instead), but setting a Cache-Control header on a response is a good way to control whether the response is cached on the end user's device.

To disable caching entirely, execute a return(pass) from vcl_recv or vcl_fetch. Doing so in vcl_recv offers better performance because it allows us to skip request collapsing.

Synthetic responses

When an error occurs during request or response processing, the vcl_error subroutine will be executed, and an HTTP response will be created within Fastly. You can trigger this behavior explicitly using the error statement:

error 601;

If you trigger an error manually as shown above, pass a number in the 600-699 range (learn more about HTTP statuses used by Fastly. Then catch that error number in vcl_error:

sub vcl_error { ... }
Fastly VCL
if (obj.status == 601) {
set obj.status = 200;
set obj.http.content-type = "text/plain";
synthetic "OK";
return(deliver);
}

When vcl_error is executed, a new, 'synthetic' HTTP response is created and represented by obj. Use set with obj.http.{NAME} and obj.status to set the headers and response status of the object, and the synthetic statement to populate the response body.

Constraints and limitations

VCL services are subject to the following restrictions or limits:

ItemLimitImplications of exceeding the limit
URL size8KBVCL processing is skipped and a "Too long request string" error is emitted.
Cookie header size32KBThe cookie header will be unset and Fastly will set req.http.Fastly-Cookie-Overflow = "1", then run your VCL as normal.
Request header size69KBDepending on the circumstances, exceeding the limit can result in Fastly closing the client connection abruptly, the client receiving a 502 Gateway Error response with "I/O error" in the body, or receiving a 503 Service Unavailable response with the text "Header overflow" in the body.
Response header size69KBA 503 error is triggered with obj.response value of "backend read error". This error can be intercepted in vcl_error. See Common 503 errors for more info.
Request header count96VCL processing is skipped or aborted if in progress, and a response with "Header overflow" in the body is emitted. A number of headers are added to the request by Fastly, so the practical limit is lower, but is not a predictable constant. Assuming a practical limit of 85 is safe.
Response header count96VCL processing is skipped or aborted if in progress, and a response with "Header overflow" in the body is emitted. A number of headers are added to the response by Fastly, so the practical limit is lower, but is not a predictable constant. Assuming a practical limit of 85 is safe.
req.body size8KBLarger requests will have an empty req.body, so request body payload is available in req.body only for payloads smaller than 8KB.
Surrogate key size1KBRequests to the purge API that cite longer keys will fail, so in practical terms it is useless to tag content with keys exceeding the length limit.
Surrogate key header size16KBOnly keys that are entirely within the first 16KB of the surrogate key header value will be applied to the cache object.
VCL file size1MBAttempts to upload VCL via the API will fail if the VCL payload is larger.
VCL total size3MBAttempts to upload VCL via the API will fail if the VCL payload would cause your total service VCL to be larger than this.
restart limit3 restartsThe 4th invocation of the restart statement will trigger a 503 error. This error can be intercepted in vcl_error.
Edge dictionary item count1000Attempts to create dictionary items will fail if they exceed the limit. Contact support@fastly.com to discuss raising this limit.
Edge dictionary item key length256 charactersAttempts to create dictionary items will fail.
Edge dictionary item value length8000 charactersAttempts to create dictionary items will fail.

WARNING: Personal data should not be incorporated into VCL. Our Compliance and Law FAQ describes in detail how Fastly handles personal data privacy.


  1. The return state from vcl_log simply terminates request processing.
  2. Returning with return(deliver) from vcl_fetch cannot override an earlier pass, but return(pass) here will prevent the response being cached.
  3. The return(pass) exit from vcl_pass triggers a backend fetch, similarly to return(fetch) in vcl_miss but the altered return state is a reminder that the object is flagged for pass, so that it cannot be cached when processed in vcl_fetch.
  4. The only possible return state from vcl_hash is hash but it will trigger different behavior depending on the earlier return state of vcl_recv. The default return(lookup) in vcl_recv will prompt Fastly to perform a cache lookup and run vcl_hit or vcl_miss after hash. If vcl_recv returns error, then vcl_error is executed after hash. If vcl_recv returns return(pass), then vcl_pass is executed after hash. The hash process is required in all these cases to create a cache object to enable hit-for-pass.
  5. All return states from vcl_recv (except restart) pass through vcl_hash first. lookup and pass both move control to vcl_hash but flag the request differently, which will determine the exit state from vcl_hash.