Self-hosted service
darkweak/souin avatar
darkweak/souin

Souin: an RFC-shaped HTTP cache that plugs into the proxy you already run

An HTTP cache system, RFC compliant, compatible with @tyktechnologies, @traefik, @caddyserver, @go-chi, @bnkamalesh, @beego, @devfeel, @labstack, @gofiber, @go-goyave, @go-kratos, @gin-gonic, @roadrunner-server, @zalando, @zeromicro, @nginx and @apache

1,012 stars90 forksGoMIT

At a glance

What is it?
Souin is a Go HTTP cache that can sit in front of Apache or Nginx, run as its own reverse proxy, or be compiled into Caddy, Traefik, Tyk and a dozen Go frameworks. Its distinguishing feature is RFC fidelity, Vary, request coalescing, the Cache-Status header and targeted cache control, and its sharpest edge is that since v1.7.0 the storage backend is yours to supply.
Who is it for?
Souin fits a team that wants HTTP caching semantics they can explain to a browser or an API client, and that wants the cache to be a process in its own right rather than a second CDN. It does not fit an operator who wants distributed caching out of the box, because since v1.7.0 the shipped build carries only one in-memory map and a distributed store has to be compiled in.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The cache is a process, a plugin, or a middleware

Souin is an HTTP cache system suitable for every reverse proxy. It can be placed on top of your current reverse proxy, whether that is Apache or Nginx, or installed as a plugin inside a proxy you already run, such as Traefik, Caddy or Tyk.

That second mode is where the project's ambition shows. The plugin list in the README is long and covers Go frameworks rather than only proxies: Beego, Chi, Dotweb, Echo, Fiber, Gin, Goa, Go-zero, Goyave, Hertz, Kratos, Roadrunner, Skipper and Webgo, alongside the Caddy module, the Traefik plugin and the Tyk plugin. The repository description adds Nginx and Apache to the compatibility list.

The reason a cache can sit in so many places is that it is written in Go and shipped as a library with a thin adapter per framework, and the Makefile shows the discipline behind that. There is a build-and-run target for every plugin in the list, beego through webgo and souin itself, and a separate PLUGINS_LIST and MOD_PLUGINS_LIST constant that keeps the two families apart, the second excluding the proxy plugins that are not built the same way.

The audience is therefore two overlapping groups. An operator who wants a cache they can run as a container next to their stack, and a Go developer whose application is behind a framework and who would rather add one middleware than rearchitect around a proxy. The documentation site is at docs.souin.io, and the project describes the container route as easy to install on top of a Swarm or a Kubernetes instance.

RFC compliance is the feature, not a footnote

What separates Souin from a caching middleware that happens to store responses is the list of specifications it implements.

The baseline is RFC 7234, with support for Vary, request coalescing, stale cache-control and the other specifications that document describes. Vary matters because it is the mechanism that lets one URL serve different representations to different clients, and getting it wrong is how a cache leaks one user's response to another. Request coalescing matters because without it, a cold cache under a burst of traffic sends the same request to the origin many times at once.

On top of that, Souin supports the http-cache-groups and http-invalidation drafts, which are still in draft, plus the Cache-Status HTTP response header from RFC 9211, the YKey group in the style of Varnish, and the Targeted HTTP Cache Control RFC, RFC 9213. The Cache-Status header is the one users will notice first, because it tells a client or a proxy what a cache did with the response.

There is also support for ESI tags, through the go-esi package, which lets a cached page be assembled from several cached fragments at the edge instead of being one monolithic HTML document. That is a meaningful difference for a site whose pages are composed of many independent pieces.

The honest caveat is that a specification being implemented is not the same as a specification being implemented perfectly, and the README does not make claims about conformance testing. What it does claim is a specific set of behaviours, and that specificity is what you can test against.

Since v1.7.0 the storage backend is a build-time decision

The warning block in the README is the single most important operational fact about Souin.

Since v1.7.0, Souin implements only one in-memory map storage. If you need a specific storage, you have to take it from the storages repository and add it either in your code or during the build, otherwise. The example given is otter with Caddy, and the build command is:

bash
xcaddy build --with github.com/darkweak/souin/plugins/caddy --with github.com/darkweak/storages/otter/caddy

