Skip to content

Configuration Overview

Dejan Strbac edited this page Nov 12, 2026 · 1 revision

This page covers the ALPS configuration file as a whole: how ALPS is started with it, the smallest file that works, how defaults and startup checks behave, which sections exist and which page covers each, the environment variables ALPS reads, and a checklist for production. Every key, with its type and default, is listed in docs/CONFIGURATION.md.

The configuration file

ALPS reads one TOML file, named on the command line with -config. There is no default path, and no environment variable names the file: without -config, ALPS prints its usage and exits. A relative path is relative to the working directory of the process.

The repository ships a commented example, config.example.toml, which gives each key's default. The usual start is to copy it to config.toml and edit it. Three things in the example need attention before you use it:

  • It points [provider.imap] server and [smtp] server at Migadu's servers. Point them at your own.
  • It sets enabled = true for the caldav, carddav and managesieve plugins and leaves their server lines commented out. CalDAV and CardDAV then load without a default server, so users get Calendar, Tasks and Contacts only once they enter servers of their own in Settings. ManageSieve stays off, and startup warns that it did not load. Set a server for each service you run, and enabled = false for the others (see Config-Plugins).
  • Its [logging] section writes console text to stderr (output = "stderr", format = "console"). Set format = "json" if your log collector reads JSON lines.

The shipped service files read /etc/alps/config.toml (systemd, dist/alps.service) and /usr/local/etc/alps/config.toml (FreeBSD rc.d, dist/alps). See Running-in-Production and docs/OPERATIONS.md.

ALPS reads the file once, at startup, so restart ALPS to apply a change. SIGHUP (systemctl reload alps, service alps reload) only reopens the log file when [logging] output is a file path. It does not read the configuration again, as the comments in both shipped service files say.

Command-line flags

Flag Effect
-config <path> The configuration file. Required. --config also works.
-version Prints alps <version> (<commit>) built at <date> and exits with status 0.
-h Prints the usage and exits with status 0.

make build writes the version, commit and build date into the binary. A plain go build, go install or go run does not, and -version then prints alps unknown (unknown) built at unknown (see docs/OPERATIONS.md).

ALPS exits with status 1 when -config is missing, when the file cannot be read, parsed or validated, or when a startup step fails. An unknown flag exits with status 2. The full table is in docs/OPERATIONS.md.

A minimal working file

ALPS needs a mail store and an SMTP submission server. [provider] type defaults to "imap", so this file is enough:

[provider.imap]
server = "imaps://imap.example.com:993"

[smtp]
server = "smtps://smtp.example.com:465"

Both URLs need a scheme. For IMAP, use imaps:// (TLS, default port 993) or imap:// (STARTTLS required, default port 143). For SMTP, use smtps:// (TLS, default port 465) or smtp:// (STARTTLS required, default port 587). ALPS always verifies the IMAP and SMTP servers' certificates. What the mail server has to support, METADATA above all, is on Mail-Server-Requirements.

Start ALPS with the file:

alps -config config.toml
# or, from a checkout of the repository:
go run ./cmd/alps -config config.toml

With this file, startup logs these lines (time fields left out):

{"level":"INFO","msg":"Configured SMTP server: smtps://smtp.example.com:465"}
{"level":"INFO","msg":"Rate limiting enabled: IP=5/min, 20/hour | User=5/15min, 10/hour | Global=100/sec"}
{"level":"INFO","msg":"Loaded plugin \"gpg\""}
{"level":"INFO","msg":"Loaded plugin \"base\""}
{"level":"WARN","msg":"WebAuthn: [webauthn] rpid is unset, defaulting to \"localhost\" — 2FA will not work unless served from localhost; set rpid and origins to your public hostname"}
{"level":"INFO","msg":"Starting server on :1323"}

Check that ALPS answers, then open http://localhost:1323 and sign in with the address and password of a mail account:

curl -sI http://localhost:1323/ | head -1
# HTTP/1.1 200 OK

A successful sign-in is logged as "msg":"sign-in" with the user and client address. A sign-in that fails because the mail server could not be reached is logged as "msg":"sign-in failed" with "reason":"error" and the cause, for example x509: certificate signed by unknown authority for a server whose CA the host does not trust (see SSL_CERT_FILE under Environment variables).

What this file gives you:

  • ALPS listens on port 1323 on every interface, over plain HTTP. It logs JSON lines at info level to stdout.
  • The base (mail) and gpg plugins load. There is no Password section in Settings, since password change needs [plugin.password] enabled = true and an endpoint or command. There are no contacts, calendars or filters, since the file has no [plugin.carddav], [plugin.caldav] or [plugin.managesieve] section.
  • Sign-in rate limits are on, at their defaults.
  • Security keys work only at the default origins, http://localhost:1323 and the Vite dev server's http://localhost:5173, as the warning says.
  • There is no login_key, so sessions do not survive a restart of ALPS, and linking accounts is unavailable.

