3.1. Startup configuration
This is the non-reloadable part of the configuration. By default, smtpd loads it from /etc/halon/smtpd.yaml. It is described by, and can be validated with, the smtpd.schema.json.
It most importantly contains the server listen sockets (bind addresses and ports), PROXY protocol and thread settings.
Note
The example configuration in /opt/halon/share/examples/ that is usually copied to /etc/halon/ during installation contains reasonable defaults. Normally you only need to modify it when adding additional virtual servers, or changing performance related settings such as the number of threads or open files resource limit.
3.1.1. Server directives
The virtual servers[]
are configured in the running configuration,
but which port(s) and address(es) to listen to needs to be specified in this file.
Most of the properties applies to servers of both smtp
, http
, https
and plugin
type, therefor the only which applies to either are explicitly mentioned.
Below is an example for adding a virtual server called “relay”, listening to any IP on port 587:
servers:
- id: relay
listeners:
- port: 587
- servers[].id
The id of the server. It must match the same type as configured in the running configuration.
- servers[].type
The type of the server, a server can either be an
smtp
server, anhttp
/https
submission endpoint orplugin
inject plugin. The default issmtp
. Thehttps
server type requies a servers[].tls.certs.cert to be configured in the running configuration.
- servers[].listeners[]
Only one listerner is supported for servers of type http. Not supported for type plugin
Each virtual server must have one or more listen directives, which specify which TCP port and address to listen on.
- servers[].listeners[].port
Which TCP port to listen on. Required.
- servers[].listeners[].address
IPv4 or IPv6 address to listen on. The default is to listen to all IPv4 and IPv6 addresses.
- servers[].listeners[].backlog
The kernel connection backlog. The default is the system default.
- servers[].listeners[].id
An optional ID that can be used for referencing a listen directive from the running configuration so that for example implicit TLS can be enabled on a per-listener basis using
servers[].tls.implicit
.
- servers[].proxyprotocol[]
Only applies to smtp servers
A list of IP addresses or networks to allow the PROXY protocol (v1 and v2) from. Set it to true to allow it from all IP addresses.
- servers[].threads.event
Not supported for type plugin
The number of
servers[]
event loop threads, allowing the event loop to take advantage of multiple CPUs. If given as a number (and not an object with an ID), a event thread pool will be created named after theservers[].id
(prefixed with an underscore). The default is 4. If configured as an object you may specify an id of aservers[].threads.event.id
or an object withservers[].threads.event.count
andservers[].threads.event.priority
.
- servers[].threads.event.id
Only applies to smtp servers
The name of the
threads.events[].id
. This property is required if configured with custom event threads.
- servers[].threads.event.count
The number of
servers[]
event loop threads, allowing the event loop to take advantage of multiple CPUs. The default is 4.
- servers[].threads.event.priority
Only applies to smtp servers
The priority (nice value) of the thread. The less nice it is the higher priority (-20 to 19). The default is 0.
Note
Don’t forget to update this value as well
environment.rlimit.nice
.
- servers[].threads.script
The number of
servers[]
script threads running the hooks such asservers[].phases.eod.hook
. If given as a number (and not an object), a script thread pool will be created named after theservers[].id
(prefixed with an underscore) with theservers[].scripting.concurrency
andservers[].scripting.stacksize
. The default is 32.
- servers[].threads.script.id
The name of the default
threads.scripts[].id
. This property is required if configured with custom hook threads, and it will be used for any unconfigured hooks.
- servers[].threads.script.hooks.connect
Only applies to smtp servers
The name of the
threads.scripts[].id
for the connect hook.
- servers[].threads.script.hooks.proxy
Only applies to smtp servers
The name of the
threads.scripts[].id
for the proxy hook.
- servers[].threads.script.hooks.helo
Only applies to smtp servers
The name of the
threads.scripts[].id
for the helo hook.
- servers[].threads.script.hooks.auth
Only applies to smtp servers
The name of the
threads.scripts[].id
for the auth hook.
- servers[].threads.script.hooks.mailfrom
Only applies to smtp servers
The name of the
threads.scripts[].id
for the mailfrom hook.
- servers[].threads.script.hooks.rcptto
Only applies to smtp servers
The name of the
threads.scripts[].id
for the rcptto hook.
- servers[].threads.script.hooks.eod
The name of the
threads.scripts[].id
for the eod hook.
- servers[].threads.script.hooks.disconnect
Only applies to smtp servers
The name of the
threads.scripts[].id
for the disconnect hook.
- servers[].scripting.concurrency
The number of concurrent hooks that may be running concurrently on the
servers[].threads.script
. The default isservers[].threads.script
(1:1). However using a plugin or function supporting the HSL suspend functionality more may be running concurrently using cooperative multitasking (M:N).
- servers[].scripting.stacksize
The stack size of a hook. The default is 2097152 bytes (2 MiB). This setting shouldn’t be changed without a strong reason and understanding for doing so.
3.1.2. Queue directives
- queues.threads.event
The queue event loop threads, allowing the event loop to take advantage of multiple CPUs. If given as a number (and not an object without an ID), a event thread pool will be created named “_queues”. The default is 4. If configured as an object you may specify an id of a
queues.threads.event.id
or an object withqueues.threads.event.count
andqueues.threads.event.priority
.
- queues.threads.event.id
The name of the
threads.events[].id
. This property is required if configured with custom event threads.
- queues.threads.event.count
The number of queue event loop threads, allowing the event loop to take advantage of multiple CPUs. The default is 4.
- queues.threads.event.priority
The priority (nice value) of the thread. The less nice it is the higher priority (-20 to 19). The default is 0.
Note
Don’t forget to update this value as well
environment.rlimit.nice
.
- queues.threads.script
The number of queue script threads running the
scripting.hooks.predelivery
andscripting.hooks.postdelivery
hooks. If given as a number (and not an object), a script thread pool will be created named “_queues” with thequeues.scripting.concurrency
andqueues.scripting.stacksize
. The default is 32.
- queues.threads.script.id
The name of the default
threads.scripts[].id
. This property is required if configured with custom hook threads, and it will be used for any unconfigured hooks.
- queues.threads.script.hooks.predelivery
The name of the
threads.scripts[].id
for the predelivery hook.
- queues.threads.script.hooks.postdelivery
The name of the
threads.scripts[].id
for the postdelivery hook.
- queues.threads.pickup
If given as a number of threads preparing messages to be delivered from the active queue. The default is 1. If configured as an object see
queues.threads.pickup.count
.
- queues.threads.pickup.count
The number of threads preparing messages to be delivered from the active queue. The default is 1.
- queues.threads.pickup.priority
The priority (nice value) of the thread. The less nice it is the higher priority (-20 to 19). The default is 0.
Note
Don’t forget to update this value as well
environment.rlimit.nice
.
- queues.threads.fetch.priority
The priority (nice value) of the thread fetching messages from the queue. The less nice it is the higher priority (-20 to 19). The default is 0.
- queues.threads.rate.priority
The priority (nice value) of the thread refilling queue policy rates. The less nice it is the higher priority (-20 to 19). The default is 0.
- queues.threads.release.priority
The priority (nice value) of the thread flagging delivered messages (somewhat like the oppsite of
queues.threads.pickup
). The less nice it is the higher priority (-20 to 19). The default is 0.
- queues.scripting.concurrency
The number of concurrent hooks that may be running concurrently on the
queues.threads.script
. The default isqueues.threads.script
(1:1). However using a plugin or function supporting the HSL suspend functionality more may be running concurrently using cooperative multitasking (M:N).
- queues.scripting.stacksize
The stack size of a hook. The default is 2097152 bytes (2 MiB). This setting shouldn’t be changed without a strong reason and understanding for doing so.
- spool.path
The email queue spool path. This option is mutually exclusive with
spool.paths[].path
. The default is /var/spool/halon/queue.
- spool.paths[].path
The email queue spool paths (multiple paths). The paths are used in a round-robin fashion to spread load evenly. This option is mutually exclusive with
spool.path
. The default is /var/spool/halon/queue. During spool in messages are spooled in the order they are specified in the configuration.
- spool.paths[].write
If the path should used when receiving messages (or just consuming existing messages in that path). The default is true.
- spool.minfree.inodes
The minimum required free inodes on the spool.path disk. The default is 0.
- spool.minfree.bytes
The minimum required free bytes on the spool.path disk. The default is 0.
- spool.threads.loader
If given as a number of threads that read the spool files into memory during startup. Those are terminated once the spool is loaded. The default is 32. If configured as an object see
spool.threads.loader.count
.
- spool.threads.loader.count
The number of threads that read the spool files into memory during startup. The default is 32.
- spool.threads.loader.priority
The priority (nice value) of the thread. The less nice it is the higher priority (-20 to 19). The default is 0.
Note
Don’t forget to update this value as well
environment.rlimit.nice
.
- spool.threads.loader.wait
If the startup process should wait for the loader to spool in all messages. This allows quotas etc to be properly updated before new messages are accepted. For faster startup times, this can be disabled. The default is true.
- spool.threads.update
The maximum number of worker threads for API or CLI queue update of the spool files. The default is 32.
- spool.fsync
If fsync operations should be used for the email queue. The default is true.
- spool.corrupt
How to handle incomplete or corrupt queue (.hqf) files upon startup. The default action is
unlink
. But it is also possible torename
those (appending .bad) orignore
to do nothing.
- spool.rate
Specify the rate (files/seconds) of which messages will be spooled in from disk. This (in combination with
spool.threads.loader.wait
) allows you to reduce the load casued by spool in while receiving new messages. The default is no limitation.
3.1.3. Threads directives
3.1.3.1. Scripts
Custom script thread pools allows you to control on which script thread pools a script / hook is executed.
- threads.scripts[].id
The name of the script thread pool
- threads.scripts[].count
The number of scripts threads running a hooks such as
servers[].phases.eod.hook
. The default is 32.
- threads.scripts[].priority
The priority (nice value) of the thread. The less nice it is the higher priority (-20 to 19). The default is 0.
Note
Don’t forget to update this value as well
environment.rlimit.nice
.
- threads.scripts[].concurrency
The number of concurrent hooks that may be running concurrently. The default is
threads.scripts[].count
(1:1). However using a plugin or function supporting the HSL suspend functionality more may be running concurrently using cooperative multitasking (M:N).
- threads.scripts[].stacksize
The stack size of a hook. The default is 2097152 bytes (2 MiB). This setting shouldn’t be changed without a strong reason and understanding for doing so.
3.1.3.2. Events
Custom event thread pools allows you to control on which thread pools a network event is processed.
- threads.events[].id
The name of the events thread pool
- threads.events[].count
The number of events threads running network events. The default is 4.
- threads.events[].priority
The priority (nice value) of the thread. The less nice it is the higher priority (-20 to 19). The default is 0.
Note
Don’t forget to update this value as well
environment.rlimit.nice
.
3.1.4. Other directives
- resolver.threads.event
Number of DNS resolver event pool threads. The default is 1.
- plugins[]
Load the following plugins into the MTA. The
id
andpath
properties are required. An optional config object or YAML path may be specified for the plugin’s configuration. A similar config property is also available in the running configuration.plugins: - id: test path: "/opt/halon/plugins/test.so" config: myval: true
- plugins[].config
The startup configuration. This property may be either a string or an YAML object. If this property is given as a string it must point to a YAML file on disk.
- pki.private[]
Array of private keys, possibly with X.509 certificates, for use with
servers[]
and script functions such as PKCS7, RSA, DKIM, client certificates, etc.The
id
andprivatekey
properties are required, andcertificate
is optional. The private key and certificate should have either apath
ordata
property.pki: private: - id: selfsigned certificate: data: |- -----BEGIN CERTIFICATE----- ... privatekey: data: |- -----BEGIN PRIVATE KEY----- ...
Note
It is also possible to add those to the running configuration, but for privilege separation reasons it’s normally recommended to define private keys here instead, as this startup configuration is read before the privilege drop. It is however possible to load the private key from a path in this startup configuration, and load the certificate from a path in running configuration, which allows you to softly reload the certificate when it changes, as long as the private key stays the same.
- scripting.rootpath
Enable accessing files from disk via the File class and the
scripting.files[]
path, relative from the specified root path. The default is to not allow accessing files on disk.
3.1.5. Environment directives
The default startup configuration that came with the installation package contains reasonable defaults for your platform. Some settings should however be revised.
3.1.5.1. Performance and log
Those settings are typicallt configured depending on your system and use case.
- environment.rlimit.nofile
Set the max number of open file descriptors (this changes the current process limit from system default
ulimit -n
). This value should be calculated from other limits to allow the server to allocate system resources as needed. There is no downside of specifying a value larger than needed, so there is no reason to be conservative (just make sure it fits within system limits). The most dominant values in the configuration has the biggest impact on the result, so for simplicity in most cases you may use a quite simple formula like this.The sum of
servers[].concurrency.total
,queues.concurrency.total
andqueues.pooling.size
multiplying by5
and adding a fixed value of eg.10000
(to safely cover other limits such a script thread usage and DNS etc). Even if this value comes out twice or more than what is actual needed it should be used as a safety margin.((1000 + 10000 + 1000) * 5) + 10000 = 70000
Given the potential complexity of the calculation, which also depends on how the scripting is used, we cannot safely list all other values that needs to be encounted for. In most cases we estimate that these should all fit within the margin of the calcuation (both in multiplication factor and also the fixed value added). However, for transparency here is a few.
smtpd’s file descriptors, used for various things
The Linux kernel has two limits, the
sysctl fs.file-max
which is the upper limit of fd in the OS kernel andsysctl fs.nr_open
which controls the upper limit of ulimit command. It’s important that they are larger than theenvironment.rlimit.nofile
specificed.
- environment.rlimit.nice
Set the the highest priority (lowest nice value) a thread can request (this changes the current process limit from system default
ulimit -e
). The less nice it is the higher priority (-20 to 19). The default is0
.
- environment.syslog.mask
If you are using
systemd-journald
forsyslog()
, we strongly recommend masking awayLOG_INFO
(non-error email transaction) for performance reasons by setting this option to 191. For transaction logging you can use a module from our script library such as Elastic, libjlog or syslog directly to rsyslog. The default is no mask.
- environment.syslog.ident
The syslog identity. The default is the program name.
- environment.syslog.facility
The syslog facility. Valid options are
daemon
,mail
,user
,local0
,local1
,local2
,local3
,local4
,local5
,local6
andlocal7
. The default ismail
.
- environment.syslog.pid
Log the process ID. The default is false.
3.1.5.2. Configuration paths
Those paths can normally be left unchanged.
- environment.appconf
From where to load the running configuration. The default is /etc/halon/smtpd-app.yaml.
- environment.policyconf
From where to load the active queue policies. The default is /etc/halon/smtpd-policy.yaml.
- environment.suspendconf
From where to load the active queue suspends. The default is /etc/halon/smtpd-suspend.yaml.
- environment.deliveryconf
From where to load the active queue delivery settings. The default is /etc/halon/smtpd-delivery.yaml.
- environment.licensekey
From where to load the license. The default is
/etc/halon/license.key
. A license key may also be loaded from the environment variableHALON_LICENSEKEY
, the should contain the license file content base64 encoded. The environment variable has precedence over thisenvironment.licensekey
setting.
3.1.5.3. Other environment
Those settings should normallt be left unchanged. The default startup configuration in /opt/halon/examples that came with the installation package should contain correct parameters for your operating system or distribution. Those settings are described in the programs section.
- environment.controlsocket.path
Have the control socket on a UNIX socket. This option is mutually exclusive with the
environment.controlsocket.port
setting.
- environment.controlsocket.owner
This option is only supported with the
environment.controlsocket.path
setting.
- environment.controlsocket.group
This option is only supported with the
environment.controlsocket.path
setting.
- environment.controlsocket.chmod
This option is only supported with the
environment.controlsocket.path
setting.
- environment.controlsocket.port
Have the control socket on a TCP port. This option is mutually exclusive with the
environment.controlsocket.path
setting.
- environment.controlsocket.address
Have the control socket listen on a specific IP address. This option is only supported with the
environment.controlsocket.port
setting.
- environment.controlsocket.backlog
The kernel connection backlog. The default is the system default.
- environment.privdrop.user
The user to run the process as.
- environment.privdrop.group
The group to run the process as.
- environment.publicsuffix
The path to a publicsuffix list (publicsuffix.org).
- environment.umask
Change the umask for file creation. The default is often 022.
- environment.uuid.version
There are two supported methods of UUID generation; time based version 1 and random version 4. The default is 1 and it requires safe UUIDs to be generated (which is tested for upon smtpd startup). Safe in this context means it has to be thread-safe; assisted by the operating system/implementation (either by a syscall, a daemon such as uuidd or file locking). Depending on the environment the prefered way may differ. If you cannot use any of these, we also offer version 4 which is random based. The probably of collision of random UUIDs are so low they should be considered safe.