A Caddy storage plugin that uses Redis Standalone as the backend for sharing TLS certificates across multiple Caddy instances.
The plugin implements the certmagic.Storage interface:
- Storage operations —
Store,Load,Delete,Exists,List,Statmap certificate data to Redis keys using a suffix-type + colon-path schema. Multi-key writes useMULTI/EXECtransactions for atomicity. - Distributed locking —
Lock,Unlock,TryLock,RenewLockLeaseuse per-lock owner tokens with Lua scripts for atomic compare-and-swap operations. Locks auto-expire via TTL and are renewed in the background. - Directory tracking — Redis Sets track child names at each path level, enabling recursive
ListandDelete. Empty ancestor directories are pruned on a best-effort basis after deletes.
One constraint: Redis Standalone only. No Cluster or Sentinel support.
Add a storage block to your Caddy JSON config:
{
"storage": {
"module": "redis",
"url": "redis://localhost:6379/0",
"prefix": "caddy",
"lock_ttl": "60s",
"lock_renew_interval": "20s"
}
}| Field | Default | Description |
|---|---|---|
url |
redis://localhost:6379/0 |
Redis URL. Supports redis:// and rediss:// (TLS). Supports Caddy placeholders like {env.REDIS_URL}. |
prefix |
caddy |
Prefix for all Redis keys. |
lock_ttl |
60s |
TTL for distributed locks. |
lock_renew_interval |
20s |
Interval for lock lease renewal. Must be less than lock_ttl. |
Caddy placeholders ({env.*}) are resolved in all string fields. This is useful for injecting secrets:
{
"storage": {
"module": "redis",
"url": "{env.REDIS_URL}"
}
}Storage path separators (/) are converted to colons. A type suffix is appended to each key.
| Redis Key | Type | Purpose |
|---|---|---|
caddy:certificates:acme:example.com:example.com.crt:binary |
String | Certificate data |
caddy:certificates:acme:example.com:example.com.crt:metadata |
Hash | Metadata (modified, size, terminal) |
caddy:certificates:acme:example.com:directory |
Set | Child name list |
caddy:certname:lock |
String | Lock owner token (with TTL) |
docker build -t caddy-redis .The image includes:
github.com/techio-dev/caddy-redis-storage— this plugingithub.com/caddy-dns/cloudflare— Cloudflare DNS challengegithub.com/ss098/certmagic-s3— S3 storage for certmagic
Pass the MODULE_VERSION build arg to pin the plugin version:
docker build --build-arg MODULE_VERSION=v1.0.0 -t caddy-redis:v1.0.0 .Without this arg, the build uses the latest commit on the default branch.
Build Caddy locally with the plugin:
xcaddy build \
--with github.com/techio-dev/caddy-redis-storage@v1.0.0 \
--with github.com/caddy-dns/cloudflare \
--with github.com/ss098/certmagic-s3Drop the @v1.0.0 suffix to build from the latest commit.
docker run -e CONFIG_URL=http://config-server/caddy.json caddy-redisThe container starts with a default config that:
- Exposes the admin API on
0.0.0.0:2019 - Loads the full config from
$CONFIG_URLafter a 5-second delay
services:
caddy:
image: caddy-redis
ports:
- "80:80"
- "443:443"
- "443:443/udp"
- "2019:2019"
environment:
- CONFIG_URL=http://config-server/caddy.json
depends_on:
- redis
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
command: redis-server --appendonly yes
volumes:
redis-data:A typical Caddy JSON config using Redis storage with environment variables:
{
"storage": {
"module": "redis",
"url": "{env.REDIS_URL}",
"prefix": "caddy"
},
"apps": {
"http": {
"servers": {
"example": {
"listen": [":443"],
"routes": [
{
"match": [{"host": ["example.com"]}],
"handle": [{"handler": "static_response", "body": "Hello from Caddy Redis cluster"}]
}
],
"automatic_https": {
"disable": false
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": ["example.com"],
"issuers": [
{
"module": "acme",
"challenges": {
"dns": {
"provider": {
"name": "cloudflare",
"api_token": "{env.CF_API_TOKEN}"
}
}
}
}
]
}
]
}
}
}
}- Go 1.24+
- Redis instance for integration tests
# Unit tests
go test -v -run "TestKeys" ./...
# Integration tests (requires Redis on localhost:6379)
docker run -d --name test-redis -p 6379:6379 redis:7-alpine
go test -v -timeout 30s ./...- Multiple Caddy nodes connect to the same Redis instance.
- Only one node performs certificate acquisition at a time (distributed lock).
- All nodes read certificates from Redis — no local storage needed.
Apache License 2.0