That is enough to try ALPS out on one machine (Setup-Local-Try-Out). For anything users reach over a network, work through the production checklist.

Sections

Only the provider section and [smtp] server are required. Every other section is optional.

Section What it sets Page
[server] Listen address, debug mode, trusted proxies and origins, login_key, the users' own CalDAV and CardDAV servers, temp directory, session lifetimes and caps, HTTP, IMAP and SMTP timeouts, attachment limits, the background connection, new-mail push, CONDSTORE Config-Server
[server.rate_limit] Limits on sign-ins and other password and recovery-code checks Config-Rate-Limiting-and-WebAuthn
[webauthn] Hostname, origins and display name for security keys Config-Rate-Limiting-and-WebAuthn
[provider], [provider.imap], [provider.maildir], [provider.multi] The mail store: one IMAP server, a local Maildir, or routing by domain Config-Providers-and-SMTP
[smtp] The submission server Config-Providers-and-SMTP
[tls], [tls.letsencrypt], [tls.letsencrypt.s3] TLS in ALPS itself: certificate files, or Let's Encrypt with file or S3 storage Config-TLS
[cluster] Gossip between several ALPS nodes Config-Cluster
[logging] Log output, format, level and the per-request lines Config-Logging-and-Cache
[cache] The per-session cache of listings and message parts Config-Logging-and-Cache
[plugin.<name>] Which of base, caldav, carddav, managesieve, gpg and password load, the CalDAV, CardDAV and ManageSieve servers, the calendar's first day of the week, and the password plugin's options Config-Plugins
[branding] The default colour theme and the logo Config-Branding

The provider section must match [provider] type: [provider.imap] for "imap", [provider.maildir] for "maildir", [provider.multi] for "multi". ALPS looks for an exact match first, then for one that differs only in case.

How defaults work

A key left out takes the default listed in docs/CONFIGURATION.md. config.example.toml sets a few keys to other values than the defaults. For example, it sets write_timeout_sec = 30 where the default is 60, and the [logging] values above.

  • Numbers. For most integer keys, 0 or a negative value means the default, so those limits cannot be turned off. This covers session_minutes, the attachment limits, the HTTP, IMAP and SMTP timeouts, every limit in [server.rate_limit], [cache] ttl_minutes and max_part_kib, and [tls.letsencrypt] sync_interval_minutes. Four keys work differently: max_session_minutes = 0 means no cap, a negative absolute_session_hours removes the absolute session limit, and a negative max_sessions or max_sessions_per_user (such as -1) turns that session cap off, while 0 keeps its default.
  • Switches that are on by default. [server] background_connection, [cache] enabled, [logging] access and [server.rate_limit] enabled stay on unless the file says false. Leaving a key out never turns one of them off.
  • [webauthn]. Each key falls back to its own default, so setting rpid without origins keeps the localhost origins.
  • [logging]. Each key has its own default, whichever of the others are set: output is stdout, format is JSON (console with [server] debug = true), and level is info (debug with debug = true).

Some values are not checked and fall back without a message: an unknown level means info, and any format other than "json" gives console text. See Config-Logging-and-Cache.

What ALPS checks at startup

ALPS has no mode that only checks the file. It checks the file when it starts, and stops with exit status 1 at the first problem. Errors found while the file is read and checked, before the log is opened, are written to stderr as plain text. Errors found in the later startup steps (the cluster, the SMTP server URL, the plugins, WebAuthn, TLS, the listener) go to the log, which is stdout unless [logging] output says otherwise. Under systemd, both end up in the journal (journalctl -u alps).

