Skip to main content
Anubis requires persistent storage for tracking challenges, cookies, and DNS cache. Choose a storage backend based on your deployment architecture.

Available Backends

BackendPersistenceMulti-InstanceUse Case
memoryNoNoDevelopment/testing
bboltYesNoSingle-instance production
valkeyYesYesMulti-instance deployments
s3apiYesYesCloud/serverless

Memory Backend

In-memory hashmap storage. Data is lost on restart.

Configuration

store:
  backend: memory
  parameters: {}
This is the default if no store is configured.

When to Use

βœ… Use for:
  • Local development
  • Testing
  • Single-instance with low traffic
  • Ephemeral environments
🚫 Don’t use for:
  • Production with high traffic
  • Multi-instance deployments
  • When you need persistence across restarts

Limitations

  • No size limits (memory grows unbounded)
  • Data lost on process restart
  • Cannot share state between instances

BoltDB Backend

Embedded key-value database using bbolt. Data persists to disk.

Configuration

store:
  backend: bbolt
  parameters:
    path: /data/anubis.bdb
ParameterTypeRequiredDescription
pathstringYesFilesystem path to database file

When to Use

βœ… Use for:
  • Single-instance production deployments
  • High-traffic single-instance setups
  • When persistent storage is required
  • VPS/dedicated server deployments
🚫 Don’t use for:
  • Multi-instance deployments (Kubernetes, etc.)
  • Environments without persistent filesystem
  • Multiple Anubis instances sharing a backend

File System Requirements

Anubis requires:
  1. Write access to the directory containing the database file
  2. Exclusive lock on the database file (one process only)
# Create directory
mkdir -p /var/lib/anubis

# Set permissions
chown anubis:anubis /var/lib/anubis
chmod 750 /var/lib/anubis
Configuration:
store:
  backend: bbolt
  parameters:
    path: /var/lib/anubis/anubis.bdb

Cleanup

BoltDB runs hourly cleanup to remove expired entries. No manual maintenance required.

Docker Example

services:
  anubis:
    image: ghcr.io/techarohq/anubis:latest
    volumes:
      - anubis-data:/data
      - ./policy.yaml:/etc/anubis/policy.yaml:ro
    environment:
      - POLICY_FNAME=/etc/anubis/policy.yaml

volumes:
  anubis-data:
Policy file:
store:
  backend: bbolt
  parameters:
    path: /data/anubis.bdb

Valkey/Redis Backend

Remote key-value store using Valkey or Redis compatible servers.

Configuration

Standalone

store:
  backend: valkey
  parameters:
    url: redis://valkey.example.com:6379/0

Cluster

store:
  backend: valkey
  parameters:
    url: redis://valkey.example.com:6379/0
    cluster: true

Sentinel (High Availability)

store:
  backend: valkey
  parameters:
    sentinel:
      masterName: mymaster
      addr:
        - sentinel1.example.com:26379
        - sentinel2.example.com:26379
        - sentinel3.example.com:26379
      username: anubis
      password: ${VALKEY_PASSWORD}
      clientName: anubis-prod

Parameters

ParameterTypeRequiredDescription
urlstringYes*Redis connection URL
clusterboolNoEnable cluster mode
sentinelobjectNoSentinel configuration (see below)
*Either url or sentinel must be provided.

Sentinel Parameters

ParameterTypeRequiredDescription
masterNamestringYesSentinel master name
addrstring or listYesSentinel server addresses
usernamestringNoAuth username
passwordstringNoAuth password
clientNamestringNoClient identifier

When to Use

βœ… Use for:
  • Multi-instance deployments
  • Kubernetes/Docker Swarm
  • High-availability requirements
  • Shared state across instances
🚫 Don’t use for:
  • Single-instance deployments (use bbolt instead)
  • If you don’t already have Redis/Valkey
  • Low-traffic sites (overhead not justified)

URL Format

redis://[username:password@]host[:port][/database]
Examples:
# Basic
url: redis://localhost:6379/0

# With auth
url: redis://user:[email protected]:6379/0

# TLS (rediss://)
url: rediss://valkey.internal:6380/0

Kubernetes Example

apiVersion: v1
kind: ConfigMap
metadata:
  name: anubis-policy
