Varnish Software Blog

varnish-rs: A Rust Framework for VMODs

Written by Guillaume Quintard | 9/21/26, 7:00 AM

I expect those who know me will have raised an eyebrow as they saw my previous blog post. “Guillaume wrote a blog post about go? I thought his whole schtick was rust?”. Don’t worry, I’m still a rust fanboy, and here’s a blog post  just to tell you how great it is!

I wrote about writing VMODs for the first time here more than 10 years ago, and I’ve been at it on and off ever since. Things have changed massively and it went from a cute experiment into a full-blown SDK for developers who want to leverage the power of Varnish and extend its VCL capabilities, create their own backends or more generally do useful and weird stuff in a fun language.

It’s high time for a quick recap and a presentation of one of my favorite projects ever.

What’s a VMOD again, and how do I write one?

If you’re new here, here’s the crash course:

  • The Varnish Configuration Language (VCL) is an imperative language that give a lot of freedom to the admin to pilot how Varnish will block, route, process and cache the traffic

  • Because speed matters, VCL is actually transpiled into (very safe) C, then compiled into a shared library that Varnish loads like a plugin

  • VCL also has support for VCL Modules[1], or “VMODs”, which can do a lot of things, from s3 signing to load-balancing to random body backends and a lot more

Because VCL is an imperative language, a VMOD doesn’t provide new configuration statements like you’d see in apache, instead you get functions and objects that you can use in your code like you would in python for example:

# import the std vmod import std; sub vcl_recv { # add a log line in the current transaction std.log(client.ip + "sent a request for " + req.url) }

This makes writing VMODs quite easy to write. I wrote a tutorial a while ago, but the gist of it is:

  • You create a vcc file that describes the API you want to implement (example), notably the name of the functions, arguments and return types

  • This runs a python script that will generate the boilerplate code and glue for the VMOD in C

  • Finally you can start hacking and actually implement the meat of your VCL

To give you an idea, here’s a vcc excerpt for vmod-str:

$Function BOOL endswith(STRING s1, STRING s2) Returns true if S1 ends with S2.

And the corresponding C code:

VCL_BOOL vmod_endswith(VRT_CTX, VCL_STRING s1, VCL_STRING s2) { const char *p; CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); if (s1 == NULL || s2 == NULL) return (0); p = s1 + strlen(s1) - strlen(s2); if (p < s1) return (0); return (!strcmp(p, s2)); }

Not too hard, but there's a problem.

C kinda sucks

There, I said it. I don’t really mean it, but I said it. C is an incredibly portable language, it’s simple, it’s fast and I think everybody should know the basics about it before programming because it’s an incredible base.

But it’s a gigantic foot gun full of undefined behavior, dangling pointers and null-terminated strings. I can hear the pitchforks being sharpened right now to the tune of “you just need to know what you’re doing!”, but I stand my ground: writing safe C is as hard as writing legible perl, sure it’s possible, but you’re going to slip at some point, and you’re going to fall. Hard.

“But Guillaume, isn’t Varnish written in C?”. You’re bloody right it is! And it’s the right choice for it: it’s incredibly fast, provides amazing control over memory allocation, and very little happens behind your back. That’s perfect for an HTTP reverse-proxy that wants to be extremely scalable and performant.

However, for my plugins, I want something different:

  • I don’t want a language that explodes in production with a grin blaming me because of a stupid mistake gcc could have caught at compile-time

  • I want a rich library ecosystem that I can leverage instead of having to write yet another JSON/YAML converter or a xoroshiro hasher

  • Above all, I want proper string support, I’ve had enough of looping through bytes manually, incrementing a pointer until I find a null character that would actually be valid in UTF-8

I could have picked go, zig, or nim, but chance had it that I fell in love with rust for a few reasons:

  • Tooling is good and modern (even though go‘s is better)

  • The FFI story that allows us to hook into C code is decent (yes, zig is better on that front)

  • Once you’ve passed the initial wall of fighting the borrow-checker, the compiler is extremely useful and a joy to work with.

Alright, turning the fanboy mode off, let’s actually dig in.

