Skip to content

Run your own Exchange4all deployment

Run the only mail and calendaring server that is both fully MAPI compatible and not limited to running only on Windows Server operating systems and comes in an easy to deploy container.

Runtime Requirements

The following are the minimum requirements for running the Exchange4all container:

  • A host system with 8 CPU cores and 64GB of RAM
    • the -slim variant requires 4 vCPUs and 16GB of RAM
  • Ability to run an OCI compliant container
    • When using docker compose the known minimum working version is 1.29.2.
  • The server must never run out of hard disk space, as it is impossible to recover all the data in such a case.
  • The host should have a dedicated IP, or at least allow exclusive access to ports 80/443/25.
    • If the optional adsredir service is used, an additional dedicated IP is required.
  • We recommend running the container in host mode for IPv6 access. Therefore, the administrator should set up a firewall to only allow access to the publicly required ports.

Use behind load balancers/reverse proxies

Currently, deploying this container behind an SSL-terminating gateway is not supported. If traffic must be forwarded to the container, it should be done through a TLS proxy; using an HTTP proxy is neither recommended nor supported.

Additionally, all externally accessible services require visibility of the client's real IP addresses. The container performs rate limiting and other checks based on this information for both IPv4 and IPv6. Therefore, it is crucial to ensure that real client IP addresses are preserved and passed to the container in any deployment configuration. The easiest way to achieve this is by running the container in host network.

About docker compose

If you are using docker compose, make sure you are using at least version 1.29.2. Older versions are not part of our testing pipeline.

Quick Start

The following chapter explains the steps the administrator needs to take to configure an Exchange4all deployment.

Installation and administration

The easiest way to get started with Exchange4all is to download our bootstrap package. Inside the package you will find the setup.sh script, a sample docker-compose.yml and a sample systemd unit.

Important files and folders Use
setup.sh Helper script to simplify the initial configuration and start the Exchange4all container.
docker-compose.yml Configuration of the Exchange4all container.
docker-compose.override.yml (Optional) Changes to docker-compose.yml should be written to this file instead.
exchange4all-config.yml Configuration of the Exchange4all application. This is a symlink to the data location. Will be created when setup.sh is run.
.env Configuration secrets for the container.
/srv/exchange4all Location where Exchange4all stores its data.
/storage Storage location within the container, this is /srv/exchange4all on the host.

Note

When we refer to /storage, we are talking about the storage location within the container. By default it is at /srv/exchange4all/e4a, but can be overriden during the set up process.

The backup strategy should include at least the data location and the .env file.

Completing the setup by running the setup script

Info

At the moment the Exchange4all container image is not yet public. You need to run the following command to log into the private container registry

docker login https://registry-testing.kopano.one/

When you run the setup script, you will be asked the following questions. If you accept the default (value in square brackets), simply press Enter at each prompt.

./setup.sh

Domains

The container needs to create ssl certificates for the domain name at which it can be reached (called FQDN). If a different domain is used for email (called MAILDOMAIN) then an additional certificate is needed for autodiscover.MAILDOMAIN. The admin must ensure that these domain names resolve to the system.

FQDN of this deployment [exchange4all.local]: mail.example.com
Domain to use for email [exchange4all.local]: example.com

Clients such as Microsoft Outlook, but also mobile clients using ActiveSync, use a mechanism called "autodiscover" to configure the account without having to ask for any information other than the email address and password. This is what the autodiscover subdomain is used for. Optionally, admins can also set up a reverse proxy to redirect requests from https://your-mail-domain.com/Autodiscover/Autodiscover.xml to the actual domain of your Exchange4all installation. At the very least it should be confirmed that the address does not redirect to a conflicting service. For further reference or configuration, please refer to the Reverse Proxy configurations section.

Automatic SSL with Let's Encrypt

