Skip to content

Securing the server

A mail server is exposed to the internet by its nature. It must accept connections from any other mail server, and its sign-in ports are open to anyone who tries to guess a password. A new Stalwart server therefore starts with protections already active: auto-ban blocks addresses that repeatedly fail authentication, probe for accounts or scan for vulnerable HTTP paths, and the default rate limits restrict how fast a single client can submit mail.

Other protections depend on decisions that only the administrator can make: which protocols the users need, who administers the server, and what sits in front of it on the network. No default can make these safe, because the right answer differs from one deployment to the next. This page walks through them. It continues the installation tutorials (Linux, macOS and FreeBSD, Docker, Windows) after the test message, and its steps belong before the server is used in production.

The STALWART_RECOVERY_ADMIN environment variable, if it was set during the installation, defines an administrator that does not depend on the directory. While the variable is set, the server accepts the credential at every start, also in normal operation. The credential also acts as a master user: a sign-in as <account>%<recovery admin> with the recovery password opens that account, as described in Impersonation. A forgotten variable is therefore a permanent full-access credential. Recovery administrator describes it.

Once a permanent administrator account exists and has been used to sign in, remove the variable from the service environment and restart the server. The variable lives in the environment file for installations made with the script, in the container environment for Docker, and on the NSSM Environment tab on Windows.

The server creates the following listeners at first start, each bound to all interfaces ([::]):

ListenerPortProtocolTLS
smtp25SMTP, mail from other serversSTARTTLS
submissions465SMTP submission from clientsImplicit TLS
imaps993IMAPImplicit TLS
pop3s995POP3Implicit TLS
sieve4190ManageSieveSTARTTLS
https443HTTP: JMAP, CalDAV, CardDAV, WebDAV, WebUI, OAuth, autoconfigurationImplicit TLS
http8080HTTPNone

Ports 587 (submission with STARTTLS), 143 (IMAP) and 110 (POP3) have no listener unless the administrator creates one, as described in Listeners. Each listener that remains is a service exposed to the internet, so remove those the deployment does not use:

  • pop3s when no client uses POP3
  • sieve when users do not manage Sieve filters from a ManageSieve client, since filters can also be managed through JMAP and the WebUI
  • http on port 8080 once the setup is finished and HTTPS on port 443 works

The http listener serves the same endpoints as https over plain HTTP, and the HTTP API accepts passwords on it through Basic authentication without encryption. The WebUI sign-in does not work on it in normal operation, because the server binds OAuth sign-in to its HTTPS address. Behind a reverse proxy that connects to Stalwart over plain HTTP, keep the listener but make it reachable only from the proxy.

Deleting a listener’s NetworkListener object removes it, and the change takes effect after a restart. Blocking the port in a firewall has the same effect for external clients. Port 25 must stay open to receive mail, and 443 is needed by most clients and by the default ACME challenge.

During bootstrap and recovery mode, the server replaces every listener with a single plain HTTP listener on port 8080 (or the port set by STALWART_RECOVERY_MODE_PORT) bound to all interfaces, [::], or 0.0.0.0 on hosts without IPv6. The bind address cannot be changed. In these modes auto-ban, rate limits and HTTP access control rules are not applied, and permissive CORS headers are sent, so the listener has no protection against password guessing. Anyone who reaches it during bootstrap and knows or guesses the temporary password can configure the server.

Keep the listener off the internet while these modes are active:

  • With Docker, publish the port on the loopback interface of the host only, -p 127.0.0.1:8080:8080, and reach it from another machine through an SSH tunnel (ssh -L 8080:127.0.0.1:8080 <host>). Do not change the bind address inside the container: the image health check connects to 127.0.0.1:8080 inside the container, and Docker forwards published ports to the container’s network interface.
  • On a host installed with the installation script, block port 8080 in the host firewall for every source except the administrator’s address, or use an SSH tunnel as above.
  • In either case the host can stay unreachable from the internet until the setup wizard is completed.

The administrator created by the setup wizard holds every permission. For daily work, create administrator accounts with only the permissions each person needs, for example one role for account management and another for the mail queue, so that a leaked credential or a mistake affects only that area. Use these accounts only for administration, not as mailboxes in IMAP, JMAP or DAV clients, so that administrative credentials are not stored in mail and calendar applications on many devices.

The HTTP listeners serve every HTTP-based service from the same port: JMAP, CalDAV, CardDAV and WebDAV, the WebUI and the management API, OAuth, autoconfiguration, MTA-STS policy files, metrics and the form submission endpoint. HTTP access control rules are expressions evaluated for every request that can refuse paths, methods or source addresses, for example to make the management API or /metrics reachable only from an internal network, or to disable WebDAV file storage. The list of HTTP endpoints shows what each path serves.

The steps above close the exposures that every deployment has. The following settings and features are worth a review as well, because their right values depend on the users and the network. Each item links to the page that explains it:

  • The password policy of the internal directory, set by minPasswordLength, maxPasswordLength and minPasswordStrength on the Authentication object, as described in Passwords.
  • Two-factor authentication and app passwords for accounts in the internal directory.
  • The protocol versions and cipher suites the listeners accept, described in TLS.
  • The auto-ban thresholds and exemptions. Addresses that must never be banned, such as monitoring systems and the administrator’s network, belong in the allowed IP list, as described in Auto-ban.
  • The rate limits for inbound SMTP and for HTTP, described in Inbound rate limits and HTTP settings.
  • The trust placed in a reverse proxy. When a proxy sits in front of the server, configure how the client address is obtained, so that bans and rate limits apply to clients and not to the proxy, as described in Reverse proxy.
  • Port 8080, which must be closed to the internet as described above.
  • The service account. The installation script and the container image run the server as an unprivileged user, and a server started as root by other means should set RUN_AS_USER and RUN_AS_GROUP, which make it drop privileges after binding its ports, as described in Environment variables.

The server is ready for production. Next steps lists the tasks that usually follow: further domains and accounts, client setup, backups and upgrades.