Ergonomics

The first varnish-rs version copied the C approach: it used a vcc file, a python script to convert it to rust boilerplate and you were on your own dealing with C structures. It was better than pure C, but not enough for Yuri of MapLibre fame who one day dropped in and started making some big changes to truly oxidize varnish-rs. Credit where credit is due, the project would look nothing like it does without him.

Fast forward a few years, and you can reimplement the str.endswith() function from above with stupidly simple rust:

/// a simple str vmod #[varnish::vmod(docs = "API.md")] mod str { /// Does s1 ends with s2? pub fn endswith(s1: Option<&str>, s2: Option<&str>) -> bool { match (s1, s2) { (Some(s1), Some(s2)) => s1.ends_with(s2), _ => false } } }

And the documentation is automatically generated in API.md:

# Varnish Module (VMOD) `str` a simple str vmod ```vcl // Place import statement at the top of your VCL file // This loads vmod from a standard location import str; // Or load vmod from a specific file import example from "path/to/libstr.so"; ``` ## Function `BOOL example.endswith([STRING s1], [STRING s2])` this comment will be seen in the docs

The compiler will catch you trying to do silly things (but you can opt-out if you want to), you will get automatic VCL to rust type conversion and you can leverage the full ecosystem of crates.io! You want a function that generates N words of lorem ipsum? Easy, use the lipsum crate and be done with it:

#[varnish::vmod(docs = "API.md")] mod str { pub fn lorem_ipsum(n: i64) -> String { # VCL gives us an i64, but lipsum wants an usize lipsum::lipsum(n.try_into().unwrap_or(0)) } }

Which can then be used in VCL:

import str; sub vcl_recv { // set the `lorem` header to "Lorem ipsum dolor sit amet" set req.http.lorem = str.lorem_ipsum(5); }

And that’s it! VMODs like vmod-reqwest (HTTP requests and dynamic backends) or vmod-rers (dynamic regular expressions and body manipulation) shamelessly leverage expert crates and focus on providing the necessary glue to be called from VCL.

And backends! And VDPs! And VFPs!

Where I think varnish-rs really shines is in the backend implementation. You can implement them very simply by defining two types:

  • A response struct implementing the VclResponse trait, i.e. what you should send as a body response

  • A backend struct implementing the VclBackend trait, which just sets the response headers and returns a VclResponse struct

You can see an implementation of an echo server in the example directory in the main repository, and 

vmod-reqwest and vmod-fileserver notably use this API to connect you to dynamic backends and to serve files from disk, but really, the sky is the limit!

But that’s not all, you can create Varnish Fetch/Delivery Processors (VDP/VFP) that won’t generate new data, but instead hook into either the fetch or delivery pipelines of Varnish and modify data on-the-fly. This is for example how vmod-rers can dynamically rewrite your documents before it enters the cache, and/or as it’s being delivered to each user.

Having a whole layer of abstraction here really saves the vmod writer from a bunch of gotchas. Thanks to the type system, we can enforce a lot of rules that C refuses to struggle with and that can only be enforced at runtime. To be fair, the safety net isn’t perfect (yet!) but it has absolutely freed me from worrying about a ton of details that would have surely bitten me later on.

And much much more

While the coverage and capabilities of varnish-rs isn’t equal to the core C framework, it’s close. There are counters, restricted call sites, subroutine calls, optional arguments and pretty much everything you might wish for. But in the end, varnish-rs is just a glue, what matters is what is built on top of it, and how users benefit from it.

So this feels like the perfect segue into two calls of actions. The first one is to join us on discord to discuss your VMODs and tooling ideas around Varnish, or just to chill, that works too!

The second call to action is that if you have a VMOD (rust or C) you’d like to see packaged, you just have to reach out: https://github.com/varnish/pkg-varnish-cache is our central repository that hydrates repositories for Debian, Ubuntu, the Red Hat family and Docker, and we are taking contributions!

[1]: Yes, technically, “VMOD” means “Varnish Configuration Language Module”, but who has the time to pronounce all that?