Memory
The formula
peak ≈ 12 MiB base (14.5 MiB with glibc)
+ 5.5 MiB if an S3 backend is used
+ hash_workers × m + 2 MiB password checks
+ sessions × 0.15 MiB
+ connections in the hash queue × 70 KiB at most hash_queue
+ local transfers × 4 MiB
+ S3 uploads × 26 MiB + S3 downloads × 8 MiB
+ addresses tracked by bans × 225 B
m is the largest argon2 m in the users file: 19 MiB with the
owasp-min profile (default of hash-password), 64 MiB with the
rfc9106-low-mem profile. The startup line gives this term:
peak_memory="1 thread × 19 MiB = 19 MiB".
See Password hashing.
The formula is an upper bound: it adds up peaks that do not all happen at the same moment. No measured case has exceeded it. File size does not enter it: transfers are streamed.
The terms are measurements on the published image (static musl binary,
x86_64), with an OpenSSH client. A connection in the hashing queue weighs
~42 KiB in Basic, a local download 2.3 MiB. At large scale with S3, uploads
dominate: 4 threads at m=65536 and 10 S3 uploads measure 415 MiB
(formula: 537). Nothing caps the number of concurrent S3 uploads;
max_sessions_per_user sets a bound per user.
A small deployment under 80 MB
30 users, ~900 files per day: a few concurrent clients.
[auth]
hash_workers = 1 # one check at a time: 1 × 19 MiB
hash_queue = 32 # a full queue: 32 × 70 KiB = 2.2 MiB
With hash-password hashes (profile owasp-min), and no
rfc9106-low-mem hash in the file:
| Variant | Formula | Fits under 80 MB |
|---|---|---|
| local backend | 45.5 MiB | yes |
| 2 threads, local backend | 64.5 MiB | yes |
| S3, one upload at a time | 64.7 MiB | yes |
| S3, 3 concurrent uploads | 116.5 MiB | no |
profile rfc9106-low-mem | 90.5 MiB | no: a single verification exceeds the budget |
| default queue (1024) | +70 MiB with a full queue | no |
glibc binaries
The Docker images (musl) give memory back after each peak. A glibc binary
(releases, glibc host) keeps the memory of the hashes in its arenas:
95 MiB retained after a burst (4 threads, m=19456), 19 MiB with
MALLOC_MMAP_THRESHOLD_=4194304. Set this variable (Environment= in the
systemd unit) and do not set MALLOC_ARENA_MAX, which raises the peak.
Kubernetes
| Setting | What it must cover |
|---|---|
limits.memory | the peak of the formula, for the expected number of concurrent transfers and sessions, plus a margin |
requests.memory | memory at rest and under ordinary load: base and sessions |
The chart ships requests.memory: 64Mi and limits.memory: 256Mi. With its
default (1 thread, derived from requests.cpu: 100m), 1000 SSH connections
queued with the rfc9106-low-mem profile reach 146 MiB. On a busy node, raise
the request toward the expected peak.
A pod without limits.cpu sees the node’s CPUs: see
Password hashing to set the pool size.
Watching memory
/metrics exposes process_resident_memory_bytes and
process_peak_resident_memory_bytes (Linux only), and the admin console plots
them live. See Metrics.
Not measured: REST transfers, SFTP proxy, active telemetry, thousands of users, aarch64 and Windows.