After that you configure otter in your Caddyfile or JSON configuration file. The README links both the storages section and the documentation website for the details.

The reason this matters is that the default build is now the simplest possible storage rather than the most capable one. A team that assumed installing the module gave them a distributed or disk-backed cache would be wrong, and would find out at the point where they needed durability. The go.mod confirms the shape: the core depends on github.com/darkweak/storages/core, and the distributed option, when it is compiled in, uses Olric or Etcd, which the configuration exposes as a distributed flag on the default cache.

This is a deliberate trade, and it has a defensible logic behind it. A cache module that has to be linked into a proxy binary cannot drag in every storage driver, and pulling optional drivers in at build time keeps the footprint of the plugin small. The cost is that the storage choice becomes a build concern, and the documentation has to be read before the first deployment rather than after it.

The cache key rules are the configuration you will actually tune

Once the basics are in place, the part of the configuration that determines whether Souin behaves the way you expect is the cache key.

The default_cache block shows the general shape. A key can disable the use of the request body, the host, the method, the query string, the scheme and the varied headers in building the key, and there is a hash option to hash the key rather than store it in plaintext, plus a hide option to keep the key out of the Cache-Status response header. A key can also add headers, such as Authorization and Content-Type, so that two requests differing only in those headers do not collide, and it can use a template built from placeholders instead of the individual parts.

The per-pattern mechanism is cache_keys, keyed by a regular expression. The example matches CSS files and turns off the body, host, method, query, scheme and vary components for them, and enables hashing and hiding.

Two settings in that block deserve attention for security. The hash option exists because a cache key can otherwise contain a header value, and an Authorization header in a plaintext key is a credential in a log or a metrics label. And the template option, when enabled, skips the individual disable directives entirely, which is the kind of interaction that produces a cache key nobody fully understands six months later.

The README also recommends setting default_cache.default_cache_control to avoid undesired caching for responses without a Cache-Control header, which it calls highly recommended. It is the difference between a cache that honours the origin and a cache that invents freshness.

A YAML file, a TTL, and the address of your origin

The configuration lives at /anywhere/configuration.yml, and you can supply your own as long as it uses one of the minimal shapes. As a plugin inside a proxy, the minimum is a TTL:

yaml
default_cache: # Required
  ttl: 10s # Default TTL

Run as a standalone cache in front of your service, the same block plus the origin address:

yaml
default_cache: # Required
  ttl: 10s # Default TTL
reverse_proxy_url: 'http://traefik' # If it's in the same network you can use http://your-service, otherwise just use https://yourdomain.com

The comment is the important part of that second block. Inside a container network the origin is the service name, and outside it is the public URL of your domain, which is the difference between a cache that sits next to the app and one that sits at the edge.

The default TTL is ten seconds in both examples, and the configuration table gives the same value as a number, describing default_cache.ttl as the duration to cache a request in seconds. The value is deliberately short in the documentation, which is the right default to ship: a cache with a ten second TTL is hard to get wrong, and lengthening it is a decision made with the origin's behaviour in mind.

For local development, the Makefile has a target per plugin that builds the plugin and runs the example, and the caddy one is a plain directory change and a run of the caddy binary, with a separate variant that uses the JSON configuration instead of the Caddyfile.

Three API surfaces: metrics, cache management, and security

Souin exposes its own HTTP endpoints, and the configuration gives each of them a basepath under the api section, with the default route basepath for every additional API set to /souin-api to avoid conflicts with existing routes.

There are three of them. The Prometheus API exposes metrics, with its own basepath that defaults to a placeholder you are expected to change. The Souin API is the listing and cache management surface, with a basepath of your choosing, and it is where you inspect the keys and evict or purge entries. The Security API is the third.

The basepath configuration is a small thing with a large consequence. A cache management endpoint that answers on a guessable path is a denial of service waiting for a script, and a metrics endpoint on the public internet leaks the shape of your traffic. The default value in the documentation is literally called anything-for-prometheus-metrics, which reads like a placeholder because it is one, and the same is true of the Souin basepath.

