Repository navigation
Running in Production
This page covers running ALPS as a service: the systemd unit and the FreeBSD rc.d script shipped in dist/, the log and its rotation, the signals ALPS handles, what a restart or an upgrade costs users, what to back up, which connections the host must allow, and a checklist. The systemd setup below was built and checked on Debian 13. The reference is docs/OPERATIONS.md.
- ALPS at
/usr/local/bin/alps, started by systemd as useralpswith/etc/alps/config.toml, listening on127.0.0.1:1323behind a reverse proxy that serveshttps://webmail.example.com. - The log in
/var/log/alps/alps.log, one JSON object per line, rotated by logrotate, which tells ALPS to reopen the file. - A drop-in for the shipped unit that lets ALPS write its log.
- Browsers that stay signed in across restarts and upgrades, through the login token that
login_keyenables. - Uploaded attachments in a
/tmpprivate to the service, which systemd deletes at every stop.
ALPS keeps no database. The mail, the settings and the security keys live on the mail server. The host holds the binary, the configuration and the log.
- A Linux host with systemd. For FreeBSD, see FreeBSD below.
- An ALPS binary.
make build-linuxwrites a static amd64 binary tobuild/alps-linux-amd64, andmake buildwritesbuild/alpsfor the build host (README). Both embed the web app, so nothing else needs to be installed. - A mail server that meets Mail-Server-Requirements. The example uses IMAPS on
imap.example.com(port 993), SMTPS onsmtp.example.com(port 465) and ManageSieve onimap.example.com(port 4190). - A reverse proxy that terminates TLS for
webmail.example.comand forwards to127.0.0.1:1323(Setup-Behind-a-Reverse-Proxy). To have ALPS serve HTTPS itself, see Setup-Lets-Encrypt-Standalone. The unit allows ALPS to listen on ports 443 and 80. - A
login_key, generated once and kept secret:openssl rand -base64 32
[server]
addr = "127.0.0.1:1323"
trusted_proxies = ["127.0.0.1/32", "::1/128"]
login_key = "paste-the-generated-value-here"
# temp_dir is left unset: the unit's PrivateTmp gives ALPS a /tmp of its own.
[logging]
output = "/var/log/alps/alps.log"
format = "json"
level = "info"
[webauthn]
rpid = "webmail.example.com"
origins = ["https://webmail.example.com"]
[provider]
type = "imap"
[provider.imap]
server = "imaps://imap.example.com"
authserv_ids = ["mx.example.com"]
[smtp]
server = "smtps://smtp.example.com"
[plugin.base]
enabled = true
[plugin.managesieve]
enabled = true
server = "managesieve://imap.example.com"-
addron the loopback address means only the proxy on this host reaches ALPS.trusted_proxieslists that proxy and nothing else, so ALPS takes the client's address fromX-Forwarded-Foronly when the request comes from it (Config-Server). -
login_keylets a browser come back after a restart without a new sign-in. The file holds it, so it is installed readable by root and thealpsgroup only (see Start it). -
temp_diris not set. Under the shipped unit,/tmpis private to ALPS (see Variations for a directory of its own). -
[logging]writes to a file. Leave the section out to log to the journal instead (see Variations). -
[webauthn]names the public hostname, so security keys work. Without it ALPS logs a warning at startup and keys work only fromlocalhost. -
authserv_idsnames the receiving mail server whoseAuthentication-Resultsfield ALPS trusts for sender verdicts and BIMI logos. Use the authserv-id your MX writes. -
[server] debugis not set, so it stays off. Keep it off in production: it turns on traces of the IMAP, CalDAV, CardDAV and ManageSieve exchanges, message content included. They are logged asprotocol tracelines at debug level, so this file'slevel = "info"leaves them out, butlevel = "debug"would write them to this log. - ALPS ignores a key it does not know, keeps its default, and warns at startup, such as
Unknown configuration key: server.trusted_proxy (ignored). Check the log after each change, and names against config.example.toml.
CalDAV, CardDAV, OpenPGP and password change are left out here; Config-Plugins covers them. Every key is on the Configuration-Overview pages.
The shipped unit runs with ProtectSystem=strict, so ALPS cannot write outside its private /tmp and /var/tmp. Run systemctl edit alps and enter:
[Service]
ReadWritePaths=/var/log/alpssystemd saves it as /etc/systemd/system/alps.service.d/override.conf. systemctl edit needs a terminal; from a script, write the same lines to that file and run systemctl daemon-reload.
ReadWritePaths=/var/log/alps lets ALPS open its log file. Without it, ALPS logs to standard error, which goes to the journal (see Troubleshooting). A reload during startup, from a log rotation for example, needs nothing in the drop-in: ALPS handles SIGHUP from the moment its log is open (see Signals and stopping).
The shipped unit's own TimeoutStopSec=60 covers ALPS's longest shutdown, 55 seconds with Let's Encrypt on S3 and [cluster] (see Signals and stopping). A unit copied from an earlier release has TimeoutStopSec=30, which systemd can reach while ALPS still waits for a request in progress. Copy the current dist/alps.service, or add TimeoutStopSec=60 to the drop-in.
/var/log/alps/alps.log {
weekly
rotate 8
compress
delaycompress
missingok
notifempty
create 0640 alps alps
postrotate
systemctl reload alps.service > /dev/null 2>&1 || true
endscript
}
systemctl reload alps sends SIGHUP, and ALPS opens the log path again, so it goes on writing to the new file. On Debian, logrotate.timer runs logrotate daily.
-
Journal only. Leave out the whole
[logging]section, or itsoutputline. ALPS then writes to standard output, as JSON lines at info level unlessformatorlevelsay otherwise, and the journal keeps them:journalctl -u alps. DropReadWritePaths=/var/log/alpsand the logrotate file. -
A temp directory of its own, on disk rather than in the host's
/tmp. Create it, set it, and make it writable:install -d -o alps -g alps -m 700 /var/lib/alps /var/lib/alps/tmp
[server] temp_dir = "/var/lib/alps/tmp"
Leave room there above[Service] ReadWritePaths=/var/lib/alps /var/log/alps
max_global_attachment_mib(default 1024 MiB). Upload files left behind by a stop or a crash then stay until ALPS's hourly sweep removes them, 7 days later (see Troubleshooting). -
Let's Encrypt. Set an absolute
[tls.letsencrypt] cache_dir, such as/var/lib/alps/cert-cache, and list/var/lib/alpsinReadWritePaths. The unit sets no working directory, so the defaultcache_dir = "cert-cache"would resolve to/cert-cache, which cannot be written. See Setup-Lets-Encrypt-Standalone. -
Maildir provider. List the maildirs in
ReadWritePaths. For maildirs under/home, also setProtectHome=false, since the unit hides/home(Setup-Maildir).
As root, from a checkout with the built binary, with the files above saved there as config.toml and alps.logrotate:
useradd --system --home-dir /nonexistent --no-create-home --shell /usr/sbin/nologin alps
install -m 755 build/alps-linux-amd64 /usr/local/bin/alps
install -d -m 750 -o root -g alps /etc/alps
install -m 640 -o root -g alps config.toml /etc/alps/config.toml
install -d -m 750 -o alps -g alps /var/log/alps
cp dist/alps.service /etc/systemd/system/alps.service
systemctl daemon-reload
systemctl edit alps # the drop-in above
cp alps.logrotate /etc/logrotate.d/alps
systemd-analyze verify alps.service # prints nothing when the unit is valid
systemctl enable alps
systemctl start alpssystemctl start returns as soon as ALPS has been started, even when ALPS then exits with a configuration error, so check the result as shown below.
systemctl is-active alps
grep 'Starting server on' /var/log/alps/alps.log
grep -c 'rpid is unset' /var/log/alps/alps.log
journalctl -u alps -n 20 -o cat
curl -sI http://127.0.0.1:1323/ | grep -iE '^HTTP|x-alps-build'-
systemctl is-activeprintsactive.activatingmeans ALPS keeps exiting and being started again (see Troubleshooting). - The log has
{"time":"…","level":"INFO","msg":"Starting server on 127.0.0.1:1323"}, and the count ofrpid is unsetwarnings is0. - Once the log file is open, the journal holds only systemd's own lines and any error ALPS met before it opened the log (see Logs and rotation).
-
curlprintsHTTP/1.1 200 OKand anX-Alps-Buildheader. ALPS has no separate health endpoint.GET /needs no session, so it serves as a liveness probe, but it says nothing about the mail server (TLS.md).
Then sign in at https://webmail.example.com and check:
- The log has a
sign-inline with the browser's address, not the proxy's:{"time":"…","level":"INFO","msg":"sign-in","user":"user@example.com","ip":"203.0.113.10","session":"f707b6116cda","2fa":false,"remember":false,"restored":false,"agent":"…"} -
ss -tnp | grep alpslists the connections from ALPS to the IMAP server's port 993: one or two for each session. -
systemctl reload alpsadds{"…","msg":"SIGHUP: log file reopened"}to the log. -
logrotate -f /etc/logrotate.d/alpsleavesalps.log.1beside a newalps.logthat starts with that sameSIGHUP: log file reopenedline. -
grep 'open files' /proc/$(systemctl show alps -p MainPID --value)/limitsshows65536, the unit'sLimitNOFILE. Each session usually holds one or two mail connections.
ALPS writes its log where [logging] output says: stdout, stderr, syslog or a file path. Without output, it writes to standard output, as JSON unless format says otherwise. Under the systemd unit, standard output and standard error go to the journal. Config-Logging-and-Cache covers the format, the level and the per-request lines (access).
The cluster's and the certificate manager's lines, the IMAP provider's warnings, such as the one for a server without METADATA, and the protocol traces of [server] debug follow [logging] like every other line. Only the errors found before the log is opened do not, such as Error loading config file: …: they go to standard error, so under the unit they are in the journal even when the log is a file. So check journalctl -u alps too when ALPS does not start.
ALPS opens a log file for appending and creates it with mode 0644 if it is missing, but it does not create its directory. It never rotates or truncates the file. A rotation tool moves the file aside and then sends SIGHUP, and ALPS opens the same path again. If that fails, ALPS logs SIGHUP: reopen log file … with the reason and keeps writing to the moved file.
The log holds user names, client addresses and browser user agents, and with debug on at level debug, message content. Keep it in a directory that only the alps user and administrators can read, as the 750 directory above does. When the reverse proxy keeps its own access log, [logging] access = false drops ALPS's per-request lines; sign-ins and session ends are still logged.
| Signal | Sent by | What ALPS does |
|---|---|---|
| SIGHUP |
systemctl reload alps, service alps reload (see FreeBSD), the logrotate script above |
Opens the [logging] file path again and logs SIGHUP: log file reopened. With no log file, it logs SIGHUP: no log file to reopen. Nothing else: it does not reload the configuration or the TLS certificate files. |
| SIGTERM |
systemctl stop alps, systemctl restart alps, service alps stop (see FreeBSD) |
Shuts down gracefully. |
| SIGINT | Ctrl-C when ALPS runs in a terminal | The same as SIGTERM. |
To apply a change to config.toml or a renewed certificate file, restart ALPS: systemctl restart alps. Reload only reopens the log file, as the comments in both service files say.
A graceful shutdown goes in this order (OPERATIONS.md):
- The listener closes, and with Let's Encrypt the ACME HTTP-01 listener too, so new connections are refused. Idle connections close, and open new-mail event streams (
/events) are ended. Requests in progress get up to 30 seconds; one still running then is cut off, and ALPS logsHTTP server shutdown: context deadline exceeded. - Every session is told to close. Each releases its mail connections and its upload files, and ALPS waits up to 5 seconds in all for that. When the cleanup takes longer, it logs
Session shutdown timed out waiting for cleanupand goes on. - The rate limiter and the hourly jobs stop, and the plugins are closed.
- With Let's Encrypt, the certificate workers stop: up to 5 seconds, or 15 with S3 storage.
- With
[cluster], the node announces that it is leaving, waiting up to 5 seconds. - ALPS exits with status 0.
A shutdown therefore takes up to 35 seconds without Let's Encrypt and [cluster]. Let's Encrypt adds up to 5 seconds, or 15 with S3 storage, and [cluster] up to 5, so with both a shutdown takes up to 55 seconds.
What the lab measured:
| Situation | Time to exit |
|---|---|
| Nothing in progress | 0.06 s |
An open /events stream |
0.06 s |
An upload still arriving, default read_timeout_sec = 10
|
9.2 s: the upload was cut off 10 s after it began, by its read timeout |
A request still running, read_timeout_sec = 60
|
30.05 s on the host, 30.02 to 30.03 s under systemd |
ALPS writes no log line of its own when a stop goes within these limits. The journal shows Stopping alps.service, then Deactivated successfully. Once a shutdown has begun, a second SIGTERM is ignored, and a SIGHUP still only reopens the log. SIGKILL ends it sooner, and systemd sends that when TimeoutStopSec runs out.
ALPS handles SIGHUP from the moment its log is open, before the slower startup steps: joining the cluster, the startup OPTIONS check of each CalDAV and CardDAV server, which can take up to 10 seconds each, and the S3 check for Let's Encrypt, which can take up to 30. A log rotation during startup therefore only reopens the log. SIGINT and SIGTERM end ALPS at once until its listener has started; startup normally takes milliseconds.
If ALPS is killed with SIGKILL or crashes, systemd starts it again after 5 seconds (Restart=on-failure, RestartSec=5). Requests in progress fail, a cluster node leaves without announcing it, and upload files stay in the temp directory until the hourly sweep removes them, or, with the unit's private /tmp, until systemd deletes it.
ALPS keeps sessions and everything tied to them in memory. A restart, an upgrade or a crash loses (OPERATIONS.md):
-
Every session. A browser comes back without a password only when
login_keyis set and unchanged and the browser still has its login token, at most 30 days old. ALPS then opens a new session on the browser's next request and logssession restored. Withoutlogin_key, everyone signs in again. A changedlogin_keysigns everyone out, linked accounts must be linked again, and separate CalDAV and CardDAV passwords saved in Settings must be entered again (Sessions-and-Sign-in). - Uploaded attachments not yet sent or saved in a draft. The composer's next save or send is refused, and the composer lists the files to add again.
- Sign-ins waiting for a security key or a recovery code, and security-key enrolments in progress. They start over.
- Open new-mail event streams.
- Rate-limit counts and lockouts.
- In-memory caches, such as the CalDAV and CardDAV home sets.
A restart keeps everything on the mail server (mail, settings, sender identities, security keys, linked accounts, the users' own CalDAV and CardDAV connections) and the files ALPS wrote: the log, the Let's Encrypt certificate cache, and the BIMI logo cache unless it is in the private /tmp.
On a single host:
systemctl stop alps
install -m 755 build/alps-linux-amd64 /usr/local/bin/alps
systemctl start alps
/usr/local/bin/alps -versionWhen several ALPS instances serve the same accounts, in a cluster or as separate standalone instances, upgrade them together, never one node at a time:
- Stop every instance that runs the old binary.
- Wait until each one has exited:
systemctl is-active alpsprintsinactiveon every host. - Install the new binary everywhere and start the instances, in any order.
Mixing binaries is not supported, because of how two-factor sign-in records are kept (WEBAUTHN.md). A configuration change with the same binary can be applied one node at a time (Clustering). Going back to an older binary needs a maintenance step for the two-factor records, so read Deployment and rollback before a rollback.
An upgrade ends every session, as any restart does. With login_key set and unchanged, browsers are restored from their login token.
To see what is running:
-
alps -versionprintsalps <version> (<commit>) built at <date>.make build,make build-linuxandmake build-freebsdset the three values. A binary from a plaingo buildprintsalps unknown (unknown) built at unknown. ALPS does not log its version. - Every response carries
X-Alps-Build, a hash of the web app ALPS serves together with the[branding]settings. It changes when the embedded frontend,[branding] default_themeor the choice of logo changes, or a file of a favicon package used aslogo(read at startup) changes (Config-Branding), not with every binary. The open web app uses it to notice that a newer frontend is being served.
The ALPS host holds no account data. Mail and the records ALPS keeps for each account (settings, identities, OpenPGP keys, linked accounts, security keys, own CalDAV and CardDAV connections) are on the mail server, in its folders and its IMAP METADATA. Back up the mail server, METADATA included (Storage-in-IMAP-METADATA).
On the ALPS host, back up:
-
/etc/alps/config.toml, and keep the copy as private as the file. A restored host needs the samelogin_keyfor login tokens, linked accounts and saved CalDAV and CardDAV passwords, and the same[webauthn] rpidfor security keys. - The drop-in and the logrotate file.
- The Let's Encrypt
cache_dirwhenstorage_provider = "file": it is the only copy of the account key and the certificates. - With the maildir provider, each maildir's
alps_store.jsonalong with the mail.
The temp directory, the BIMI logo cache and the log need no backup for ALPS to work.
Inbound, the host must accept:
-
[server] addrfrom the reverse proxy (here127.0.0.1:1323, loopback only); - when ALPS terminates TLS: the HTTPS port in
addr, and with Let's Encrypt the ACME HTTP-01 listener onacme_http_addr(default:80); - with
[cluster]: the gossip port (default 7946), TCP and UDP, from the other nodes.
Outbound, the firewall must allow (OPERATIONS.md):
| Destination | When | Port |
|---|---|---|
| IMAP server | Sign-in, and for the life of each session | 993 for imaps://, 143 for imap://, unless the URL names one |
| SMTP server | Each send | 465 for smtps://, 587 for smtp://, unless the URL names one |
| ManageSieve server | Filters and auto-reply | 4190 unless the URL names one |
| CalDAV and CardDAV servers | One OPTIONS check each at startup, then as users work |
HTTP or HTTPS |
| CalDAV and CardDAV servers that users enter in Settings | When a user saves a connection, then as the user works | HTTPS at public addresses; with [server] dav_allow_private_servers, also HTTP and any address |
| Password-change endpoint | Each password change | HTTP or HTTPS |
| Let's Encrypt | With provider = "letsencrypt"
|
HTTPS |
| S3 | With storage_provider = "s3"
|
HTTPS, or HTTP for an http:// endpoint |
| Other ALPS nodes | With [cluster]
|
7946 TCP and UDP by default |
| DNS | Every host name above, and the BIMI and SRV lookups | 53 |
| Any public web host | Remote images and fonts that users choose to load, and BIMI sender logos | HTTP and HTTPS, on any port a URL names |
The remote-content proxy, the BIMI fetch and, while dav_allow_private_servers is off, the requests to the DAV servers users enter connect directly and ignore HTTP_PROXY and HTTPS_PROXY. A host whose web traffic must pass an HTTP proxy cannot load remote content or BIMI logos, nor reach those DAV servers. The other HTTP clients (CalDAV and CardDAV for the servers in the configuration, the password endpoint, ACME, S3) follow HTTP_PROXY, HTTPS_PROXY and NO_PROXY.
dist/alps is an rc.d script. rc.subr starts daemon(8) as user alps, and daemon(8) runs /usr/local/bin/alps -config /usr/local/etc/alps/config.toml. daemon(8) appends ALPS's standard output and standard error to /var/log/alps/alps.log, writes ALPS's pid to /var/run/alps/alps.pid and its own to /var/run/alps/daemon.pid, and reopens alps.log when it receives SIGHUP. The script sets procname to /usr/local/bin/alps, so service alps status, stop and reload find ALPS by that pid file. Before each start, the script creates the log directory and /var/run/alps with mode 750, owned by alps_user and alps_group.
cp dist/alps /usr/local/etc/rc.d/alps
chmod +x /usr/local/etc/rc.d/alps
sysrc alps_enable=YES
service alps start
rc.conf variable |
Default |
|---|---|
alps_enable |
NO |
alps_user |
alps |
alps_group |
alps |
alps_config |
/usr/local/etc/alps/config.toml |
alps_logdir |
/var/log/alps |
-
The user
alpsmust exist. -
service alps reloadsends SIGHUP to ALPS. That reopens only a file ALPS opened itself through[logging] output, not daemon(8)'salps.log, and it does not reload the configuration. Useservice alps restartfor a configuration change. -
daemon(8), not ALPS, holds
alps.logopen, so newsyslog must send SIGHUP to the pid in/var/run/alps/daemon.pidafter moving it. The script's comments give this entry for/etc/newsyslog.conf.d/alps.conf:/var/log/alps/alps.log alps:alps 640 7 * @T00 JC /var/run/alps/daemon.pid 1A log file of its own set in
[logging] outputis rotated the other way: newsyslog sends SIGHUP to ALPS itself, at/var/run/alps/alps.pid. -
daemon(8) runs ALPS in
/, so set an absolutecache_dirfor Let's Encrypt. -
ALPS uses the shared
/tmpunless told otherwise, so settemp_dirto a directory of its own, created beforehand with mode 700 and owned byalps.
The FreeBSD script was checked against its source, FreeBSD's rc.subr and OPERATIONS.md only; it was not run on FreeBSD for this page.
These came up while building the setup.
Failed to open log file /var/log/alps/alps.log: open /var/log/alps/alps.log: read-only file system, falling back to stderr in journalctl -u alps. The unit's ProtectSystem=strict keeps ALPS from writing there. Add the directory to ReadWritePaths in the drop-in and restart. The log directory must also exist and belong to alps.
Attachments fail with HTTP 500 upload_storage_failed, small ones work. Uploads over 32 KiB are written to the temp directory, and it is missing or cannot be written. ALPS logs an ERROR line upload multipart: ... with the cause, such as no such file or directory or read-only file system. Under the shipped unit this happens when temp_dir is under the host's /tmp (the unit's private /tmp hides it) or outside ReadWritePaths. Leave temp_dir unset, or follow A temp directory of its own.
systemctl is-active alps prints activating, and the journal repeats the same error every 5 seconds, for example:
alps[3871]: Error loading config file: failed to parse TOML config: toml: line 9 (last key "server.read_timeout_sec"): incompatible types: TOML value has type string; destination has type integer
systemd[1]: alps.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: alps.service: Scheduled restart job, restart counter is at 2.
ALPS exits with status 1 on a configuration error and Restart=on-failure starts it again 5 seconds later, without end. These errors come before the log file is opened, so they are in the journal only. ALPS has no flag that only checks a configuration, so keep the previous file to go back to. Fix the file and run systemctl restart alps.
A configuration change does not take effect after systemctl reload alps. Reload only reopens the log. Run systemctl restart alps.
multipart-* files in a temp_dir of ALPS's own. A stop waits up to 5 seconds for the sessions to remove their upload files, and a crash or a kill leaves them all. A running ALPS removes them once an hour, starting an hour after startup: every multipart-* file at the top of the temp directory that is more than 7 days old, apart from the uploads it still holds. The sweep does not tell ALPS's files from another program's files of the same name, which is one more reason to give ALPS a temp directory of its own. No upload survives a restart, so while ALPS is stopped every multipart-* file in its own temp directory can be deleted. With temp_dir unset under the unit, systemd deletes the private /tmp at every stop.
A stop takes 30 seconds. A request was still running, such as a large download. ALPS gives it up to 30 seconds, and the unit's TimeoutStopSec=60 lets systemd wait for that.
Everyone has to sign in again after a restart. login_key is not set, or it changed. Set it once and keep it, the same on every node.
-
[server] debugoff (the default). -
[webauthn] rpidandoriginsset to the public hostname, and norpid is unsetwarning at startup. -
login_keyset, kept secret, the same on every node;config.tomlmode 640, owner root, groupalps. - Behind a proxy:
addron the loopback address or a private interface, and only your own proxies intrusted_proxies. Thesign-inlog line shows the client's address. -
[provider.imap] authserv_idsset to your receiving mail server's authserv-id. - The rate limiter left on (the default), with the same
[server.rate_limit]values on every node. - The log: in the journal, or in a file in a
750directory withReadWritePathsand logrotate. -
temp_dir: unset under the unit, or a directory of ALPS's own, mode 700, listed inReadWritePaths, with room abovemax_global_attachment_mib. - The unit from the current
dist/alps.service, withTimeoutStopSec=60. The drop-in:ReadWritePathsfor every directory ALPS writes. On FreeBSD, the currentdist/alpsscript, and newsyslog signalling/var/run/alps/daemon.pid. - Let's Encrypt: an absolute
cache_dirinReadWritePaths. - The firewall allows the outbound connections ALPS needs.
- Backups cover the mail server with its METADATA, and the ALPS configuration.
- Upgrades stop every instance before any new one starts.
Security-Features has the security side of this list, and Configuration-Overview goes through the configuration file key by key.
ALPS main a5443047 (linux/arm64 and darwin/arm64 builds), Debian 13.7 (debian:trixie image) with systemd 257.13-1~deb13u1, logrotate 3.22.0-1+b2, curl 8.14.1 and Python 3.13.5, in Docker Desktop (engine 29.9.2, arm64) on macOS 26.4; mail server sora (lab build, checkout e8eff5cb). Signal timings were measured with the darwin/arm64 build on the host, and the stop with a request still running also under systemd. The systemd setup was then rebuilt from this page in a fresh container, interactive systemctl edit included. The FreeBSD rc.d script was not run. 2026-11-12.
Updated from the code at main 92ec0e11 without a rerun: the drop-in, which no longer needs RestartForceExitStatus=SIGHUP now that ALPS handles SIGHUP during startup, so that troubleshooting case is gone; the protocol traces of debug, which now follow [logging]; the startup warning for unknown keys; the 500 answer and log line for an upload the temp directory cannot take; X-Alps-Build with a favicon package; and the CalDAV and CardDAV connections users save in Settings, in what a restart keeps, what to back up, the key they need and the outbound connections. The configuration file above, alone and with a temp_dir, was started once with a 92ec0e11 build on macOS, to check that ALPS knows every key and loads base and managesieve.
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