Message Cause
Error: -config flag is required -config is missing.
Error loading config file: failed to read config file: open ...: no such file or directory The path is wrong, or the file cannot be read.
Error loading config file: failed to parse TOML config: toml: line <n> ... A TOML syntax error, or a value of the wrong type, such as addr = 1323 instead of addr = ":1323".
Error loading config file: provider.imap: missing TOML section [provider.imap] No section matches [provider] type.
Error generating options: unknown provider type 'pop3' [provider] type is not imap, maildir or multi, and a section of that name exists. Without the section, the message is the one above.
Error generating options: IMAP server requires a scheme (imaps://, imap://, imap+insecure://), got: ... [provider.imap] server has no scheme, or is missing (got empty string).
Error generating options: no SMTP server specified in config file ([smtp] server) [smtp] server is missing. It is required with every provider.
Error generating options: invalid login key login_key is not 32 bytes in base64 or hex.
Error generating options: invalid trusted_proxies IP/CIDR ... An entry of trusted_proxies is not an address or CIDR block.
Error generating options: branding.default_theme: unknown theme ... [branding] default_theme is not alps, migadu, nord or ocean.
Error generating options: cluster.bind: must be an IP address with an optional port, got ... With [cluster] enabled = true, bind is a host name or an interface name (see Config-Cluster).
Error generating options: multi: ... A mistake in [provider.multi], such as a template without its allowlist or a backend endpoint without a scheme (see Config-Providers-and-SMTP).
SMTP server requires a scheme (smtps://, smtp://, smtp+insecure://), got: smtp.example.com:465 (in the log) [smtp] server has no scheme, for example host:port. Write smtps://smtp.example.com:465.
failed to load plugins: CardDAV server requires a scheme ... (in the log) A [plugin.carddav] server without a scheme. ALPS reads a plugin's server only while enabled lets the plugin load, so with enabled = false the value is not checked.
failed to initialize cluster ... or failed to decode cluster secret key: ... (in the log) The cluster could not start, most often because [cluster] enabled = true has no valid secret_key (see Config-Cluster).
Failed to build TLS config: unsupported tls provider: (in the log) [tls] enabled = true without provider = "file" or "letsencrypt" (see Config-TLS).
listen tcp :1323: bind: address already in use (in the log) Another process listens on [server] addr.

Each section's page lists that section's own checks.

Some settings do not stop ALPS and are only logged at startup:

  • at warn level: each unknown key (see Misspelt keys), an unset [webauthn] rpid (shown above), an entry of [webauthn] origins whose host name is not rpid or under it (WebAuthn: origin "http://localhost:1323" is outside rpid "webmail.example.com"; ...), and a name in the plugins' enabled list that did not load (Enabled plugin "<name>" did not load; check its name and backend configuration);
  • at info level: push_new_mail with background_connection = false (push_new_mail needs background_connection, which is off: new mail is not pushed), push_subscribed_folders without push_new_mail, a ManageSieve plugin with nothing to connect to (ManageSieve enabled without a server or routed endpoint; plugin disabled), and a password plugin with no way to change a password (password: enabled without an endpoint, command or routed backend; plugin disabled).

Misspelt keys

ALPS ignores keys and sections it does not know, so a misspelt key keeps its default. It logs a warning at startup for each one, once the log is open, naming the key but not its value. For example, write_timeout = 5 leaves write_timeout_sec at 60 and logs:

{"level":"WARN","msg":"Unknown configuration key: server.write_timeout (ignored)"}

A [serverr] section is skipped entirely, and ALPS names the section and each key in it. The keys of a [provider.<type>] section for another type than [provider] type selects are reported too. Two kinds of table are free-form and not checked: a plugin's options, and under [provider] type = "multi" the password table of a backend. Look for Unknown configuration key after each change.

Plugin names are not checked this way, since every [plugin.<name>] section is valid TOML for ALPS. One [plugin.<name>] enabled = true anywhere, even under a misspelt name such as [plugin.carddv], means only base and the plugins whose own sections say enabled = true load. ALPS then warns Enabled plugin "carddv" did not load; check its name and backend configuration, and starts without the other plugins. base, with sign-in and mail, always loads. After each start, check the Loaded plugin lines for every plugin you expect (Config-Plugins).

Environment variables

Everything else is set in the file. ALPS also reads these variables (docs/CONFIGURATION.md):

Variable Effect
CLUSTER_SECRET_KEY When set, used in place of [cluster] secret_key. This keeps the gossip key out of the file.
TMPDIR The temp directory when [server] temp_dir is unset. temp_dir sets TMPDIR for the whole ALPS process.
SSL_CERT_FILE, SSL_CERT_DIR On Linux and the BSDs, where Go reads the trusted CA certificates. They apply to ALPS's TLS connections to the IMAP, SMTP, ManageSieve, CalDAV and CardDAV servers. A mail server with a private CA needs that CA here or in the host's trust store.
HTTPS_PROXY, HTTP_PROXY, NO_PROXY Go's proxy settings, for the HTTP requests ALPS makes to the CalDAV and CardDAV servers, the password plugin's endpoint, Let's Encrypt and S3. IMAP, SMTP and ManageSieve connect directly, and so do ALPS's fetches of remote message content and BIMI logos, and its requests to the CalDAV and CardDAV servers users enter in Settings while [server] dav_allow_private_servers is off.
The AWS SDK's own variables Credentials and region for the [tls.letsencrypt.s3] bucket, where the file does not set them.

The password plugin's command runs with PATH as its only environment variable.

Production checklist

Each item links to the page with the details. Running-in-Production covers the service files, logs and restarts.

  1. Keep debug off. Leave [server] debug and [provider.imap] debug at false. The protocol traces they turn on go to the log at debug level, and they include message, contact and calendar content (Config-Server, Config-Logging-and-Cache).
  2. Use your own mail servers, with TLS. Use imaps:// or imap://, and smtps:// or smtp://. The +insecure schemes and insecure = true send passwords in clear text (Config-Providers-and-SMTP). Use a mail server with METADATA, or settings, security keys and keyrings are not kept (Mail-Server-Requirements).
  3. Choose where TLS ends. Behind a reverse proxy, leave [tls] off, bind addr = "127.0.0.1:1323" when the proxy runs on the same host, and have the proxy send X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-For (Setup-Behind-a-Reverse-Proxy). Without a proxy, enable [tls] (Config-TLS, Setup-Lets-Encrypt-Standalone).
  4. List only your own proxies in trusted_proxies. List every proxy hop in front of ALPS, and nothing else. ALPS takes the client address for rate limits and logs, the scheme and host for the origin check, and the scheme for Secure cookies from these peers only (Config-Server).
  5. Set [webauthn] rpid and origins to the public hostname and its origin, for example "webmail.example.com" and ["https://webmail.example.com"]. Neither can be taken from a proxy's headers. The startup warning goes away once rpid is set (Config-Rate-Limiting-and-WebAuthn).
  6. Set login_key and keep it secret. With it, sessions survive a restart, "Stay signed in after closing the browser" keeps a sign-in for 30 days, users can link accounts, and users can save separate CalDAV and CardDAV passwords. Generate one with openssl rand -base64 32. Every node serving the same hostname needs the same key. Changing it signs everyone out, linked accounts must be linked again, and separate DAV passwords entered again (Config-Server, Sessions-and-Sign-in, docs/SESSIONS.md).
  7. Set [provider.imap] authserv_ids to the authserv-id your receiving server writes in Authentication-Results. Sender badges and BIMI logos then follow your server's verdict (Config-Providers-and-SMTP).
  8. Leave rate limiting on. [server.rate_limit] enabled = false turns every tier off. With several nodes, enable [cluster] so the nodes share their counts (Config-Rate-Limiting-and-WebAuthn).
  9. Choose the log format and destination. The example's console text suits a terminal, and format = "json" suits a log collector. Give a log file an absolute path, since the shipped service files run ALPS in / (Config-Logging-and-Cache).
  10. Check the temp directory. Under the shipped systemd unit, PrivateTmp gives ALPS its own /tmp. Leave temp_dir unset there, or point it outside /tmp and /var/tmp and list it in ReadWritePaths. A temp_dir must exist before ALPS starts and be writable only by the user ALPS runs as (docs/OPERATIONS.md).
  11. Set up the plugins you use. Give caldav, carddav and managesieve a server, or enabled = false. enabled = false keeps a plugin off. Once any section sets enabled = true, only the plugins whose sections set it load, so then give every plugin you want enabled = true. Leave [server] dav_allow_private_servers off unless your users keep their own DAV servers on a trusted intranet. Check the Loaded plugin lines and the warnings after a restart (Config-Plugins, Config-Server).
  12. For several nodes, enable [cluster] with a secret_key (or CLUSTER_SECRET_KEY), a stable and unique node_id, and the same login_key on every node. With Let's Encrypt, the certificates must be stored in S3 (Config-Cluster, Setup-Cluster).
  13. Protect the file. It holds login_key, and possibly S3 keys and the password plugin's credentials. Make it readable by the user ALPS runs as and by no other users.
  14. Restart and read the startup lines after every change: no errors, no Unknown configuration key or other warnings, the expected Loaded plugin lines, and Starting server on <addr>.

A file for one node behind a reverse proxy on the same host, before plugins and TLS:

[server]
addr = "127.0.0.1:1323"
trusted_proxies = ["127.0.0.1/32", "::1/128"]
login_key = "<output of openssl rand -base64 32>"

[webauthn]
rpid = "webmail.example.com"
display_name = "Example Webmail"
origins = ["https://webmail.example.com"]

[logging]
format = "json"

[provider.imap]
server = "imaps://imap.example.com:993"
authserv_ids = ["mx.example.com"]

[smtp]
server = "smtps://smtp.example.com:465"

It starts with no warning and logs JSON lines to stdout, the default output.

See also

Clone this wiki locally