By default, the container creates a self-signed certificate (the root certificate can be downloaded from http://your-server.com/.well-known/root-ca.crt), but optionally a certificate can be retrieved from the Let's Encrypt service if an email address is provided for it during setup:

Email address to use for Let's Encrypt []:
This must be filled in with a valid email address in order to retrieve a certificate from Let's Encrypt.
By filling in this field, you agree to the Let's Encrypt Terms of Service.
your@email.com

Change the value to production after you've tested certificate retrieval for a trusted official Let's Encrypt certificate [Staging]:

It is recommended that you first verify that certificates can be retrieved using the Let's Encrypt staging environment. Once a certificate has been retrieved, the ACMESERVER option within the .env file can be set to production (or simply rerun the setup.sh script).

A note on using the Let's Encrypt Staging CA

Use of the Let's Encrypt Staging CA should be limited to certificate retrieval testing, not general testing of the Exchange4all application. The recommended sequence is:

  1. Leave the Email field empty to use a self-signed certificate from the container's own CA.
  2. When testing has progressed to the point where the server should be exposed to the public:
    1. Set the LEEMAIL variable in your .env file to your email address or re-run setup.sh
    2. Verify that the container can retrieve a certificate from Let's Encrypt.
    3. Switch to the Let's Encrypt production CA by setting ACMESERVER="staging" in your .env

Reusing existing SSL certificates

If existing SSL certificates are to be used, the Let's Encrypt option should be left blank. The desired certificates can then be mounted into the container using an override file.

To do this, create a file called docker-compose.override.yml in the same directory as docker-compose.yml and add the following content

version: "3.5"

services:
  exchange4all:
    volumes:
      - ./private.key:/opt/exchange4all/tls/fullchain.pem.key:ro
      - ./certificate.crt:/opt/exchange4all/tls/fullchain.pem:ro

If the certificate is from an internal CA, the root CA should also be mounted in the container. To do this, add the following line to the volumes block above

      - ./rootCA.pem:/opt/exchange4all/tls/root-ca.crt:ro

Data storage

Where to store data [/srv/exchange4all] 

For most environments this variable does not need to be changed.

Warning

Changing this value only updates the configuration and does not move any existing data.

First start

Once the configuration of the container has been completed, the latest container image will be fetched and the container will then be started. On initial startup, the container will perform some initialisation tasks which may take a few moments.

Pulling updated images and starting Exchange4all for the first time.
Once the startup is complete, you can navigate to https://your-server.com/manage/ to create your domain and users.
You can use the following credentials to log in to the manage ui:
User: admin@exchange4all.local
Password can be retrieved from /storage/admin.secret

You can check the progress by running docker compose ps and docker compose logs -f.

Once the container has finished starting, you can proceed to the next chapter and create your first domain and users.

Installing a licence file

In order to use Exchange4all, a licence file must be installed and properly activated. To add your *.license file to your system, copy it to /storage/license.

To manually trigger the activation of the licence, the command /usr/local/bin/lx-renew register must be executed inside the container. If the licence file is already present at the start of the container, activation will be performed automatically.

Without a valid licence and activation users will only be able to use the Exchange4all webmail client. Connections via Outlook, ActiveSync or IMAP/POP3 will be denied.

Users with the System Administrator role will receive nightly reminders of licence expiry and expired licences.

Configuring defaults for MX, SPF and Autodiscover

The admin interface provides the administrator with instructions on how to configure the MX and SPF records, and the autodiscover mechanism for clients. If these values are not configured in /storage/config.yaml, auto-generated values based on the container's FQDN will be used.

To use values other than the auto-generated ones, add the following configuration to your /storage/config.yaml file and modify it to suit your local environment.

e4a:
# skipping unrelated content
  domain_service:
    dns:
      - type: mx
        value: "mail.protection.exchange4all.local"
      - type: spf
        value: "v=spf1 include:spf.protection.exchange4all.local -all"
      - type: autodiscover
        value: ""

The autodiscover value should match the domain specified in the _autodiscover._tcp SRV record, which clients use to locate the autodiscover service.

Autodiscover recommendations for customer provided domains

Exchange4all provides a special service called "adsredir" to simplify the handling of autodiscover requests for hosting services. When using adsredir, SSL certificates will be requested on demand from Let's Encrypt (or any other service implementing the acme protocol). Please see /etc/service/e4a-service-adsredird/run in the container for runtime instructions.

Creating Users, Domains and Organisations

Once the container is up and running, the admin can create users and add domains in the system using the manage api and according web interface. The web interface is accessible via https://your-server.com/manage/.

Note

When the container is started for the first time, it automatically creates:

  • an organisation named "Default",
  • a domain based on the specified FQDN belonging to the above organisation
  • a user with "system administrator" privileges belonging to the above domain.

From the management interface, system administrators can

  • Create Organisations (often referred to as "Tenants").
    • Organisations are optional and can be used to group domains.
    • Users of domains belonging to the same organisation can see each other in the GAL (global address list).
  • Create email domains
    • Users, groups and aliases can be created within an email domain.

Please refer to the Information for Administrators for more detailed instructions on using the Manage Web Interface.

Permissions for using the Administration Interface

The admin interface implements permissions to restrict what users can change themselves:

  • A "user" can
    • log in, view and change details such as their own password or profile picture.
  • A "Global Administrator" can
    • do everything above, and delete domains
    • view and create users, groups and aliases
    • as long as they are part of the Global Administrator's organisation.
  • A "System Administrator" can
    • do everything above
    • and view, create and delete organisations.

Getting users from an LDAP source

Starting with v6.0.8 the container includes an easy way to run ad-connect to seamlessly integrate users from a single user directory. For instructions on how to configure ad-connect in general please refer to Managing users via Directory Synchronization in the Information for Administrators section of this documentation.

The following two requirements must be met:

  • The configuration file must be stored in /storage/adc.yaml (a sample configuration can be found in /storage/adc.yaml.dist).
  • The application id and secret token must be exposed to the container via the AD_CONNECT_AUTH environment variable.

Once these two conditions are met, a service called e4a-ad-connect will start within the same container to facilitate password checks. A cron job will be run every hour to perform a user import. To trigger the import manually, the adc-import script can be run inside the container.

Connect clients and change passwords

Please refer to the End User Documentation for usage instructions.

Changing the Exchange4all configuration

Exchange4all is configured centrally via a yaml file located at /storage/config.yaml and changes to this file are propagated to the individual services during container startup. The default configuration should cover most use cases, outside the examples given in this document. Please do not change it unless instructed to do so by support. Possible configuration options and defaults can be found in the container under /opt/exchange4all/config/conf.d/config-all-v1.yaml.

Changing PHP configuration

Note

The below instructions only apply to version 8.0.0 and newer.

The container includes a way to change selected PHP settings through environment variables. The following options are available:

Environment variable Example Description
PHP_FPM_POOL_RPC_MAX_CHILDREN 100 Maximum number of PHP-FPM worker processes for the RPC pool
PHP_FPM_POOL_RPC_MAX_SPARE_SERVERS 50 Maximum number of idle PHP-FPM processes allowed
PHP_FPM_POOL_RPC_MEMORY_LIMIT 32M PHP memory limit per process
PHP_FPM_POOL_RPC_MIN_SPARE_SERVERS 10 Minimum number of idle PHP-FPM processes
PHP_FPM_POOL_RPC_POST_MAX_SIZE 20M Maximum size of POST data (affects file uploads)
PHP_FPM_POOL_RPC_START_SERVERS 10 Number of PHP-FPM workers started on launch
PHP_FPM_POOL_RPC_UPLOAD_MAX_FILESIZE 20M Maximum file size for uploads via forms to RPC pool
PHP_FPM_POOL_WWW_MAX_CHILDREN 100 Maximum number of PHP-FPM worker processes for the frontend
PHP_FPM_POOL_WWW_MAX_SPARE_SERVERS 50 Maximum number of idle frontend PHP-FPM processes
PHP_FPM_POOL_WWW_MEMORY_LIMIT 64M Memory limit per frontend PHP process
PHP_FPM_POOL_WWW_MIN_SPARE_SERVERS 10 Minimum number of idle frontend processes
PHP_FPM_POOL_WWW_POST_MAX_SIZE 20M Maximum size of POST data for web interface (e.g. large attachments)
PHP_FPM_POOL_WWW_START_SERVERS 10 Initial number of frontend PHP processes on startup
PHP_FPM_POOL_WWW_UPLOAD_MAX_FILESIZE 20M Maximum upload size (e.g attachments) allowed in the web app

The www pool is used for clients such as the Webclient and ActiveSync devices, while the rpc pool is used for example by the indexing component.

Required ports

By default, the following application ports must be open to the network:

Service Port(s)
Postfix 25 (SMTP), 587 (submission, explicit TLS)
Web server 80 (HTTP), 443 (HTTPS)
POP3 995 (TLS)
IMAP 993 (TLS)

Inspecting the log files

Inside the container, the main Exchange4all services are configured to log to standard output, which is shown in the container logging. However, some supporting services write to dedicated log files:

Service Log Location
Postfix /var/log/mail.log
Caddy /var/log/caddy/
APISIX /var/log/apisix/

Log files are persisted between container restarts by storing them in /storage/log.

Adding additional domains to the system

When the container is started, the system is already configured to be accessible via the domain specified as the FQDN. If the system only handles a limited number of domains, these can be configured directly (to allow auto-detection of client configuration), if the system handles external client domains, the adsredir service should be used instead. Further documentation for this service can be found inside the container at /etc/runit/runsvdir/default/e4a-service-adsredird/run.

To add additional domains, open the .env file in an editor, find the line starting with DOMAINS= and add your domains between the double quotes, separating the domains with spaces. The container must be recreated to propagate the updates to the DOMAINS variable into the container.

Once the container has been recreated, execute in the container and manually run bash /usr/local/bin/lego-renew add to get certificates for your new domains and automatically add them to the web server.

Warning

Make sure that all domains specified in the DOMAINS environment variable actually resolve to the container, otherwise actions such as the Let's Encrypt certificate retrieval and activation of the license will fail.

Adding Additional Postfix Configuration

Administrators can easily override the configuration of both main.cf and master.cf using a mechanism inspired by the approach of docker-mailserver.

To extend the default configuration, you can create custom configuration files:

  • For main.cf: Create a file named /storage/postfix-main-v2.cf using the same format as main.cf.
  • For master.cf: Create a file named /storage/postfix-master.cf. The contents of this file will be passed line by line to postconf -P. Use the format <service_name>/<type>/<parameter> to define custom parameters.

These changes will be automatically applied the next time the container is started. To apply changes immediately, run /etc/my_init.d/43_postfix-config-overrides.sh && sv restart postfix inside the container.

Pass all mails through a mail relay

The following functionality is available as of v7.2.0

The container allows for a Postfix multi-instance configuration to ensure both inbound and outbound traffic pass through the configured relay. To enable this POSTMULTI_ENABLED=true needs to be added to the environment of the container.

Add the following to /storage/postfix-main-v2.cf to configure your relay and to set a unique hostname. This Postfix instance handles inbound messages.

myhostname=inbound.exchange4all.local
relayhost=[mail.protection.myexchange.rocks]:25
Add the following to /storage/postfix-outbound-main-v2.cf. This Postfix instance will handle outbound messages.

myhostname=outbound.exchange4all.local
relayhost=[mail.protection.myexchange.rocks]:25

inet_protocols=all
virtual_transport=smtp:[mail.protection.myexchange.rocks]:25
virtual_alias_maps=
mydestination=
alias_maps=
alias_database=
local_recipient_maps=
local_transport=error:5.1.1 Mailbox unavailable

Using a mail relay that requires auth

If the mail relay provider requires basic authentication, some additional commands are required for a successful installation. To add basic authentication to the postfix configuration, 3 additional lines need to be added to the /storage/postfix-main-v2.cf following the Adding Additional Postfix Configuration approach.

These lines are:

smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/storage/postfix/provider_auth
smtp_sasl_auth_enabled enables the basic authentication. smtp_sasl_security_optionsdefines what kind of security option the provider is using.

The available options are:

Option Description
noanonymous Disallows anonymous authentication mechanisms (usually the default option)
noplaintext Disallows mechanisms that transmit passwords in plain text
noactive Disallows active authentication mechanisms (e.g. captcha)
nodicationary Disallows mechanisms susceptible to dictionary attacks
mutual_auth Only allows mechanisms that provide mutual authentication

For further information, please reference the postfix documentation

smtp_sasl_password_maps allows postfix to know where to reference the authentication details. The example calls the file provider_auth, and places under /storage/postfix. While this isn't a requirement, update the file location, and name accordingly.

Note

Some providers require you to use port 587, so remember to update the relayhost variable accordingly

Updating

To update Exchange4all, all that needs to be done is to replace the E4AVERSION value in the .env file with the desired version specified in the release notes. After updating the E4AVERSION value, recreate the container to apply the update.

Prometheus Metrics

To get metrics from the container, the 9100 port must be made available.

When using ufw this can be achieved by running

ufw allow from 1111.1111.1111.1111 to any port 9100
ufw status

Metrics can then be retrieved from http://your-server.com:9100/metrics.

Enabling/disabling additional plugins on startup

Exchange4all uses a plugin architecture internally to provide functionality such as user limit enforcement for hosting setups. As plugins are stored within the filesystem of the container changes to them must be reapplied each time the container is recreated. To automate this, it should be specified through an environment variable, like in the example below:

# list of additional plugins to enable
E4A_FORCE_ENPLUGIN="zcore:svc:oidc delivery:svc:remote_mx_hello"
# List of additional plugins to disable
E4A_FORCE_DISPLUGIN="smtp:pas:status_header delivery:svc:remote_mx_hello"

Warning

The above two variables should only be set if instructed to do so by support.

Backup

At this time Exchange4all only supports full system backups and while individual users/mailboxes can be extracted from this backup, a per folder or per item restore is not currently part of the software. To perform a backup we recommend using file system snapshots (our reference system uses zfs snapshots). The docker-compose.yaml example centralises data on the host in the /srv/exchange4all folder. Inside this folder you will find the following data:

  • /srv/exchange4all/db: Mount point for the MariaDB database in /var/lib/mysql.
    • We use InnoDB as the storage engine in MariaDB.
    • To centralise backups to the /storage folder, the following command could be run before taking the snapshot /usr/bin/mysqldump email > /storage/backup.sql.
    • When MariaDB is first started, it looks in /docker-entrypoint-initdb.d for scripts to run or sql files to import. A previously created backup could be mounted as /docker-entrypoint-initdb.d/99-my-backup.sql and would be automatically imported the first time the instance is booted.
  • /srv/exchange4all/e4a/: Mount point for our main data folder /storage.
    • The container is configured to write all persistent data to /storage. This includes things like service configuration, generated passwords, the Redis database and user data (located under /storage/system/var/storage).
    • Redis is configured to use an append-only file. No additional steps are required before the Redis data can be snapshotted.
  • /srv/exchange4all/php/: Mount point for php sessions in /var/lib/php/sessions.
    • These could be considered optional, as without them users would simply have to login to the Exchange4all webmail client again.
  • /run/exchange4all/webapp: This is a temporary folder for the Exchange4all webmail client and contains short-lived data created when interacting with attachments. This folder does not need to be backed up and it is also recommended to mounted as tmpfs on the host.

Running test/preview versions

Info

This section will be largely obsolete once the container is publicly available.

A test container is mirrored to a special private registry. Access requires an account on the Kopano Cloudron with membership in the "testers" group.

Login to the registry:

docker login https://registry-testing.kopano.one/

Change the E4AIMAGE variable in your .env file to point to the testers registry:

E4AIMAGE=registry-testing.kopano.one/e4a-container

The test version will be released with a specific tag, this can can be specified through the E4AVERSION variable in your .env file:

E4AVERSION=test-image-for-problem@sha256...

Warning

Please note that while only selected users have access to the above registry, write access is intentionally not restricted. Only start containers with a specific digest.

Security

The container includes several built-in features to protect against malicious activity.

Rate limiting incoming requests

By default, the container is optimized for hosting scenarios where only a few users share the same IP address. If a bigger group of users shares the same IP, you will need to either whitelist this IP address or adjust the rate limits accordingly.

Each service that handles user requests has its own IP filter settings. The http section handles requests from Outlook, ActiveSync clients, EWS and the webmail client.

e4a:
  http:
    ip_filter:
      audit_interval: 1second
      audit_times: 200
  imap:
    ip_filter:
      audit_interval: 1second
      audit_times: 200
  pop3:
    ip_filter:
      audit_interval: 1second
      audit_times: 200
  smtp:
    ip_filter:
      audit_interval: 1second
      audit_times: 200

The audit_times option defines the number of requests allowed within the period defined by audit_interval. The interval is specified as Xsecond (e.g. 5second or 60second), if the value is set to 0second the filter is disabled. To check that the settings have been applied correctly, each service prints out its current rate limiting configuration on startup.

To set different limits for a set IP address, add an entry for it to /storage/system/var/data/ip_filter.txt:

# ip audit_times audit_interval
127.0.0.1 1 0second # rate limiting is disabled for this ip
198.51.100.5 400 1second # allows 400 requests every second
203.0.113.19 1000 5second # allows 1000 requests every five seconds

Rate limiting user sessions

After applying the ip filter, the container also restricts the number of sessions a user can create in a given period. So, unlike the ip filter, the user gets restricted, and not the ip address.

e4a:
  common:
    session:
      user_session:
        rate_limit_period: 10minutes
        rate_limit_burst: 100   

The rate_limit_burst option defines the maximum number of new login sessions a user can create within a given period, specified by rate_limit_period.

Disabling Connection and Session Limits

You can disable connection and session rate limits by enabling log-only mode. To do so:

  • Set E4A_IP_FILTER_LOG_ONLY=1 to disable enforcement of the IP filter.
  • Set E4A_SESSION_RATE_LIMIT_LOG_ONLY=1 to disable session rate limit enforcement.
Querying Active Sessions

Active sessions are stored in a Redis database. You can query active sessions by running the following command:

e4a service admin dump-sessions

The output is in JSON format. To retrieve sessions for a specific user (e.g., username@example.com), use the following command:

e4a service admin dump-sessions | jq 'select(.record[1].v1.username == "username@example.com")'

Rate limiting outgoing emails

The e4a-policyd service is responsible for managing outgoing email quotas. Limits are implemented as "leaky buckets":

  • at each interval the bucket(s) are replenished to the set limit
  • each message sent takes something out of the bucket
  • once the bucket is empty, no more messages can be sent.

The interval to refill and the individual sizes of the buckets are stored in /storage/config.yaml, the defaults are:

e4a:
  policyd:
    quota:
      smtp:
        key_prefix: "pol:quota:"
        interval: 12hours
        limits:
          default: 500
          org: 1000
          domain: 1000
          internal: 1000
  • internal limits the number of mails that can be sent internally on the same server.
  • default is the limit per user.
  • domain limits how many mails the sending user's domain can send.
  • In addition to the domain, org limits mails sent by the user's organisation.

Frequently asked questions

Below are answers to some frequently asked questions.

How does licence activation work?

Licence activation uses the ACME protocol to retrieve a short-lived certificate that unlocks the features that are part of the purchased subscription. To retrieve the certificate, the licence exchange tool (lx for short) performs an HTTP-01 challenge for each domain that is part of the DOMAINS environment variable.

Retrieved certificates are valid for 60 days by default, and lx will automatically attempt to renew the certificate if it is valid for less than 40 days.

How do I reset the admin password?

If the (main) admin password has been lost, and there are no other users with system administrator privileges to trigger a password reset from the manage ui, the following method can be used to send a forgotten password mail.

docker exec e4a_exchange4all_1 \
  curl -s --unix-socket /var/run/exchange4all-manage-api/rest0.sock "http://manage-api/api/v1/invitations" \
    -H 'content-type: application/json' \
    --data-raw '{"invitedUserDisplayName":"Administrator","invitedUserEmailAddress":"external-address@example.com","inviteRedeemUrl":"https://exchange4all.local/manage/#/oidc/request-callback?to=set-password","sendInvitationMessage":true,"invitedUserType":"Member","invitedUser":[{"id":"admin@exchange4all.local"}]}'

In the above command, it is important to replace invitedUserEmailAddress and id with the actual details of the desired user.

If the container cannot (yet) send mail, the following method can also be used to replace the password directly.

docker exec e4a_exchange4all_1 \
  curl -s --unix-socket /var/run/exchange4all-manage-api/rest0.sock "http://manage-api/api/v1/users/admin@exchange4all.local" \
    -X 'PATCH' \
    -H 'content-type: application/json' \
    --data-raw '{"passwordProfile":{"password":"new-password"}}'

In the above command, the user id (above admin@exchange4all.local) must be replaced with the actual user id.

How does automatic configuration of Outlook and mobile clients work?

Microsoft documents the steps at Autodiscover services in Outlook.

Why does Exchange4all need a dedicated IP address?

At this stage the container is designed to terminate SSL connections on its own and does not support use with an SSL terminating proxy in front of it. A second dedicated IP is required when using the adsredir service.

Why does Exchange4all only configure smtp authentication on the inbound port?

When setting up the container we decided to allow authentication by default only on the submission port (587) and not on the SMTP port (25). The practical reason for this is that it allows admins to better implement rate limiting for the different protocols by separating client to server and server to server traffic.

What if Outlook Autodiscover detects IMAP?

As part of the Simplified Account Creation process, Outlook performs a lookup against Microsoft services at prod-autodetect.outlookmobile.com. If you change providers, this service may show outdated but still cached values for the old provider. If this happens, select "Configure manually" and then select "Exchange" to properly autodetect the remaining setting for your mailbox.

Additional steps can be taken to avoid having to manually select the account type:

  • Add HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Outlook\Setup\DisableAccountSettingsDetectionService as a DWORD value of 1. This disables the account detection service and goes straight to the account type dialogue box where the user has to select "Exchange". To automatically add the value to your registry, run this reg file.
  • Add HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Outlook\Setup\DisableOffice365SimplifiedAccountCreation as DWORD value 1. This disables the simplified account creation and therefore the lookup of the Microsoft service.
  • Setting the A/AAAA records for the imap, pop and pop3 subdomains to 0.0.0.0 or :: will eventually invalidate the Microsoft service data.

I am getting midb out of memory during a migration, even though I have plenty of ram left

When running large migrations via IMAP it could be that internal buffers of midb are running full, which leads to the error message midb out of memory. To prevent this from happening the following settings can be adjusted temporarily.

e4a:
  imap:
    context_average_mem: 512K
    context_max_mem: 5M

How do disabled users/domains affect usage reporting?

As soon as a user or a domain has been disabled, it will be automatically deducted from the number of licensed users. Mail for such a user will then also no longer be accepted.

How can I change the memory limit for php?

Note

With the 8.x release of e4a-container we will introduce a way to override selected php settings via environment variables and the below description will be obsolete.

When managing users with very large mailboxes or onboarding many new users at once, some backend processes may encounter the following error:

PHP Fatal error:  Allowed memory size of 67108864 bytes exhausted

Currently, there is no direct container setting available to increase the PHP memory limit. However, it can be modified manually as described below.

The relevant configuration file is /etc/php/7.4/fpm/pool.d/php-fpm-pool-www.conf. This file controls the shared PHP socket used by e4a-system, e4a-push, and e4a-webapp.

To override the default PHP memory limit, use the container’s init system. Create an executable script named php-override.sh and mount it into /etc/my_init.d/. This script will be executed automatically before any services are started.

Example php-override.sh:

#!/bin/bash
FILE="/etc/php/7.4/fpm/pool.d/php-fpm-pool-www.conf"
sed -i "s/^\s*php_admin_value\[memory_limit\].*/php_admin_value[memory_limit] = 128M/" "$FILE"

Note

Scripts in /etc/my_init.d/ are executed in alphabetical order before any services are launched. Ensure your script is executable and runs after any of the containers default scripts.

Appendix

Docker Compose Cheat Sheet

Some useful commands for interacting with the container:

# Pull the container tag specified in .env (or the latest if nothing is specified)
docker compose pull
# Start the container in the background, then put the container output into a new command
docker compose up -d && docker compose logs -f
# Get a bash shell in the container
docker compose exec exchange4all bash
# Get a list of environment variables from the container
docker compose exec exchange4all env | sort
# Prevent the container from deleting any volume data
docker compose down -v
# restart the container (will restart but not rebuild)
docker compose restart
# get verbose healtcheck information from the container
docker compose exec exchange4all healthcheck.sh --format documentation
# get the information of the user user1@exchange4all.local
docker compose exec exchange4all e4a userinfo user1@exchange4all.local 
# If using the systemd unit, this will get the latest available tag and restart the container
systemctl restart exchange4all

Additional DNS Entries for Client Autodiscovery

The RFC 6186 defines the following additional DNS entries to allow clients to auto-configure hostname and port.

# grep SRV zones/my.server/my.server.zone
_imap._tcp              IN SRV   0 0 0   .
_imaps._tcp IN SRV 0 0 0 993 your-server.com.
_pop3._tcp IN SRV 0 0 0 .
_pop3s._tcp IN SRV 10 0 995 your-server.com.
_smtp._tcp IN SRV 0 0 0 0 .
_smtps._tcp IN SRV 0 0 0 .
_submission._tcp IN SRV 0 0 0 587 your-server.com.
_autodiscover._tcp IN SRV 0 0 443 your-server.com.

Enabling additional logging for debugging

To debug problems in the container, additional logging can be enabled by setting the following environment variables:

  • TESTING_LOGGING=true - enable debug logging for all e4a services in the container
  • LIBSEGFAULT_ENABLED=true - enable libsegfault to get more information when a service crashes

Example:

version: "3.5"

services:
  exchange4all:
    environment:
      - LIBSEGFAULT_ENABLED=true
      - TESTING_LOGGING=true

Configuring the container/host for core dumps

To get a core dump in the event of a crash, the following changes need to be made to both the container and the host.

On the host itself, the core pattern must be set to a location that exists on both the host and the container. We recommend configuring this as /storage/coredumps/, while ensuring that a quota is set on the filesystem, to limit the amount of data that can be written to it (so that the whole server does not potentially run out of space).

With ZFS this can be done as follows:

mkdir -p /storage
zfs create -o quota=50G -o canmount=on -o mountpoint=/storage/coredumps \
    rpool/USERDATA/coredumps
chmod 777 /storage/coredumps # 777 is used because various user ids in the container need write access to this location.

The pattern itself can either be set temporarily at runtime by running

echo '/storage/coredumps/core-%e.%h.%t.%p' | sudo tee /proc/sys/kernel/core_pattern

Or permanently by running:

cat <<EOF >>/etc/sysctl.d/99-coredump.conf
kernel.core_pattern=/storage/coredumps/core-%e.%h.%t.%p
EOF
sysctl -p /etc/sysctl.d/99-coredump.conf

To configure the container for core dump simply add the following content to your docker-compose.override.yml:

version: "3.5"

services:
  exchange4all:
    volumes:
      - /storage/coredumps/:/storage/coredumps/
    ulimits:
      core: -1

Testing if core dumps are generated

Run the following command to force a core dump of the http process:

Warning

This will interrupt user connections, and is not recommended to be run in a production environment

kill -SEGV $(pidof http)

Reporting crashes to the maintainers

When reporting a crash please first make sure core dumps are configured and a core dump has been created.

The following information should be included in the report:

  • The backtrace from the log
    • alternatively the backtrace can be extracted from the core file, as long as the version the core file was created from still matches the current running version
    • gdb --cd=/opt/exchange4all/system/var/run /opt/exchange4all/system/libexec/imap /storage/core/filename
    • then type bt
    • optionally you can request the matching debug symbols for your container
  • A compressed dump file
    • xz /storage/coredumps/core-http.exchange4all.local.1641398017.5206
    • Ensure that the dump file is compressed before sending
  • The exact version of the container

    • e.g. from .env or via docker inspect -f {{.Config.Labels}} e4a_exchange4all_1
  • and the exact versions of the components in the container

    • e.g. from /opt/exchange4all/.version or again from the labels

When completed:

Getting (debug) logs from the ActiveSync component

The ActiveSync component is simply called push and uses the following log files:

  • /storage/push/logs/running.log.
    • Default log file
    • Contains the ActiveSync command, memory usage, timings, device and user information
  • /storage/push/logs/user/userlog.log.
    • Truncated log file containing information when a user has
      • sent a mail
      • changed/moved/created an item in a particular folder
  • /storage/push/logs/debug.txt
    • is automatically created for matching errors
    • additional debug output can be generated by defining DEBUG_BACKTRACE_IGNORE_ARGS

Additional logging per user can be enabled by setting the LOG_USER_FILTER configuration option in /opt/exchange4all/push/config/config.php:

// debug log switch and filter
// user account should be split by ','
// empty string to log all users
// comment to totally disable log
define("LOG_USER_FILTER", "markus@example.com");

Once this configuration option is set (and the client makes a new request), a folder with the user's identity will be created under /storage/push/logs/dump/ containing a separate log file for each connected device.

Non-persistent configuration file

The file /opt/exchange4all/push/config/config.php is part of the container file system and any manual changes to it will be overwritten when the container is restarted or recreated.

Reverse Proxy configurations

If, for any reason, the Autodiscover domain (e.g. autodiscover.example.com) cannot be pointed directly to the Exchange4all container, due to DNS policies, infrastructure constraints, customer preferences, or other issues, a reverse proxy can be used to redirect Autodiscover traffic from an external server to the container hosting the Exchange4all instance. For example, requests like https://example.com/Autodiscover/Autodiscover.xml can be redirected to your actual Exchange4all instance via a proxy. This is not the default or recommended deployment method, but it is supported as a fallback option.

To do this, add the following lines to the nginx.conf file of your external server.

location = /mail/config-v1.1.xml {
  return 302 https://autodiscover.example.com$request_uri;
  }

  location /autodiscover/ {
  return 302 https://autodiscover.example.com$request_uri;
  }

  location /Autodiscover/ {
  return 302 https://autodiscover.example.com$request_uri;
  }
Remember to replace autodiscover.example.com with your actual autodiscover domain. These few lines reroute any that traffic that hit the location endpoints, to the autodiscover endpoint running the Exchange4all installation.

Optional Environment Variables

Each of the following variables can be set either at container level or (where applicable) for a particular service.

Environment variable Example Description
ACMESERVER staging ACME endpoint used for certificate retrieval
DOMAINS mail.example.com example.com Domain names through which the system should be reachable. Used for certificate retrieval (if configured) and web server configuration.
E4A_LOG_LEVEL 4 Controls the logging level of the e4a services.
E4A_LOG_PUT_TIME 0 Prefix for the time and date the log was created.
E4A_SERVICED_MAILGEN_COMPANY_NAME ExampleCompany Company name to be used for generated mails.
E4A_SERVICED_MAILGEN_LOGO_PATH /opt/exchange4all/manage/manage-webapp/ Logo to be used for generated mails
E4A_SERVICED_MAILGEN_MAIL_FROM_ADDR no-reply@example.com Sending information to be used for generated mails
E4AFQDN mail.example.com The main domain of the system.
E4AMAILDOMAIN example.com First mail domain to be created (only relevant at first start)
encryption_secret_key /storage/lico/lico-encryption-secret.key The location of the encryption secret used for auth tokens.
identifier_registration_conf /storage/lico/identifier-registration.yaml The location of the OpenID provider registration.
identifier_scopes_conf /storage/lico/scopes.yaml Location of the scopes configuration for the OpenID provider.
LEGO_PATH /storage/.lego-tmp Location of the Let's Encrypt client data.
LIBSEGFAULT_ENABLED true Use libsegfault for better stack traces on crashes (default)
MARIADB_UNIX_PORT /run/mysqld/mysqld.sock MariaDB unix socket location
NOPERMISSIONS true Don't chown /storage/system and /storage/service (only done on first start)
signing_private_key /storage/lico/signing-private-key.pem location of private key used to sign auth tokens
SUBWORKERS 1 Number of worker processes for the e4a-manage-api
ULIMIT_FRONTEND 65536 Open file limit for front facing services
ULIMIT_GENERAL 8196 Open file limit for general services
ULIMIT_IGNORE false Ignore container warning about low open file limit
ULIMIT_NETWORKED 102400 Open file limit for networked services
validation_keys_path /storage/lico/validationkeys Location of the keys used to verify auth tokens

Explanation of privilegeBits

When using the Manage API to create or modify users and domains, the following privilege bits can be specified. These are summed and then passed as a single value for privilegeBits.

User Privilege Bits

Value Name Description
0x1 POP3_IMAP if user can retrieve mail via pop3/imap
0x2 SMTP if user can send email via SMTP
0x4 CHGPASSWD if user can change password
0x40 USRADM user administrator
0x100 DOMADM domain administrator
0x400 ORGADM Organisation Administrator
0x1000 SYSADM System Administrator

Domain Privilege Bits

Value Name Description
0x1 ARCHIVE reserved for historical reasons via the archive_agent plugin, handled by daemon/slugs/sync_from_mysql_1.php
0x2 MONITOR unused, reserved for historical reasons
0x4 VERYFYD indicates that the domain has passed validation
0x8 SUBSYSTEM Reserved for historical reasons via domain_subsystem plugin processed by daemon/slugs/sync_from_mysql_1.php
0x10 NETDISK unused, reserved for historical reasons
0x20 EXTPASSWD unused, reserved for historical reasons
0x400 SUBROOT if domain is available as parent for subdomain creation