Skip to content

Settings Overview

chrisholloway5 edited this page Sep 8, 2026 · 3 revisions

Settings Overview

This page began as a chapter of the 6.2.10 manual and has been corrected for 6.2.24, then checked and extended against the 6.2.28 source on 8 September 2026. The Control Panel's pages are grouped differently now, so the paths below use today's groups; the TLS ports 465, 993 and 995 exist only after you create them on the TCP/IP ports page (a fresh install seeds 25, 587, 110 and 143); and everything added since 6.2.10 is in Changes-Since-6210. Where a value here disagrees with the Settings Reference, which is generated from the code, the reference is right.

Everything here is also editable in the Control Panel, which offers to restart the service when a change requires it. File: Bin\hMailServer.INI, section [Settings]. Values shown are the defaults.

This page is the tour. The Settings Reference is the map: every one of the 238 [Settings] keys, plus [Database], [Directories], [Security], [GUILanguages], [SendingLimits] and [LDAP], each with its default, its validation, the file that consumes it and when it is read.

How the file is found, read and applied

The file name is literally hMailServer.ini, and it lives in the server's Bin directory — HKLM\SOFTWARE\hMailServer\InstallLocation + \Bin when that registry value exists, otherwise the directory the running hMailServer.exe sits in. COM Application.InitializationFile returns the resolved path, which is the quickest way to be certain which file you are editing.

Three things write it, and one thing reads it into the running server:

flowchart LR
    CP["Control Panel"] -->|"writes directly when it is on the server"| F["hMailServer.INI in the Bin directory"]
    CP -->|"COM SetIniSetting when it is remote"| COM["COM Settings object"]
    COM --> F
    COM --> DB[("hm_inisettings mirror")]
    ED["A text editor"] --> F
    INST["The installer"] --> F
    F --> LS["IniFileSettings::LoadSettings"]
    DB --> LS
    LS --> M["About 200 typed members"]
    M --> CONS["Consumers: listeners, tasks, protocol handlers"]
Loading

LoadSettings reads every key in one pass. It runs in five situations: first thing in Application::InitInstance with the file alone; again in the same start once the database is open, this time with the hm_inisettings overlay; on Application.Reinitialize; on every COM Settings.Directories get and on Database.CreateInternalDatabase; and on hMailServer.exe /Register, so the service account settings reach the SCM.

