Learn Authelia - Performance Tuning
Episode 28 of 31

Learn Authelia - Performance Tuning

Authelia is lightweight, but there are still points that can slow down: Redis, database connections, and the reverse proxy. This episode covers benchmarking, Redis and PostgreSQL tuning, keep-alive and static asset caching at the proxy, and capacity planning for real user scales.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

Episode 27 made Authelia safe: data can be recovered at any time. Now you're adding users — dozens, hundreds, thousands. The next question arises: does this gate stay fast when everyone logs in at once? Episode 28 answers it: performance tuning.

Authelia is written in Go and is very lightweight — at idle it uses memory below a few dozen megabytes. That means the bottleneck is almost never in the Authelia binary itself. The points to watch are around it: connections to Redis, the database connection pool, and the reverse proxy in front. Good tuning is reducing work that doesn't actually need to be done.

Measure First, Tune Later

The first rule you've learned in previous episodes: don't tune without a baseline. Before changing anything, build base numbers with a benchmark. Start from the lightest endpoint — the health endpoint — for example with hey -n 10000 -c 100 http://127.0.0.1:9091/api/health:

Baseline with hey
hey -n 10000 -c 100 http://127.0.0.1:9091/api/health

Then benchmark a more realistic flow — the verify endpoint with a session cookie and Remote-User header, as the reverse proxy does on every request:

Benchmarking the verify endpoint
hey -n 5000 -c 50 \
  -H "Cookie: authelia_session=<cookie-value>" \
  -H "Remote-User: arman" \
  "https://auth.example.com/api/verify?rm=0"

Note the requests per second numbers and p50/p99 latencies. Change one variable, measure again, compare. Every optimization in this episode is that cycle — measure, change, measure again.

Tip

For load tests that can enter CI, tools like hey, ab, or k6 can be run as repeatable benchmarks. Save the results in a document — when the scale grows, you need a reference to know whether performance dropped because of a configuration change or data growth.

Tuning Redis: The Session Store

Redis is the component most frequently touched by requests. Every verify and every session access hits it, so its connections and memory policy have direct impact.

Authelia manages its own Redis connection pool. The maximum_active_connections and minimum_idle_connections values determine how many parallel connections are kept:

configuration.yml — Redis connection pool
session:
  redis:
    host: redis
    port: 6379
    database_index: 0
    maximum_active_connections: 16
    minimum_idle_connections: 4

On the Redis server side, watch the memory policy. Authelia sessions use TTL, so volatile-lru eviction — only evicting keys that have a TTL — is the right choice; non-session keys you might store on the same instance won't be evicted:

redis.conf — for session load
maxmemory 1gb
maxmemory-policy volatile-lru
appendonly yes
appendfsync everysec

appendonly yes with per-second fsync gives a balance between session durability (episode 27) and write performance. If sessions don't need to survive a restart, RDB snapshots alone are enough and faster. Verify the active policy with redis-cli CONFIG GET maxmemory-policy.

Tuning the Database: Pool and Maintenance

Authelia writes to the database only on changes — first login, MFA registration, OIDC tokens. That makes the database not a per-request critical path, except for one thing: wasteful connections. Each Authelia instance opens its own connection pool; with many replicas, total connections can pile up.

Set the per-instance pool as needed, then keep the total well below PostgreSQL's max_connections:

configuration.yml — database connection pool
storage:
  postgres:
    address: tcp://postgres:5432
    database: authelia
    username: authelia
    password: '<password>'
    maximum_active_connections: 12
    minimum_idle_connections: 2

A rough formula: PostgreSQL max_connections minus maintenance connections, divided by the number of Authelia instances. For medium scale, a pool of 8-12 per instance is already plenty. PgBouncer in front of PostgreSQL solves the many instances, one database problem by sharing connections from a small pool.

Don't forget routine maintenance. Keep autovacuum enabled (on by default) so heavily written tables don't bloat, and monitor long-running queries via the monitoring from episode 26. Check connection health periodically with psql -U authelia -h postgres -d authelia -c "SELECT 1". Authelia's schema has its own indexes — don't create extra indexes without load data showing they're needed.

Reverse Proxy Tuning

Because all traffic passes through the proxy, this is where the biggest wins come with little effort:

  • Connection keep-alive. Every request that opens a new TCP connection to Authelia pays a handshake cost. With keepalive, connections are reused:
nginx.conf — keepalive to the Authelia upstream
upstream authelia {
    server authelia-1:9091;
    server authelia-2:9091;
    keepalive 32;
}
 
server {
    listen 443 ssl;
    server_name auth.example.com;
 
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
 
    location / {
        proxy_pass http://authelia;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
  • Static asset caching. The Authelia login page loads CSS and JavaScript that rarely change. The proxy can serve them from cache without touching Authelia at all — the easiest chunk of work to eliminate.
  • TLS session resumption. ssl_session_cache makes the TLS handshake short for repeat connections — a real latency drop for returning users.

Horizontal Scaling and Capacity Planning

Authelia is designed to be scaled horizontally — exactly what was built in episodes 24 and 25. When load rises, adding instances is easier than fiddling with configuration. But watch two limits that don't grow along with it:

  1. Connections to Redis. Each instance opens a pool; make sure total connections stay below Redis capacity.
  2. Connections to the database. Same story — this is why PgBouncer exists.

For capacity planning, start from real numbers, not guesses: measure peak active sessions (Redis metrics or logs), compute authentication RPS from the authelia_authn_total metric in episode 26, then multiply by a rule of thumb: one modern Authelia instance easily handles hundreds of verification RPS. For thousands of users with simultaneous peaks, two to four instances are almost always enough. Give 2-3 times headroom over the peak need, and use HPA (episode 25) so scaling is automatic during surges.

Closing

Episode 28 trains you to tune performance with data: building a baseline with the hey benchmark on the health and verify endpoints, setting the Redis connection pool and the volatile-lru eviction policy, keeping the database pool small with PgBouncer as an umbrella, optimizing the reverse proxy via keep-alive, static asset caching, and TLS session resumption, and doing number-based capacity planning.

Key points:

  • Measure first; benchmark health and verify before changing anything.
  • Redis must have maxmemory-policy volatile-lru so TTL sessions are evicted correctly.
  • The database connection pool is the most common source of problems with many instances.
  • Keep-alive and static asset caching are proxy optimizations with the best returns.
  • The scaling limit isn't Authelia's CPU, but connections to Redis and the database.

Even with the best configuration, a day will come when something breaks. In episode 29 we dissect Troubleshooting & Debugging: 503 from the proxy, redirect loops, session problems, TOTP failures, up to reading logs and validating the configuration. See you in episode 29!

Learn Authelia - Performance Tuning | Learn Authelia