There is also a CDN section in the configuration, for the case where Souin sits behind a provider such as fastly, cloudflare, akamai or varnish. You supply the provider API key if the provider needs one, choose a purge strategy of soft or hard, and set dynamic to true if you want to add custom keys beyond those defined under the surrogate_keys key. The purge-by-tag model is the interesting part, because it is how you invalidate a cache across two tiers without guessing which URLs changed.

MIT licensed, Go 1.25, and a plugin matrix that has to be maintained by hand

The licence is MIT, with the LICENSE file at the root of the repository.

The Go version requirement is 1.25.0, declared in go.mod, and the direct dependencies describe the implementation rather precisely: Caddy v2.11.2 for the Caddy module, xxhash for hashing, the storages core package, google/uuid, lz4 for compression, pquerna/cachecontrol for the cache-control parsing that the RFC behaviour depends on, the Prometheus client libraries, zap for logging, and yaml.v3 for the configuration file.

The release history shows a project that ships steadily but not fast. Version 1.7.7 was published on 2025-06-02, 1.7.8 on 2025-09-18, and 1.7.9 on 2026-09-16, and the last push to the repository was on 2026-10-16. The gap between 1.7.8 and 1.7.9 is close to a year, which is worth noting for anyone who plans to depend on the plugin and pin a version.

The plugin matrix is the real maintenance cost, and it is visible in the tree. There is a plugins directory with a subdirectory per framework, a Dockerfile-dev and a Dockerfile-prod, three docker-compose files for development, production and test, a gatling directory for load testing, a tests directory, and the standard Go layout with configuration, configurationtypes, context, helpers and pkg. Every framework adapter is a place where an upstream API change breaks the build, and the Makefile's long list of build-and-run targets is the visible cost of keeping them all honest.

The project also carries the usual release machinery, with a.goreleaser.yml for building the binaries and a.golangci.yml for the linter configuration, and a.traefik.yml at the root.

Editorial conclusion

Souin fits a team that wants HTTP caching semantics they can explain to a browser or an API client, and that wants the cache to be a process in its own right rather than a second CDN. It does not fit an operator who wants distributed caching out of the box, because since v1.7.0 the shipped build carries only one in-memory map and a distributed store has to be compiled in. Before you adopt it, read the storage section of the documentation at docs.souin.io, decide which cache key rules you want, and set default_cache.default_cache_control, since the README calls that highly recommended to avoid caching responses that carry no Cache-Control header.

Frequently asked questions

What is Souin and how is it deployed?

Souin is an HTTP cache system written in Go that can be placed on top of an existing reverse proxy such as Apache or Nginx, run as its own reverse proxy in front of a service, or compiled into a plugin for Caddy, Traefik, Tyk and many Go frameworks. The configuration file is expected at /anywhere/configuration.yml.

Why do I need to build a custom storage for Souin since v1.7.0?

The README states that since v1.7.0 Souin implements only one in-memory map storage, and that a specific storage has to be taken from the storages repository and added in your code or during the build. The example is xcaddy build --with github.com/darkweak/souin/plugins/caddy --with github.com/darkweak/storages/otter/caddy.

Which HTTP specifications does Souin implement?

The README says it is RFC compatible, supporting Vary, request coalescing and stale cache-control under RFC 7234, plus the http-cache-groups and http-invalidation drafts, the Cache-Status response header from RFC 9211, the YKey group in the style of Varnish, the Targeted HTTP Cache Control RFC 9213, and ESI tags through the go-esi package.

How do I control what goes into a Souin cache key?

The cache_keys section is matched by regular expression, and each entry can disable the body, host, method, query, scheme and vary components of the key, hash the key instead of storing it in plaintext, hide it from the Cache-Status header, add extra headers to the key, or use a template. The README recommends setting default_cache.default_cache_control to avoid caching responses without a Cache-Control header.

Does Souin expose metrics and cache management endpoints?

Yes. The configuration has an api section with basepaths for the Prometheus metrics endpoint, the Souin endpoint used for listing keys and cache management, and a third security API, with a default route basepath of /souin-api to avoid conflicts with existing routes.

Official sources

  1. darkweak/souin on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/darkweak-souin.svg)](https://hysenlabs.com/projects/darkweak-souin)

Was this useful?