Repository navigation
Config Server
This page covers the keys of the [server] section: where ALPS listens, which reverse proxies and origins it trusts, login_key, the users' own CalDAV and CardDAV servers, the temp directory, session lifetimes and caps, HTTP timeouts, attachment limits, new-mail push and IMAP change numbers. Each group has a table (key, default, meaning) and a short example. The full reference is section 1 of docs/CONFIGURATION.md. The login rate limits, [server.rate_limit], are on Config-Rate-Limiting-and-WebAuthn.
- The keys go under
[server]in the file passed withalps -config <path>. ALPS reads the file once, at startup. Restart ALPS to apply a change: SIGHUP only reopens the log file. - ALPS ignores a key it does not know and keeps that key's default, but logs a warning for it at startup, naming the key and not its value. A misspelt
write_timeout = 60(forwrite_timeout_sec) starts ALPS as if the line were not there, and logsUnknown configuration key: server.write_timeout (ignored). Check the startup warnings after each change, and key names against config.example.toml. - A known key with a value of the wrong type stops startup with exit status 1, for example:
Error loading config file: failed to parse TOML config: toml: line 2 (last key "server.max_sessions"): incompatible types: TOML value has type string; destination has type integer - For the number keys, 0 or a negative value keeps the default. So the timeouts and the attachment limits cannot be turned off. The exceptions are
max_session_minutes, where 0 or a negative value means no cap, andabsolute_session_hours,max_sessionsandmax_sessions_per_user, where 0 keeps the default and a negative value, such as-1, removes the limit.
| Key | Default | Meaning |
|---|---|---|
addr |
":1323" |
The address and port ALPS listens on. ":1323" is every interface. With [tls] enabled = true, the same address serves HTTPS (see Config-TLS). |
debug |
false |
For development only. Where [logging] does not set them, it changes the default log level to debug and the default format to console text. It logs TLS handshake errors, which are otherwise dropped. It also turns on the IMAP, CalDAV, CardDAV and ManageSieve protocol traces, which go through [logging] as protocol trace lines at debug level, so they appear only while the log level is debug. These include message, contact and calendar content (passwords, and the HTTP Authorization and cookie headers, are redacted). Keep it off in production. See Config-Logging-and-Cache. |
When a reverse proxy runs on the same host, listen on the loopback address only, so that only the proxy reaches ALPS:
[server]
addr = "127.0.0.1:1323"At startup ALPS logs Starting server on 127.0.0.1:1323.
| Key | Default | Meaning |
|---|---|---|
trusted_proxies |
none | Addresses or CIDR blocks of your own reverse proxies. A bare address counts as /32 (IPv4) or /128 (IPv6). Blank entries are skipped, and any other entry that does not parse stops startup (invalid trusted_proxies IP/CIDR "10.0.0.300/32"). ALPS reads forwarded headers only on requests whose TCP peer is in this list. |
trusted_origins |
none | Extra origins the CSRF check accepts, beside the one ALPS works out from the request. Write each as scheme://host or scheme://host:port. Case does not matter and a trailing / is dropped, but the port must match. Use it when the proxy cannot send X-Forwarded-Proto and X-Forwarded-Host. |
On a request from a listed proxy, ALPS reads these headers:
| Header | What ALPS uses it for |
|---|---|
X-Forwarded-For |
The client's address, as used by the access log, the sign-in log lines and the per-address rate limits. ALPS reads the list from the right, skips the addresses in trusted_proxies, and takes the first address that is not in it. |
X-Real-IP |
The client's address, when X-Forwarded-For gives none. |
X-Forwarded-Proto (first value) |
The scheme of the origin the CSRF check expects, and whether the cookies get the Secure flag. |
X-Forwarded-Host (first value) |
The host of the origin the CSRF check expects. |
On a request from any other peer, ALPS ignores these headers. The client's address is then the TCP peer, and the expected origin is the connection's scheme plus the request's Host header.
For POST, PUT, PATCH and DELETE requests, the browser's Origin header must equal the expected origin or an entry of trusted_origins. When the browser sends no Origin, its Referer must start with one of them. Otherwise ALPS answers 403 Invalid origin (or Invalid referer). The log names both origins and often the likely cause. These are the messages of the two log lines (JSON by default, see Config-Logging-and-Cache) for a TLS-terminating proxy at 127.0.0.1 that is not listed:
CSRF: Origin mismatch: got "https://webmail.example.com", expected "http://webmail.example.com" (scheme differs and the peer 127.0.0.1 is not in trusted_proxies; behind a TLS-terminating proxy, forward X-Forwarded-Proto and list the proxy in trusted_proxies, or set trusted_origins)
Request error: code=403, message=Invalid origin
Advice:
- List only proxies you run. This list decides whose word ALPS takes for a client's address, and so keeps the per-address rate limits counting each real client.
- List every proxy hop in front of ALPS (load balancer and proxy), since the first unlisted address from the right is taken for the client.
- Prefer
trusted_proxieswith forwarded headers overtrusted_originsalone. Withtrusted_originsalone, cookies behind a TLS-terminating proxy get noSecureflag, and every user shows up with the proxy's address, so they share one per-address rate limit. - Pass the browser's
Originheader through unchanged. Do not rewrite it in the proxy.
For nginx on the same host, speaking HTTP to ALPS:
[server]
addr = "127.0.0.1:1323"
trusted_proxies = ["127.0.0.1", "::1"]The proxy side, with the headers to set, is on Setup-Behind-a-Reverse-Proxy and in docs/TLS.md. For a proxy that cannot send the forwarded headers:
[server]
trusted_origins = ["https://webmail.example.com"]| Key | Default | Meaning |
|---|---|---|
login_key |
none | A Fernet key: 32 random bytes, written as base64 (standard or URL-safe) or as 64 hex characters. It encrypts the alps_login_token cookie, the stored passwords of linked accounts, and the separate CalDAV and CardDAV passwords users save in Settings. A value that does not decode to 32 bytes stops startup with Error generating options: invalid login key. |
With login_key set:
- Every sign-in also writes the
alps_login_tokencookie. When the in-memory session is gone, after a restart for example, the browser's next request opens a new session from the token, without a new sign-in. - "Stay signed in after closing the browser" keeps the cookies for 30 days. A token is accepted for at most 30 days after it was written.
- Users can link accounts. Without the key, linking is refused.
- Under Settings > Calendar and Settings > Contacts, users can save a separate username and password for a CalDAV or CardDAV server. Without the key, those connections can use only the mail username and password.
Without it, every restart signs everyone out.
Generate a key with:
openssl rand -base64 32[server]
login_key = "paste-the-generated-value-here"- Give every node that serves the same hostname the same key (see Config-Cluster).
- Keep it secret. It protects the passwords inside the login tokens, the linked accounts and the saved DAV connections, so make the configuration file readable only by ALPS's user and by administrators.
- Changing the key signs everyone out, users must remove each linked account and add it again, and users must enter their separate CalDAV and CardDAV passwords again. Until they do, those connections fail. Change it only when that is what you want, for example to make every kept sign-in invalid at once.
Token lifetime, restores and linked accounts are covered on Sessions-and-Sign-in and in docs/SESSIONS.md.
| Key | Default | Meaning |
|---|---|---|
dav_allow_private_servers |
false |
Lets the CalDAV and CardDAV servers that users enter in Settings use plain HTTP and loopback, private and other non-public addresses. |
While the caldav or carddav plugin is loaded, Settings > Calendar and Settings > Contacts let each user enter a server of their own, a personal server, in place of the one this file sets (see Config-Plugins). Any signed-in user can enter one, so by default ALPS reaches personal servers only over HTTPS and only at public addresses. It refuses the same addresses as the remote-content proxy, when it connects and on every redirect, and uses no HTTP proxy for them (The Address Check). A mail account therefore cannot use the setting to reach services on the network ALPS runs in.
Set it to true when your users keep their own DAV servers on a trusted intranet:
[server]
dav_allow_private_servers = truePersonal URLs may then also use http+insecure://, caldav+insecure:// and carddav+insecure://, at any address, and HTTPS_PROXY, HTTP_PROXY and NO_PROXY apply to them. The servers set in [plugin.caldav], [plugin.carddav] and the multi backends are never restricted, whatever this key says. While it is off, a saved connection that needs it stops working, and Settings offers to remove it. In a cluster, give every node the same value. The rules for personal servers are in CDAV.md.
| Key | Default | Meaning |
|---|---|---|
temp_dir |
$TMPDIR, else /tmp
|
The directory for uploaded attachments waiting to be sent or saved in a draft (multipart-* files, mode 0600) and for the BIMI logo cache (alps-avatars). ALPS sets TMPDIR to it for its own process at startup. |
- ALPS does not create the directory at startup. Until it exists, every upload whose files together exceed 32 KiB fails with 500 and the code
upload_storage_failed, and ALPS logsupload multipart: ...at error level with the cause. Smaller uploads stay in memory and still work, so the problem shows only with real attachments. Create the directory beforehand, owned by ALPS's user and closed to others:install -d -o alps -g alps -m 700 /var/lib/alps/tmp
[server] temp_dir = "/var/lib/alps/tmp"
- The shipped systemd unit (
dist/alps.service) setsPrivateTmp=trueandProtectSystem=strict. So a directory under the host's/tmpor/var/tmpdoes not exist for ALPS, and nothing outside them can be written. With that unit, either leavetemp_dirunset, or point it outside/tmpand/var/tmpand list it inReadWritePaths(see docs/OPERATIONS.md). - Leave room above
max_global_attachment_mib. An upload's body is written to the directory before it counts against the attachment limits. - A crash or a kill leaves
multipart-*files behind, and so can a shutdown, which waits up to 5 seconds in all for the sessions' cleanup. A running ALPS sweeps them: once an hour, the first time an hour after startup, it removes everymultipart-*file at the top level of the temp directory that is more than 7 days old. It keeps the files of the uploads it still holds, however old. The sweep cannot tell ALPS's files from another program's files of the same name, which is one more reason to give ALPS a directory of its own. No upload survives a restart, so while ALPS is stopped, everymultipart-*file in its own temp directory is a leftover and can be deleted. Under the shipped systemd unit withtemp_dirunset,PrivateTmp=truehas systemd remove them when the service stops.
| Key | Default | Meaning |
|---|---|---|
session_minutes |
30 |
The server's idle window for an account whose saved settings hold no Auto-logout time. A session that gets no request for this long ends (session ended, reason idle). |
max_session_minutes |
0 (no cap) |
The cap on a user's Auto-logout choice. A longer choice, "Never" included, is lowered to it (1440 caps it at 24 hours). Without a cap, "Never" means one year. 0 or a negative value means no cap. It does not cap session_minutes. |
absolute_session_hours |
168 (7 days) |
The longest a session lives, however much it is used (reason max-age). It is never shorter than session_minutes. 0 keeps the default, and a negative value, such as -1, removes the limit. |
- Every request resets the idle window, including the requests an open page makes by itself, such as its checks for new mail. So while a page is open, the server's idle window does not end its session: the page's own auto-logout (next point) or
absolute_session_hoursdoes. -
session_minutesis also the Auto-logout time of an account that has not picked one. Settings shows it, as an extra choice when it is not one of the listed times (15, 30, 60, 120 or 360 minutes), and an open page signs the user out after that many minutes with no activity in any open tab. Saving other settings leaves it unset, so a later change ofsession_minutesreaches the account. Once the user picks an Auto-logout time, that time replacessession_minutesfor the account, capped bymax_session_minutes. - With
login_keyset, a session that ends at one of these limits is opened again from the login token on the browser's next request. The user signs in again only when the browser no longer holds a valid token: it is more than 30 days old, the browser dropped it on closing (without "Stay signed in after closing the browser"), or the user signed out. The page's own auto-logout counts as signing out. See Sessions-and-Sign-in for how long a token stays valid.
[server]
session_minutes = 30
max_session_minutes = 1440 # "Never" and longer choices become 24 hours
absolute_session_hours = 168How the page's idle timer and the server's window work together is on Sessions-and-Sign-in.
| Key | Default | Meaning |
|---|---|---|
max_sessions |
10000 |
Sessions on one node, all users together. At the cap a sign-in is not refused. The least recently used session is ended instead (reason evicted). 0 keeps the default, and a negative value, such as -1, turns the cap off. |
max_sessions_per_user |
10 |
Sessions of one user on one node. At the cap, that user's least recently used session is ended. 0 keeps the default, and a negative value, such as -1, turns the cap off. |
background_connection |
true |
Each session opens a second mail connection when it first needs one, for work the user is not waiting for: checks for new mail, sender verdicts, the search of Sent for a conversation's replies, draft saves, and the messages a conversation reads ahead. ALPS closes it after a minute without use. false keeps one connection per session. |
- A session holds up to two mail connections, so one user's sessions on a node can hold up to twice
max_sessions_per_user, all from ALPS's address. Mail servers limit the connections of one user from one address (Dovecot:mail_max_userip_connections, 10 by default), so size the two settings together. If the mail server refuses the second connection, the work runs on the session's own connection and only speed suffers, except that withpush_new_mailthat session gets no push. See Mail-Server-Requirements. - With
login_keyset, a browser whose session was ended at a cap opens a new session from its token at its next request, and that can end another session of the same user. Setmax_sessions_per_userabove the number of browsers and devices a user has open at once. - A cap turned off no longer bounds the memory and mail connections the sessions hold. Keep both caps on for a server that users reach over a network, and raise them rather than turn them off.
[server]
max_sessions = 1000
max_sessions_per_user = 5| Key | Default | Meaning |
|---|---|---|
read_timeout_sec |
10 |
The time allowed to read a whole request, headers and body. An attachment upload must arrive within it. |
write_timeout_sec |
60 |
The time from the end of a request's headers to the end of its response. It covers an upload's body, the handling and the response together, and so also downloads and long answers. config.example.toml sets 30. Keep it at least as long as read_timeout_sec. |
idle_timeout_sec |
120 |
How long a keep-alive connection waits for its next request. |
smtp_timeout_sec |
30 |
Bounds opening the connection (the TCP connect, with the TLS handshake for smtps://), then for smtp:// the greeting, EHLO and STARTTLS together, and after that each SMTP command and the wait after the message data. See Config-Providers-and-SMTP. |
imap_timeout_sec |
unset | An older name for [provider] timeout_sec (default 30). It is read only with [provider] type = "imap" and only when timeout_sec is unset or 0. Set [provider] timeout_sec instead. |
- 0 or a negative value keeps each default.
- An upload slower than
read_timeout_secfails with 408 and the codeupload_timed_out, and ALPS logsupload multipart: ...with the cause. For large attachments over slow links, raiseread_timeout_secandwrite_timeout_sec, or put a proxy in front that buffers request bodies. - On HTTP/1.1, a request whose handler is still running when
read_timeout_secpasses is cancelled. Its mail operation then gets up to 10 more seconds before the session's mail connection is let go. If long operations on a slow mail server are cut off, raiseread_timeout_sec. - The event stream (
GET /events) lifts both the read and the write deadline for itself, so these timeouts do not cut it.
For a slow network or large attachments, config.example.toml suggests:
[server]
read_timeout_sec = 30
write_timeout_sec = 120
smtp_timeout_sec = 60
[provider]
timeout_sec = 60| Key | Default | Meaning |
|---|---|---|
max_attachment_mib |
32 |
The upper bound on the uploads one session holds for one composer window. The mail server's own message limits can lower it (see below). Each upload request is also capped at the limit in force plus 1 MiB. |
max_session_attachment_mib |
128 |
The uploads one session holds across all its composer windows. When an upload would go over it, the session's oldest uploads are dropped to make room. |
max_global_attachment_mib |
1024 |
The uploads all sessions on one node hold. When an upload would go over it, the oldest uploads on the node, other users' included, are dropped first. |
The mail server's message limits lower the composer limit for an account when they leave less room than max_attachment_mib:
- ALPS reads the IMAP
APPENDLIMITthe server advertises, for the whole server or in theSTATUSof the Drafts and Sent folders, and the SMTPSIZEthe submission server advertises. It probesSIZEwithout signing in and keeps the answer for 10 minutes in each session. - From the smallest of these limits, it keeps 64 KiB for the headers and the body and allows for base64 encoding. A 25 MiB message limit leaves about 18 MiB for attachments. The composer gets the result, in MiB with a fraction, from
GET /sessionandGET /settings, and checks the message's attachments against it before uploading. - Before it sends, ALPS checks the whole encoded message against the limits it knows, the Sent copy included. A message that is too large, or that the mail server refuses as too large (IMAP
TOOBIG, SMTP 552), is answered 413message_too_large. The composer keeps its contents and asks the user to shorten the message or remove attachments. - A server that advertises
APPENDLIMIT=0leaves no room for attachments, and sends are refused before they reach SMTP, so that no message is sent without its Sent copy.
So max_attachment_mib above the mail server's limits has no effect. Without advertised limits, it is the limit.
What an upload (POST /attachments) gets:
- 413 when the request body is over the composer limit in force plus 1 MiB. ALPS stops reading the body there.
- 400
Your attachments exceed the maximum file size. Remove some and try again.when the upload would take its composer window over the composer limit, or when the files of one request together exceed any of the three limits. - 408
upload_timed_outwhen the body does not arrive withinread_timeout_sec, and 500upload_storage_failedwhen it cannot be written to the temp directory (see temp_dir). - 200 otherwise, with older uploads dropped first when the session or node limit requires it. A composer that lost an upload this way gets 409
missing_attachmentsat its next save or send, and asks the user to add those files again.
0 or a negative value keeps the default, so none of the limits can be turned off. Uploads live in the memory and temp directory of the node that received them, and a restart loses them. Set the reverse proxy's request-body limit to at least max_attachment_mib + 1 MiB, and no less than 32 MiB, the largest message body ALPS accepts. nginx's default client_max_body_size 1m refuses every upload over 1 MiB. Details are in docs/ATTACHMENTS.md.
[server]
max_attachment_mib = 25
max_session_attachment_mib = 100
max_global_attachment_mib = 4096| Key | Default | Meaning |
|---|---|---|
push_new_mail |
false |
While a mail page is open, its session waits in IMAP IDLE on its background connection, and tells the page over an event stream (GET /events) as soon as a message arrives in the Inbox or leaves it. Without push, pages check only at the user's check-mail interval. The checks keep running with push on too, for flags and the other folders. |
push_subscribed_folders |
false |
With push_new_mail on and a mail server with NOTIFY (RFC 5465), the session also asks for the news of the subscribed folders, on the same connection, and pushes it as it pushes the Inbox's. The pages' own checks then run a third as often (every 15 minutes at the default 5). Without NOTIFY, or where the server refuses it, the Inbox alone is pushed. |
Push needs background_connection on, a mail server with IDLE, a browser in a secure context (HTTPS, or localhost), a reverse proxy that does not buffer /events and keeps it open, and, in a cluster, routing to the node that holds the session. Features explains each requirement and how to check that push works. When a key it depends on is off, ALPS says so at startup:
-
push_new_mailwithoutbackground_connectionlogspush_new_mail needs background_connection, which is off: new mail is not pushed, andGET /eventsanswers 404new mail is not pushed. -
push_subscribed_folderswithoutpush_new_maillogspush_subscribed_folders needs push_new_mail, which is off: no news of the folders is pushed.
What it costs: every session with a mail page open holds two mail connections (its own and the background one in IDLE) for as long as the page is open, all from ALPS's one address. Check the mail server's per-user and per-address connection limits before turning it on. With push_subscribed_folders, the mail server also watches every subscribed folder of every open page. docs/CONFIGURATION.md has the numbers.
[server]
push_new_mail = true
push_subscribed_folders = true| Key | Default | Meaning |
|---|---|---|
condstore |
false |
Each mail connection turns on IMAP CONDSTORE (RFC 7162) where the server lists it. When a folder still holds the messages a cached page was read with, the next listing reads again only the flags that changed (one FETCH with CHANGEDSINCE), so a star or a read made in another client shows at the next check. |
- A server that does not list CONDSTORE is used as with
condstore = false. ALPS never turns on QRESYNC. - The cost is one round trip as each connection signs in.
- With it on,
GET /mailboxes/{mbox}/statusalso answershighestmodseq, as a decimal string, for example"highestmodseq":"2". - Without it, a flag change made in another client that leaves the unread count as it was, such as a star, shows only when the cached page expires (
[cache] ttl_minutes, see Config-Logging-and-Cache).
[server]
condstore = truedocs/CONFIGURATION.md describes each step.
One node behind nginx on the same host, which terminates TLS:
[server]
addr = "127.0.0.1:1323"
trusted_proxies = ["127.0.0.1", "::1"]
login_key = "paste-the-output-of-openssl-rand-base64-32"
temp_dir = "/var/lib/alps/tmp"
max_session_minutes = 1440
absolute_session_hours = 168
max_sessions = 10000
max_sessions_per_user = 10
read_timeout_sec = 30
write_timeout_sec = 120
max_attachment_mib = 32
push_new_mail = true
condstore = trueWhat to check after a change:
- The log shows
Starting server on 127.0.0.1:1323, and noUnknown configuration keywarning. A value of the wrong type, an invalidtrusted_proxiesentry or an invalidlogin_keyexits with status 1, with a line on standard error that starts withError loading config file:orError generating options:. See docs/OPERATIONS.md for the other startup failures. - Through the proxy, the sign-in's
Set-Cookieheaders carrySecure. - The access log's
ipis the browser's address, not the proxy's. - An attachment of a few MiB uploads. If it fails, check the proxy's body limit and that
temp_direxists.
More for production is on Running-in-Production.
- Configuration-Overview: every section of the file
- Sessions-and-Sign-in: how sessions, the login token and linked accounts behave
-
Setup-Behind-a-Reverse-Proxy: the proxy side of
trusted_proxies, uploads and/events -
Config-Rate-Limiting-and-WebAuthn:
[server.rate_limit] -
Config-Providers-and-SMTP:
[provider] timeout_secand[smtp] -
Config-Logging-and-Cache:
[logging]and[cache] - Mail-Server-Requirements: IDLE, NOTIFY, CONDSTORE and connection limits
These pages describe ALPS as it is on the main branch; the reference documents live in the repository's docs/ directory.
Architecture
- Architecture Overview
- Mail Server Requirements
- Sessions and Sign-in
- Storage in IMAP METADATA
- Mail Providers
- Plugins
- Features
- Security Features
- Clustering
Tests
Configuration
- Configuration Overview
- Server
- Providers and SMTP
- TLS
- Cluster
- Rate Limiting and WebAuthn
- Logging and Cache
- Plugins
- Branding
Common Setups