data:
  policy.yaml: |
    store:
      backend: valkey
      parameters:
        url: redis://valkey-service:6379/0
    bots:
      - name: browsers
        user_agent_regex: Mozilla
        action: CHALLENGE
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: anubis
spec:
  replicas: 3  # Multiple instances share Valkey
  template:
    spec:
      containers:
      - name: anubis
        image: ghcr.io/techarohq/anubis:latest
        env:
        - name: POLICY_FNAME
          value: /etc/anubis/policy.yaml
        volumeMounts:
        - name: policy
          mountPath: /etc/anubis
      volumes:
      - name: policy
        configMap:
          name: anubis-policy

S3 API Backend

Object storage backend using S3-compatible APIs.

Configuration

store:
  backend: s3api
  parameters:
    bucketName: anubis-production
    pathStyle: false

Environment Variables

S3 credentials are read from environment:
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_S3=https://s3.amazonaws.com  # Optional

Parameters

ParameterTypeRequiredDescription
bucketNamestringYesS3 bucket name
pathStyleboolNoUse path-style URLs (default: false)

When to Use

βœ… Use for:
  • Serverless deployments (AWS Lambda, Cloud Run)
  • Multi-instance without managing Redis
  • Cloud-native deployments
  • When object storage is already available
🚫 Don’t use for:
  • High-frequency traffic (S3 API calls are expensive)
  • Latency-sensitive applications
  • If you can use Redis/Valkey instead

Supported Providers

  • AWS S3
  • Cloudflare R2
  • Minio
  • Tigris
  • Backblaze B2
  • DigitalOcean Spaces
  • Any S3-compatible service

Lifecycle Policy

Configure bucket lifecycle to auto-delete old objects:
{
  "Rules": [
    {
      "Status": "Enabled",
      "Expiration": {
        "Days": 7
      }
    }
  ]
}
Anubis stores expiry metadata but doesn’t auto-cleanup. Lifecycle policies prevent unbounded growth.

Example: Cloudflare R2

# Environment
AWS_ACCESS_KEY_ID=<r2-access-key>
AWS_SECRET_ACCESS_KEY=<r2-secret>
AWS_ENDPOINT_URL_S3=https://<account-id>.r2.cloudflarestorage.com
store:
  backend: s3api
  parameters:
    bucketName: anubis-data
    pathStyle: false

Storage Interface

All backends implement the same interface:
type Interface interface {
    Get(ctx context.Context, key string) ([]byte, error)
    Set(ctx context.Context, key string, value []byte, expiry time.Duration) error
    Delete(ctx context.Context, key string) error
    IsPersistent() bool
}

Choosing a Backend

Decision Tree

Are you running multiple Anubis instances?
β”œβ”€ Yes β†’ Use valkey or s3api
β”‚  β”œβ”€ Already have Redis/Valkey? β†’ Use valkey
β”‚  └─ Using serverless/cloud? β†’ Use s3api
└─ No β†’ Single instance
   β”œβ”€ Production? β†’ Use bbolt
   └─ Development? β†’ Use memory

Comparison

Featurememorybboltvalkeys3api
PersistenceβŒβœ…βœ…βœ…
Multi-instanceβŒβŒβœ…βœ…
Setup complexityNoneLowMediumMedium
PerformanceFastestFastFastSlower
Operational overheadNoneLowMediumLow
CostFreeFree$-$$$$-$$$

Key Generation Warning

When using persistent storage (bbolt, valkey, s3api), always configure a signing key to prevent challenge invalidation on restart:
# Generate Ed25519 key
openssl rand -hex 32 > /etc/anubis/ed25519.key

# Use it
anubis --ed25519-private-key-hex-file /etc/anubis/ed25519.key
See Security for details.

Validation Errors

BoltDB

store:
  backend: bbolt
  parameters: {}  # Error: ErrMissingPath
Fix: Provide path:
store:
  backend: bbolt
  parameters:
    path: /data/anubis.bdb

Valkey

store:
  backend: valkey
  parameters: {}  # Error: ErrNoURL
Fix: Provide URL:
store:
  backend: valkey
  parameters:
    url: redis://localhost:6379/0

S3 API

store:
  backend: s3api
  parameters: {}  # Error: ErrNoBucketName
Fix: Provide bucket:
store:
  backend: s3api
  parameters:
    bucketName: anubis-data

Next Steps

Build docs developers (and LLMs) love