perch serve runs in the foreground, logs to stderr, drains connections on
SIGTERM/SIGINT (waiting up to five seconds), and reloads certificate files
on SIGHUP, so it maps directly onto a Type=simple unit. It does not fork,
write a PID file, implement sd_notify, or support socket activation; do not
use Type=notify or socket units.
The examples below use a dedicated perch service account,
/etc/perch/perch.yaml, and /var/lib/perch. Adjust paths if your layout
differs, and keep data_dir in YAML in sync with StateDirectory and
ReadWritePaths.
1. Create the account and install
sudo useradd --system --home-dir /var/lib/perch --shell /usr/sbin/nologin perch
sudo install -Dm0755 perch /usr/local/bin/perch
sudo install -d -m 0700 -o perch -g perch /etc/perch /var/lib/perch
# Provision mxRaven and write the config as the service user.
sudo -u perch -H env MXRAVEN_API_TOKEN=... \
/usr/local/bin/perch setup \
--config /etc/perch/perch.yaml \
--data-dir /var/lib/perch \
--workspace my-workspace \
--domain example.com \
--hostname mail.example.com
# Create the first mailbox.
sudo -u perch -H /usr/local/bin/perch account add \
--config /etc/perch/perch.yaml \
--email you@example.comRun perch setup, perch init, perch account, and perch doctor as the
service user (or chown their output afterwards). perch writes the config with
mode 0600 and the data directory with mode 0700, so a root-owned config
will not be readable by the perch account.
Verify everything before starting the service:
sudo -u perch -H /usr/local/bin/perch doctor --config /etc/perch/perch.yaml2. Create the unit
Write /etc/systemd/system/perch.service:
[Unit]
Description=perch mail server
Documentation=https://github.com/synqronlabs/perch
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=perch
Group=perch
ExecStart=/usr/local/bin/perch serve --config /etc/perch/perch.yaml
ExecReload=/bin/kill -HUP $MAINPID
EnvironmentFile=-/etc/perch/perch.env
Restart=on-failure
RestartSec=5s
TimeoutStopSec=15s
LimitNOFILE=65536
UMask=0077
# Must match data_dir in /etc/perch/perch.yaml.
StateDirectory=perch
StateDirectoryMode=0700
ReadWritePaths=/var/lib/perch
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.targetIf you use perch setup --public (ports 143/993/587/465) or ACME, add:
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICEOpen the corresponding ports in your firewall. For a public deployment:
sudo ufw allow 80/tcp
sudo ufw allow 143,993,587,465/tcpThe default quickstart ports (1143/1587/1993/1465) are unprivileged and
need neither capabilities nor firewall changes for loopback use.
ExecReload sends SIGHUP, which perch only handles when tls.cert_file and
tls.key_file are configured. With ACME or a plaintext-only loopback setup,
remove the ExecReload line: SIGHUP would terminate the process.
3. Optional: secrets from the environment
perch setup writes listener and S3 secrets inline into the 0600 config by
default. To keep them out of YAML, replace key_secret: with
key_secret_env: PERCH_MXRAVEN_KEY_SECRET (and similarly for the submission
listener) and secret_key: with secret_key_env: PERCH_S3_SECRET_KEY, then
put the values in /etc/perch/perch.env:
sudo install -m 0600 /dev/null /etc/perch/perch.env
sudo tee /etc/perch/perch.env >/dev/null <<'EOF'
PERCH_S3_SECRET_KEY=...
PERCH_MXRAVEN_KEY_SECRET=...
PERCH_MXRAVEN_SUBMISSION_KEY_SECRET=...
EOFsystemd reads EnvironmentFile as root before dropping privileges, so the file
can remain root-owned and unreadable by the service account.
4. Enable and verify
sudo systemctl daemon-reload
sudo systemctl enable --now perch
sudo systemctl status perch
sudo journalctl -u perch -fLogs are captured by journald; set log.format: json in perch.yaml for
structured output. Account, alias, and mailbox changes apply immediately
without a restart. After changing perch.yaml, run
sudo systemctl restart perch.
Rootless user service
perch also works as a per-user service under systemctl --user. Put the unit
in ~/.config/systemd/user/, drop User=, Group=, and StateDirectory=,
adjust ProtectHome/ProtectSystem/ReadWritePaths, and pass explicit
--config/--data-dir paths. Enable lingering:
loginctl enable-linger "$USER"
systemctl --user enable --now perchUser services cannot acquire CAP_NET_BIND_SERVICE, so this only works with
unprivileged listener ports.
See Backup and garbage collection for timer examples.