The house rule is: persist now, apply on the next start. Nothing in SetIniSetting re-runs LoadSettings. So whichever way you make a change, the reliable sequence is edit, then restart the service (or call COM Application.Reinitialize, which is what the regression suite does). A handful of things are exceptions and are read live: [Settings] UseLanguage, [Directories] LogFolder (once, then cached), the whole of [SendingLimits]/[SendingLimitsOverrides] and [LDAP] (re-read when the file's timestamp changes, checked at most every two seconds), and the separate hMailServerApiKeys.ini (re-read on every REST request).

Two read mechanics that catch people out, both consequences of the Windows profile API:

  • A key that is present but empty — LogDeleteDays= — reads as 0, not as "absent". Deleting the line is how you return a setting to its default.
  • Booleans are == 1. Only the literal 1 is true; 2, yes and true are all false. A value such as 5x reads as 5, and x reads as 0.

The database mirror, and who wins

Since schema 6011 the [Settings] section is mirrored into a table, hm_inisettings, so that a remote Control Panel can change settings and so that settings are included in backups. Only [Settings] is mirrored — [Database], [Directories], [Security] and [GUILanguages] always come from the file alone. The reconciliation runs at every start:

flowchart TD
    A["For each key in the file"] --> B{"Is there a row for it?"}
    B -- no --> C["Insert a row. The file value wins."]
    B -- yes --> D{"Has the file changed since the two last agreed?"}
    D -- yes --> E{"Has the row also changed?"}
    E -- yes --> F["HM5804 conflict reported. The file wins, and the row is updated."]
    E -- no --> G["The file wins, and the row is updated."]
    D -- no --> H{"Has the row changed?"}
    H -- yes --> I["Write the row's value into the file"]
    I --> J{"Did the write succeed?"}
    J -- yes --> K["Both now agree on the row's value"]
    J -- no --> L["HM5803. The file value is used for this run."]
    H -- no --> M["They agree. Nothing to do."]
    N["For each row whose key is gone from the file"] --> O{"Did the row still agree with the last file value?"}
    O -- yes --> P["The deletion is the newer fact. Delete the row."]
    O -- no --> Q["A database-side edit or a restore. Write it back into the file."]
Loading

Read that as one sentence: the file wins when the file changed; the database wins when only the database changed; and deleting a line really does delete the setting. If the table cannot be read at all, the server runs on the file alone and reports HM5802, and your settings stop being included in backups until it is fixed.


Transport security and authentication

MtaStsEnabled=1               ; honour recipient MTA-STS policies when sending
DaneEnforcementEnabled=1      ; honour recipient DANE/TLSA records when sending
DnssecValidationEnabled=1     ; validate DNSSEC for DANE and SPF/DKIM/DMARC lookups
DnssecTrustAnchors=           ; override root trust anchors ("tag alg digesttype hex;...")
ArcSealingEnabled=0           ; add ARC seals when forwarding (uses the domain's DKIM key)
TlsRptFromAddress=            ; sender for daily TLS-RPT reports (empty = disabled)
TlsRptOrganizationName=hMailServer
Key Default When it takes effect What you see when it is wrong
MtaStsEnabled 1 Next start This is the outbound switch — whether this server honours other people's policies. It has nothing to do with publishing your own; that is MtaStsHostingEnabled below, and the two are confused constantly.
DaneEnforcementEnabled 1 Next start The key is not DaneEnabled, although the getter is. Spelling it DaneEnabled in the file leaves the default in force and reports nothing.
DnssecValidationEnabled 1 Next start With it off, DANE has nothing trustworthy to stand on. Diagnostics.DnssecChainStatus(name, type) over COM answers 0 secure, 1 insecure, 2 bogus for one name, which is the fastest way to tell a broken zone from a broken setting.
DnssecTrustAnchors empty Next start Empty means the built-in IANA root keys. A malformed override does not fall back — it replaces them, so every validation fails.
ArcSealingEnabled 0 Next start Seals use the domain's DKIM key, so a domain with DKIM off is silently not sealed.
TlsRptFromAddress empty Next start Empty is not "off" in the sense you might expect: statistics are still collected, they are simply never sent. Set the address and reports start the next day.

TlsRptOrganizationName and DmarcRptOrganizationName both default to hMailServer and appear in the reports recipients read, so they are worth setting to your own name.

Automatic certificates (Let's Encrypt)

AcmeEnabled=0                 ; issue and renew certificates automatically
AcmeContactEmail=             ; expiry notices from the CA
AcmeDomains=                  ; comma-separated host names for the certificate
AcmeDirectoryUrl=https://acme-v02.api.letsencrypt.org/directory
AcmeHttpPort=80               ; port for http-01 challenges
AcmeReuseKey=1                ; keep the same key across renewals (keeps TLSA records valid)

The Control Panel's ACME page, where automatic certificate issuance is switched on and the certificate's host names are listed

Key Default What you see when it is wrong
AcmeEnabled 0 Nothing happens until this is 1.
AcmeDomains empty AcmeEnabled=1 with no domains logs ACME: No domains configured and issuance never runs. This is the single most common "ACME does nothing" cause.
AcmeContactEmail empty Optional — the account is simply created without a contact, so the CA cannot warn you about expiry.
AcmeHttpPort 80 The http-01 challenge must be reachable from the internet on this port. If the web-services HTTP listener owns the same port it serves the challenges; otherwise a transient challenge server is started for the duration, which fails if something else holds the port.
AcmeReuseKey 1 Setting it to 0 changes the public key at every renewal, which invalidates any 3 1 1 TLSA record you published from /api/v1/tlsa.

Certificates are written as fullchain.pem and privkey.pem (plus account.key) in AcmeCertificateDirectory, which defaults to <DataFolder>\ACME. After a successful issuance the server logs ACME: Restarting servers to load the new certificate. and reinitialises itself — which is how a REST or web-services HTTPS listener that could not start for want of a certificate comes up without anyone touching it.

Web services (MTA-STS hosting, client autoconfiguration)

WebServicesHttpPort=0         ; 80 to enable
WebServicesHttpsPort=0        ; 443 to enable (uses the ACME certificate if none is set)
WebServicesBindAddress=0.0.0.0
MtaStsHostingEnabled=1        ; serve https://mta-sts.<domain>/.well-known/mta-sts.txt
MtaStsPolicyMode=enforce      ; enforce, testing or none
MtaStsPolicyMaxAge=604800
MtaStsPolicyMx=               ; override mx patterns (default: the domain's live MX records)
AutoconfigEnabled=1           ; Thunderbird autoconfig + Outlook autodiscover
AutoconfigClientHost=         ; host name clients connect to (default: the server's host name)

Per domain, point mta-sts.<domain>, autoconfig.<domain> and autodiscover.<domain> at this server, and include them in AcmeDomains for HTTPS.

Both feature switches above default to on and do nothing. MtaStsHostingEnabled=1 and AutoconfigEnabled=1 are the shipped defaults, but neither has a listener until WebServicesHttpPort or WebServicesHttpsPort is set — and MTA-STS specifically needs the HTTPS one, because the specification requires the policy to be fetched over HTTPS. The server says so at every service start:

WebServices: these features are enabled but unreachable, because no web services listener is configured: ... Nothing answers those URLs until WebServicesHttpPort and/or WebServicesHttpsPort is set...

Key Default What you see when it is wrong
WebServicesHttpPort 0 0 means no listener.
WebServicesHttpsPort 0 Set, but with no certificate of its own and no ACME certificate on disk, the HTTPS listener is quietly kept down while the HTTP one still runs: No TLS certificate available yet. The HTTPS listener is disabled until a certificate exists (enable ACME or set WebServicesCertificateFile).
MtaStsPolicyMode enforce Anything that is not enforce, testing or none is read as enforce — a typo makes your policy stricter, not looser.
MtaStsPolicyMaxAge 604800 Clamped to 86400–31557600 whatever you write.
MtaStsPolicyMx empty Empty means the domain's live MX records, looked up and cached for an hour. Set it only when the live records are not what you want published.
AutoconfigClientHost empty Empty means the server's own host name. This is the name written into every autoconfig, autodiscover and .mobileconfig answer and used as the SRV target from /api/v1/srv, so getting it wrong sends every new client to the wrong host.

The Apple .mobileconfig profile is served over HTTPS only. A plain-HTTP request is redirected to the HTTPS listener when one is configured, and refused with a reason when it is not; X-Forwarded-Proto: https from a TLS-terminating proxy counts as HTTPS.

Administration and monitoring

RestApiPort=0                 ; REST admin API (administrator password over HTTP Basic, or an API key as Bearer); new in 6.2.28: also the self-service portal at /portal
RestApiBindAddress=127.0.0.1  ; TLS is required unless bound to 127.0.0.1, localhost or ::1
RestApiCertificateFile=       ; PEM; falls back to the ACME certificate
RestApiPrivateKeyFile=
MetricsServerPort=0           ; Prometheus metrics endpoint (/metrics) + health probes
MetricsServerBindAddress=127.0.0.1
LogDeleteDays=0               ; prune date-stamped logs older than N days (0 = keep all)
ShutdownDrainSeconds=0        ; on stop, wait up to N seconds for sessions to finish
MessageStoreFsync=0           ; force each message to physical disk before acknowledging
MessageStoreConsistencyCheck=0; periodically cross-check message rows against files
ManageSieveServerPort=0       ; ManageSieve (RFC 5804); standard port 4190
ManageSieveServerBindAddress=127.0.0.1
JsonLogging=0                 ; write logs as JSON lines

The Control Panel's API and monitoring page, which edits the REST listener, the metrics listener, OpenTelemetry, ManageSieve and the Windows event log

Key Default When it takes effect What you see when it is wrong
RestApiPort 0 Restart 0 means no listener. Set, it still refuses to start when [Security] AdministratorPassword is empty, saying so in the log. See The REST API.
RestApiBindAddress 127.0.0.1 Restart Any address but 127.0.0.1, localhost or ::1 requires a certificate; without one the listener refuses to start rather than serving management traffic in clear.
RestApiCertificateFile / RestApiPrivateKeyFile empty Restart Empty is not necessarily "no TLS": if <AcmeCertificateDirectory>\fullchain.pem and privkey.pem both exist they are used, logged as RestApi: Using the ACME certificate for HTTPS.
MetricsServerPort 0 Restart 0 means no listener — no /metrics, and no /livez, /readyz or /healthz either.
MetricsServerBindAddress 127.0.0.1 Restart On a non-loopback bind with no credential configured, /metrics answers 503 with a body naming MetricsServerAuthToken and MetricsServerAuthUsername/Password. The health probes keep answering; they are never authenticated. This is the usual cause of "Prometheus gets a 503 but the server is fine".
LogDeleteDays 0 Next start 0 keeps every date-stamped log forever. On a busy server that is the disk filling up slowly.
ShutdownDrainSeconds 0 At stop 0 means sessions are cut at stop. Raise it before a planned restart if you would rather in-flight deliveries finish.
MessageStoreFsync 0 Next start 1 forces each spool file to physical disk before the 250 — durable, and measurably slower. A failed flush refuses the message rather than acknowledging it.
MessageStoreConsistencyCheck 0 Next start 1 starts a read-only scheduled walk comparing rows to files and publishes hmailserver_messagestore_missing_files. Nothing is repaired; it only tells you.
ManageSieveServerPort 0 Restart 0 means no ManageSieve. The standard port is 4190; the bind defaults to loopback, so a remote Sieve client sees nothing until you change both.
JsonLogging 0 Next start Changes the log line format. Anything parsing the old format will stop working the moment this is flipped.

Automatic updates (new in 6.2.28)

UpdateCheckEnabled=0          ; read the project's release feed every UpdateCheckHours
UpdateCheckHours=24
UpdateChannel=stable          ; or prerelease
UpdateFeedUrl=                ; empty = this project's GitHub releases; set for a mirror
HttpProxy=                    ; forward proxy (host:port, or [ipv6]:port) for the web requests this server
                              ; makes as a client; empty = direct; no proxy credentials
UpdateAutoDownload=0          ; fetch and Sigstore-verify the installer as soon as one is found
UpdateWindow=                 ; when a verified update may be applied unattended, e.g. Sun 03:00 (empty = never)
UpdateBackupBeforeApply=1     ; back up to the configured destination before an unattended apply
UpdateRequireAuthenticode=0   ; also require an Authenticode signature on the installer
UpdateServiceWaitSeconds=180  ; how long the helper waits for the service before rolling back

The trust settings (UpdateTrustRootsFile, UpdateLogPublicKeyFile, UpdateSigningIdentity, UpdateSigningIssuer, UpdateSourceRepository) default to the public Sigstore instance and this repository's release workflow; see the Settings Reference.

Key Default What you see when it is wrong
UpdateCheckEnabled 0 The scheduled task exists either way and runs every fifteen minutes, but does nothing at all until this is 1. An on-demand check over COM or POST /api/v1/update/check ignores this key and reads the feed anyway.
UpdateCheckHours 24 Values below 1 are read as 1. The Control Panel's label says "1 to 168" but the server applies no ceiling.
UpdateChannel stable prerelease (or pre-release) makes pre-releases eligible and switches the default feed to the last ten releases. Any other value is the stable channel.
UpdateFeedUrl empty A mirror must be https, unless it is on this machine: the server's web client refuses plain http to anything but a loopback address.
UpdateAutoDownload 0 Only downloads and verifies. It never runs anything.
UpdateWindow empty Empty means nothing is ever applied unattended. An unparsable value is logged once per process and, again, nothing is applied. Forms: 03:00, Sun 03:00, Sat,Sun 02:00-05:00; days are English three-letter names, times local 24-hour, and a range crossing midnight belongs to the day it starts on.
UpdateBackupBeforeApply 1 With no backup destination configured, this cancels the unattended apply rather than applying without a backup.
UpdateServiceWaitSeconds 180 Values below 5 are read as 5. This is how long the helper gives the service to come back before it reinstalls the previous version.

HttpProxy is not update-only: it carries every request the server makes through its own web client - the release feed and its downloads, and the OAuth2 JWKS and token-introspection fetches. CONNECT for an https target, with the certificate checked exactly as on a direct connection; the absolute URL in the request line for plain http. A value without a port is an error, not a quiet direct connection. Its editor is beside the feed URL on the Control Panel's Updates card.

ACME issuance and the outbound-relay XOAUTH2 token request build their own connections and do not go through it.

Secret protection and least privilege

ProtectStoredSecretsWithDPAPI=1   ; machine-scoped DPAPI for reversible stored secrets
ServiceAccountName=               ; run the service as this account (empty = LocalSystem)
ServiceAccountPassword=           ; empty for virtual/managed accounts

The Control Panel's advanced hardening page on a 6.2.10-era build, subtitled "Lower-level hMailServer.INI Settings knobs that are not exposed on the other pages", with its Greylisting card (record expiration interval 240 minutes) and its Scanner timeouts card (SpamAssassin 30 to 90 seconds, ClamAV 15 to 90) in view

The page is called Server limits & expert settings in 6.2.28, and the secret-protection and service-account rows quoted above are further down it than this capture reaches.

Key Default What you see when it is wrong
ProtectStoredSecretsWithDPAPI 1 Protection is machine-scoped (CRYPTPROTECT_LOCAL_MACHINE). A DPAPI:-prefixed value copied to a different machine cannot be decrypted there — which matters when you restore a configuration onto new hardware. Legacy Blowfish values stay readable either way, and if DPAPI fails the server falls back to Blowfish rather than losing a secret.
ServiceAccountName empty (LocalSystem) Applied only by hMailServer.exe /Register. Editing the key alone changes nothing; the service keeps logging on as whatever the SCM already records.
ServiceAccountPassword empty Leave empty for virtual and managed accounts.

A related trap that belongs here: [Database] Passwordencryption is read with a lower-case e but written by the server as PasswordEncryption. Both work, because the Windows profile API is case-insensitive about key names — but the hm_inisettings mirror matches names case-sensitively, so a case-only rename produces one noisy start that heals itself.

Deliverability and SMTP standards

SRSEnabled=0                         ; Sender Rewriting Scheme for forwarded mail
SRSSecret=                           ; HMAC secret for SRS (required when SRSEnabled=1)
MaxSubmissionsPerIPPerMinute=0       ; cap MAIL FROM per source IP per minute (0 = off)
MaxOutboundPerDestinationPerMinute=0 ; cap outbound per destination domain (0 = off)
Key Default What you see when it is wrong
SRSEnabled 0 SRSEnabled=1 with an empty SRSSecret does nothing and says nothing. The rewriter returns an empty address for an empty secret, and the forwarder then leaves the envelope alone. Forwarded mail keeps failing the recipient's SPF check and nothing in the log points at the cause. Set both keys or neither.
SRSSecret empty Changing it invalidates addresses already in flight, so bounces for recently forwarded mail stop reversing.
MaxSubmissionsPerIPPerMinute 0 Over the cap, submissions get 421 Too many messages from your IP. A shared NAT counts as one IP.
MaxOutboundPerDestinationPerMinute 0 Over the cap, delivery to that destination domain is deferred, not refused — so the queue grows rather than bouncing.

PIPELINING, SMTPUTF8/EAI (RFC 6531/6532), ENHANCEDSTATUSCODES (RFC 2034), DSN (RFC 3461), CHUNKING/BDAT (RFC 3030) and 8BITMIME are advertised automatically and need no configuration. Legacy HELO sessions keep the classic non-enhanced replies.

"I set it and nothing happened"

Nearly every report of this shape is one of seven things. Work down the list:

flowchart TD
    A["A setting appears to do nothing"] --> B{"Did the service restart after the edit?"}
    B -- no --> B1["Restart, or call Application.Reinitialize. Persist now, apply at next start."]
    B -- yes --> C{"Is the value literally 1 for a boolean?"}
    C -- no --> C1["Only 1 is true. 2, yes and true all read as false."]
    C -- yes --> D{"Is the key spelled exactly as the code reads it?"}
    D -- no --> D1["An unknown key is silently the default. Check the Settings Reference: the key name and the getter name differ for several settings."]
    D -- yes --> E{"Does the feature need a listener that is still off?"}
    E -- yes --> E1["MTA-STS hosting and autoconfig need a WebServices port. IndexerFullText needs COM Settings.MessageIndexing. The startup log names the unreachable ones."]
    E -- no --> F{"Does it need a companion value?"}
    F -- yes --> F1["SRSEnabled needs SRSSecret. ACME needs AcmeDomains. Outbound XOAUTH2 needs a token URL or a fixed token. Metrics off loopback needs a credential."]
    F -- no --> G{"Was it edited on a remote Control Panel while the database mirror disagreed?"}
    G -- yes --> G1["Check the log for HM5802, HM5803 and HM5804."]
    G -- no --> H["Read the key's row in the Settings Reference: its validation may be clamping or rejecting the value."]
Loading

Values out of range do not usually fail loudly. They are clamped, or reset to the default with a numbered error in the application log: HM5528 (PreferredHashAlgorithm), HM5561 (the three PasswordHash* work factors), HM5562 (SpamAssassinUser with a control character), HM5780 (an unusable CalDavRedirectUrl/CardDavRedirectUrl), HM6210 (DmarcRptSchemaVersion), HM4401 (an invalid DNSServer) and HM6119 (SimulateDatabaseFailureFor — a test-only key that is reported as High on every start while it is set, because a server running with it is deliberately broken).

Worked example: switching the REST API on, from nothing

; Bin\hMailServer.INI

[Security]
AdministratorPassword=<set this from the Control Panel, not by hand>

[Settings]
RestApiPort=8443
RestApiBindAddress=127.0.0.1

Restart the service, then read the application log. One of these three lines will be there:

RestApi: Listening on 127.0.0.1:8443 (http, loopback only).
RestApi: Refusing to start - the administrator password is not set.
RestApi: Refusing to start - TLS certificate is required unless bound to 127.0.0.1 or ::1. Set RestApiCertificateFile and RestApiPrivateKeyFile.

Then confirm it end to end:

curl -u Administrator:your-admin-password http://127.0.0.1:8443/api/v1/status
{"version":"6.2.28","state":3,"processedMessages":10412,"spamMessages":318,
 "virusesRemoved":4,"sessions":{"smtp":2,"imap":11,"pop3":0}}

To reach it from another machine you must add TLS, because the listener refuses a non-loopback bind without it:

RestApiBindAddress=0.0.0.0
RestApiCertificateFile=C:\hMailServer\Certificates\fullchain.pem
RestApiPrivateKeyFile=C:\hMailServer\Certificates\privkey.pem

or leave both file keys empty and let ACME provide them — the listener picks up <DataFolder>\ACME\fullchain.pem and privkey.pem on the restart that ACME triggers after its first successful issuance.

Where to go next

  • Settings Reference — every key, with its default, validation, consumer and timing.
  • The REST API — the listener this page switches on, in detail.
  • APIs Reference — the four HTTP listeners, every route, and the COM object model.
  • Security Hardening — which of these keys matter for a server on the public internet.

Clone this wiki locally