Axon Docs
GitHub Quickstart

Docs / Guides

Storage backends

By default, job and recurring-job state live in memory and are lost on restart. To persist them, run the chosen backend's Schema.sql against your database, then register it. Only register one backend — whichever call runs last wins, since they all replace the same underlying services.

Program.cs
// SQL Server: run Axon.Store.SqlServer/Schema.sql first
var sqlConnectionString = builder.Configuration["Axon:SqlConnectionString"];
if (!string.IsNullOrEmpty(sqlConnectionString))
    builder.Services.AddAxonSqlServerStore(sqlConnectionString);

// PostgreSQL: run Axon.Store.Postgres/Schema.sql first
var postgresConnectionString = builder.Configuration["Axon:PostgresConnectionString"];
if (!string.IsNullOrEmpty(postgresConnectionString))
    builder.Services.AddAxonPostgresStore(postgresConnectionString);

// MySQL: run Axon.Store.MySql/Schema.sql first
var mySqlConnectionString = builder.Configuration["Axon:MySqlConnectionString"];
if (!string.IsNullOrEmpty(mySqlConnectionString))
    builder.Services.AddAxonMySqlStore(mySqlConnectionString);

// SQLite: run Axon.Store.SQLite/Schema.sql first. Single-instance/local-dev only.
var sqliteConnectionString = builder.Configuration["Axon:SQLiteConnectionString"];
if (!string.IsNullOrEmpty(sqliteConnectionString))
    builder.Services.AddAxonSQLiteStore(sqliteConnectionString);

// MongoDB: must point at a replica set. No schema to run first.
var mongoConnectionString = builder.Configuration["Axon:MongoConnectionString"];
if (!string.IsNullOrEmpty(mongoConnectionString))
    builder.Services.AddAxonMongoDbStore(mongoConnectionString, databaseName: "axon");

Which one to pick

BackendMulti-instance safeNotes
SQL ServerYesUPDLOCK, HOLDLOCK on the claim range.
PostgreSQLYespg_advisory_xact_lock keyed on concurrency group.
MySQLYesSELECT ... FOR UPDATE under next-key locking.
MongoDBYesRequires a replica set (even single-node) — TryClaimJob's concurrency check runs inside a multi-document transaction, which a standalone server can't open. Managed offerings (Atlas, etc.) are already a replica set. Self-hosted: start mongod --replSet rs0 and run rs.initiate() once.
SQLiteNoSingle-writer model means it does not support the multi-instance dispatch scenario the other backends target. Use it for a single-instance deployment or local development only.

Multi-instance safety means: when several Axon.Server instances share one store, only one of them ever wins the atomic claim on a given job — see Architecture for how each backend enforces that.

Redis backplane

If you're running multiple Axon.Server instances behind a load balancer, chain .AddRedisBackplane(...) so job dispatch and dashboard updates reach a client no matter which instance's WebSocket it's connected to:

Program.cs
builder.Services.AddAxonServer()
    .AddRedisBackplane(builder.Configuration["Axon:RedisConnectionString"]!)
    .AddAxonDashboard();

Without this, a client's connection is pinned to whichever instance accepted it, so a job dispatched by instance A never reaches a client connected to instance B. This is a separate concern from storage: dispatch correctness across instances comes from the atomic claim above, regardless of whether a backplane is configured. The backplane is specifically about routing a client's inbound WebSocket traffic to the right instance.

Job data cleanup

By default, completed jobs (Succeeded/Failed/Skipped) and their history are kept forever. Chain .AddJobCleanup(...) to purge them after a retention window:

Program.cs
builder.Services.AddAxonServer()
    .AddAxonDashboard()
    .AddJobCleanup(retention: TimeSpan.FromDays(30));

A background sweep (once an hour by default; override with the pollInterval parameter) deletes completed jobs whose most recent history entry is older than retention. Enqueued/Scheduled/Processing/AwaitingParent jobs are never touched, regardless of age.