-
Notifications
You must be signed in to change notification settings - Fork 3
Encryption and Certificates
This page began as a chapter of the 6.2.10 manual and has been corrected against the code of 6.2.28. 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.
Mail must be encrypted in transit. You need a TLS certificate for
mail.yourcompany.com, you need at least one listener configured to use it, and — for
mail you send — you need the three outbound policies in §9.6, all of which are already
on.
A fresh install offers no TLS at all. The four seeded listeners (25, 587, 110, 143) are created with connection security None and no certificate (
DBScripts/CreateTables*.sql, thehm_tcpipportsseed rows), andSTARTTLSis advertised only on a port whose security is STARTTLS-optional or STARTTLS-required (SMTPConnection.cpp, the250-STARTTLSline). Getting a certificate is step one; §9.5 is step two, and without it nothing changes.
Start with the summary if the server is already running. TLS & certificates → Transport encryption overview is a read-only page that answers "is anything on this server carrying a password in the clear, and will it still serve a valid certificate next month". It exists because the interesting states only appear in the combination of four editors: a port set to use TLS with no certificate assigned does not fall back to plaintext, it fails to start; the AEAD-ONLY cipher preset leaves TLS 1.0 and 1.1 advertised with no suite they can use; a certificate that expired last week is listed exactly like one that has not (
Tools/ControlPanel/Views/TlsOverviewView.cs).
hMailServer has an ACME v2 client built in (RFC 8555, http-01 challenge). It obtains
a free certificate, installs it, assigns it to your TLS ports, renews it before expiry and
reloads it — with no restart and no scheduled task.
Open TLS & certificates → Certificates (ACME):

Or in hMailServer.INI:
[Settings]
AcmeEnabled=1
AcmeContactEmail=you@yourcompany.com
AcmeDomains=mail.yourcompany.com,mta-sts.yourcompany.com,autoconfig.yourcompany.com
AcmeHttpPort=80
AcmeReuseKey=1| Setting | Default | Notes |
|---|---|---|
AcmeEnabled |
0 | Off. With it off, AcmeRenewalTask::DoWork returns immediately and nothing on this page can fire |
AcmeDirectoryUrl |
https://acme-v02.api.letsencrypt.org/directory |
Any RFC 8555 CA. Point it at the staging directory while you are testing |
AcmeContactEmail |
empty | Where the CA sends expiry notices |
AcmeDomains |
empty | Comma separated. Every name becomes a SAN on one certificate |
AcmeHttpPort |
80 | Where the http-01 challenge is answered |
AcmeCertificateDirectory |
empty | Falls back to <data directory>\ACME
|
AcmeReuseKey |
1 | Keep the same private key across renewals |
Requirements: port 80 must be reachable from the internet — that is how the CA
verifies you own the name — and each name in AcmeDomains must already resolve to this
server.
AcmeReuseKey=1matters if you use DANE. Keeping the same private key across renewals keeps your published3 1 1TLSA records valid. Leave it on. When it is on and aprivkey.pemalready exists, the log saysACME: Reusing the existing certificate key (keeps published TLSA records valid).Otherwise a fresh 2048-bit RSA key is generated.
sequenceDiagram
participant S as hMailServer
participant CA as ACME directory
participant L as Challenge listener on port 80
S->>CA: GET /directory
CA-->>S: newNonce, newAccount, newOrder, renewalInfo
S->>S: Load or create the ACME account key
S->>CA: POST newAccount, JWS signed with the account key
CA-->>S: 200 or 201 with the account URL
S->>CA: POST newOrder with every configured name
CA-->>S: 201, order URL, finalize URL, authorization URLs
loop one per name
S->>CA: POST-as-GET the authorization
CA-->>S: challenges, including http-01 with a token
S->>L: publish token and key authorization
S->>CA: POST the challenge, empty JSON object
loop up to 30 times, 2 seconds apart
S->>CA: POST-as-GET the authorization
CA-->>S: pending, valid or invalid
end
end
S->>CA: POST finalize with a CSR carrying every name as a SAN
loop up to 30 times, 2 seconds apart
S->>CA: POST-as-GET the order
CA-->>S: processing, valid or invalid
end
S->>CA: POST-as-GET the certificate URL
CA-->>S: the PEM chain
S->>S: Write privkey.pem.new and fullchain.pem.new,<br/>then rename key first, certificate second
S->>S: Create or update the certificate record,<br/>assign it to TLS ports that have none,<br/>reinitialise the servers
The challenge is answered by whichever listener owns port 80. If the built-in web services
server is already listening there, it serves /.well-known/acme-challenge/<token> from a
process-wide store and no second listener is started; otherwise a transient
AcmeChallengeServer binds the port for the duration of the validation and closes it
again.
The renewal task runs once an hour, plus once at startup
(Application.cpp, where AcmeRenewalTask is scheduled). Each run asks one question:
should this certificate be renewed now?
flowchart TD
A["Hourly renewal task"] --> B{"AcmeEnabled?"}
B -- no --> STOP["Nothing happens"]
B -- yes --> C{"Can fullchain.pem<br/>be read for its dates?"}
C -- no --> REQ["Request a certificate"]
C -- yes --> D{"Does the CA publish<br/>renewalInfo, ARI?"}
D -- yes --> E["Ask the CA for a suggested window,<br/>pick a stable point inside it"]
E --> F["Clamp to at most<br/>24 hours before expiry"]
F --> G{"Is that moment past?"}
D -- no --> H["renewAt = notBefore + two thirds<br/>of the certificate lifetime"]
H --> I["Clamp to at most<br/>24 hours before expiry"]
I --> G
G -- no --> J["Deploy a certificate that was issued<br/>but never recorded, if there is one"]
G -- yes --> REQ
REQ --> K{"Issuance succeeded?"}
K -- yes --> DONE["Deployed, servers reinitialised"]
K -- no --> L{"How long until<br/>the current certificate expires?"}
L -- "more than 7 days" --> LOG["One application-log line"]
L -- "7 days or fewer" --> ERR["HM5992 reported as an error"]
L -- "already expired" --> ERR
The renewal arithmetic is a fraction of the lifetime, not a fixed number of days, and that is the point. Thirty days was right while certificates lasted ninety; Let's Encrypt defaults to 64 days from February 2027, the maximum falls to 100 days in March 2027 and to 47 in March 2029 — and a fixed 30-day window against a 47-day certificate starts renewing seventeen days after issuance and never stops, which is how an operator gets rate-limited by their own CA.
So, from AcmeClient::GetRenewalTime:
lifetime = notAfter - notBefore (90 days assumed if notBefore is unreadable)
renewAt = notBefore + (lifetime / 3) * 2
renewAt = min(renewAt, notAfter - 24 hours) floor, so a failure gets another attempt
renewAt = max(renewAt, notBefore) a certificate shorter than a day renews at once
For a 90-day certificate that is 60 days in, 30 days of margin. For a 47-day one it is 31 days in, 16 days of margin. Whatever the lifetime, a third of it remains to notice a failure.
When the CA implements ARI (RFC 9773, a renewalInfo endpoint in its directory) its
opinion wins, with two guards: the suggested moment is never later than 24 hours before
expiry, and the point chosen within the CA's window is derived from the certificate's own
ARI identifier rather than from a random number — so this server picks the same moment
every hour for this certificate, and a different one from the server next door, which is
what §4.2 of the RFC is actually asking for.
<data directory>\ACME\
├── account.key the ACME account key; created on first use, never rotated
├── privkey.pem the certificate private key; reused across renewals by default
├── fullchain.pem the leaf certificate followed by the issuer chain
├── privkey.pem.new transient, during a renewal only
└── fullchain.pem.new transient, during a renewal only
The two .new files exist because the order of the writes matters. The key is written,
then the certificate, then the key is renamed into place first and the certificate
second. A half-finished renewal therefore never leaves a new key beside an old
certificate — a mismatched pair is not a degraded listener, it is a listener that refuses
to start, which would be a worse outcome than the expiry it was avoiding. If the second
rename fails anyway, that is error HM5993 and the message names both files and says the
fix is manual.
A successful run:
ACME: Certificate is missing or due for renewal. Requesting a new certificate.
ACME: Reusing the existing certificate key (keeps published TLSA records valid).
ACME: Certificate issued successfully: C:\Program Files\hMailServer\Data\ACME\fullchain.pem
ACME: Assigned the certificate to port 587 (no certificate was configured).
ACME: Restarting servers to load the new certificate.
ACME: DANE TLSA record for this certificate: _25._tcp.<mx-host>. IN TLSA 3 1 1 3b1f…
A failed one always names the failing step, then says so plainly:
ACME: Authorization for mail.example.com offers no http-01 challenge. Response: {…}
ACME: Certificate renewal FAILED. The failing step is in the lines above; the next attempt
is in one hour. The certificate currently installed has not been changed.
| Symptom in the log | Almost always means |
|---|---|
Failed to start the http-01 challenge listener. Is the port in use? |
Something else owns port 80 — IIS, another web server, or a WebServicesHttpPort set to a different port |
Challenge validation failed for <name> |
The name does not resolve to this server, or port 80 is not reachable from the internet |
Could not read the authorization (HTTP status 0) |
Outbound HTTPS to the CA is blocked |
No domains configured |
AcmeDomains is empty |
Timed out waiting for challenge validation |
The CA reached something, but not this server — check for a proxy or a stale DNS record |
If you have a certificate from another CA, go to TLS & certificates → SSL certificates, add it, then assign it to your TLS ports under Connections & protocols → TCP/IP ports.

- Both files are PEM. The certificate file must contain the leaf and the issuer chain — hMailServer loads it twice, once as the certificate and once as the chain, and reports each failure separately.
- An encrypted private key is supported: give the certificate its passphrase in the same dialog. Different certificates may have different passphrases, which is why there is no single global one.
- A certificate is not checked for validity dates when it is served, only when it is verified — so an expired certificate loads without complaint and every client then refuses to connect, with no symptom on the server side. hMailServer therefore checks the dates itself at startup and reports HM5991 for a certificate that has expired or one that is not valid yet (which usually means this server's clock is wrong). It reports and carries on rather than refusing to listen, because a mail server that stops answering the moment a certificate expires is worse than one that answers with a stale certificate.
| Code | Message | Meaning |
|---|---|---|
| HM5113 |
Failed to load certificate file / …certificate chain… / …private key file
|
The path, the format or the passphrase is wrong. The port does not start |
| HM5113 | Error initializing SSL. Certificate not set. |
A TLS port has no certificate assigned |
| HM5991 | The TLS certificate … expired on … |
The listener started and is offering an expired certificate |
| HM5991 | The TLS certificate … is not valid until … |
Future-dated certificate, or the server clock is behind |
| HM5603 | Unable to enable Diffie-Hellman key agreement |
dh2048.pem is missing from the Bin directory |
TLS & certificates → SSL/TLS controls what is negotiable, for this server's own listeners and for the connections it makes when delivering.

Two things about this 6.2.10-era capture. Its Auto-ban tab is now a page of its own
under Access & abuse protection, so today this page carries Protocol versions,
Ciphers and the Session resumption tab of the next section. And all four protocol
versions are ticked in the shot because that build was configured that way; the shipped
default is SslVersions 24 — TLS 1.2 and 1.3 only, as the table below says.
| Setting | Storage | Default | What it does |
|---|---|---|---|
| TLS 1.0 / 1.1 / 1.2 / 1.3 |
SslVersions bitmask |
24 = 8 + 16, i.e. TLS 1.2 and TLS 1.3 only | SSLv2 and SSLv3 cannot be enabled at all |
| Cipher list for TLS 1.2 and below | SslCipherList |
a long OpenSSL list ending …:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
|
Applied with SSL_CTX_set_cipher_list. Does not touch TLS 1.3
|
| TLS 1.3 cipher suites |
TlsCipherSuites13 (INI) |
empty = OpenSSL's defaults | RFC 8446 names, colon separated, e.g. TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
|
| Key exchange groups |
TlsKeyExchangeGroups (INI) |
X25519MLKEM768:SecP256r1MLKEM768:X25519:secp384r1:secp256r1 |
Two post-quantum hybrids first, then the classical curves |
| Prefer server cipher order |
TlsOptions bit 2 |
off | SSL_OP_CIPHER_SERVER_PREFERENCE |
| Prioritize ChaCha20-Poly1305 |
TlsOptions bit 4 |
off | Only effective with server preference on and TLS 1.2 or 1.3 enabled |
| Verify remote certificates when delivering | VerifyRemoteSslCertificate |
on | Applies to outbound SMTP |
The defaults — TLS 1.2 and 1.3 only — are correct. Do not re-enable TLS 1.0 or 1.1 unless a specific ancient client forces you to, and then plan to replace that client.
The post-quantum default is real and it applies in both directions. The shipped group
list offers X25519MLKEM768 and SecP256r1MLKEM768 — ML-KEM hybrids — ahead of X25519 and
the NIST curves, so a modern peer negotiates a hybrid and an older one falls back with no
loss. The same list is installed on the client context, so mail this server sends uses it
too.
Six failure modes here are handled by falling back loudly rather than quietly, and each has its own error number:
| Code | Condition | What the server does instead |
|---|---|---|
| HM5990 | No TLS version is enabled at all | Falls back to TLS 1.2 + 1.3, because "TLS but no version of it" cannot be meant — turning TLS off is done per port |
| HM5720 |
TlsKeyExchangeGroups is rejected by OpenSSL, or would leave no group enabled |
Falls back to secp384r1:x25519:secp256r1
|
| HM5721 | Even that fallback is rejected | Reports loudly; OpenSSL's own defaults are in force |
| HM6120 |
TlsCipherSuites13 is rejected outright |
The list is not applied; OpenSSL's default TLS 1.3 suites stand |
| HM6121 | One or more names in TlsCipherSuites13 are unknown to this build |
The rest of the list is applied — a single typo would otherwise silently halve the suites offered |
| HM5511 |
SslCipherList is rejected |
Reported; the previous list stands |
AEAD-ONLY is a named preset, not an OpenSSL string: set SslCipherList to exactly
that word and the server applies a vetted forward-secret AEAD-only list (ECDHE/DHE with
AES-GCM or ChaCha20-Poly1305). It excludes every CBC-mode suite — the Lucky13
padding-oracle family — and static-RSA key exchange. The cost is stated plainly: TLS 1.2
clients that only speak CBC cannot connect, and TLS 1.0/1.1 are left with no usable
cipher at all, so do not enable those protocols alongside it. A misspelled preset name is
not silently ignored; it flows through to OpenSSL, which rejects a list containing no known
cipher, and HM5511 names the string.
Four settings, all on the SSL/TLS page's Session resumption tab. At their defaults the server makes no OpenSSL call at all for three of them, so resumption behaves exactly as stock OpenSSL does.
| Setting | Default | What a non-default value buys |
|---|---|---|
TlsSessionTicketsEnabled |
on | Off: no tickets on any TLS version. TLS 1.2 clients fall back to the in-process cache, TLS 1.3 clients get no ticket at all |
TlsSessionCacheSize |
0 = OpenSSL's cap of 20480 | A positive value replaces the cap; -1 turns the server-side cache off entirely, leaving tickets unaffected |
TlsSessionTimeoutSeconds |
0 = OpenSSL's 300 | Bounds how long a leaked resumption secret stays useful |
TlsTicketKeyRotationSeconds |
0 = one non-rotating key for the life of the process | With rotation on, a captured ticket is decryptable for at most two intervals. 86400 is a day |
Ticket-key rotation is the one worth turning on if you care about forward secrecy: without it, every ticket a listener ever issues is sealed under one key generated at startup, and that key — recovered later from a memory dump or a hibernation file — decrypts every ticket ever recorded. Rotation is process-global, so all listeners rotate together; cross-listener resumption is still refused, because each listener has its own session-ID context.
Per port, not per server, under Connections & protocols → TCP/IP ports:
| Policy | Value | Behaviour |
|---|---|---|
| Off | 0 | Never ask for a client certificate. The default, and what every port did before this existed |
| Request | 1 | Ask, verify one if offered, log the outcome, never fail the handshake. Use it to inventory which clients could survive "require" |
| Require | 2 | The handshake fails unless the client presents a certificate chaining to the port's configured CA bundle |
A verified client certificate that names an address also makes AUTH EXTERNAL
(RFC 4422 Appendix A) available on that connection — SMTP, IMAP and POP3 all advertise it
exactly when such a certificate exists, and to nobody else.
This is per port on purpose: the population connecting to a partner-relay SMTP port and the population connecting to the public IMAP port are different populations with different certificate authorities, and a single global switch set to "require" would lock every ordinary mail client out of the server.
Nothing above changes a single connection until a listener is configured to use it.

| Connection security | Value | What it means | Typical ports |
|---|---|---|---|
| None | 0 | Plaintext only. STARTTLS is not advertised |
— |
| SSL/TLS | 1 | Implicit TLS from the first byte | 465, 993, 995 |
| STARTTLS (optional) | 2 |
STARTTLS is advertised; a client may or may not use it |
25 |
| STARTTLS (required) | 3 |
STARTTLS is advertised and the session is refused until it is used |
587, 143, 110 |
A working modern configuration:
| Port | Protocol | Security | Why |
|---|---|---|---|
| 25 | SMTP | STARTTLS optional | Other servers must be able to reach you; many still cannot do TLS |
| 587 | SMTP | STARTTLS required | Submission from your own users — never in the clear |
| 465 | SMTP | SSL/TLS | Implicit-TLS submission (RFC 8314) for clients that prefer it |
| 143 | IMAP | STARTTLS required | |
| 993 | IMAP | SSL/TLS | |
| 110 | POP3 | STARTTLS required | |
| 995 | POP3 | SSL/TLS |
Assign the certificate to every one of them. ACME does this for you for any TLS-enabled port that has no certificate configured; a port that already names one is left alone.
A TLS port with no certificate does not fall back to plaintext — it fails to start. That is HM5113, and the only other symptom is a service that came up with one listener missing. See Ports Reference for the full list of what listens where.
Two related controls live elsewhere and belong in the same decision:
-
Require TLS before AUTH is an IP range option, not a port option
(Security Hardening §17.1). On a range with it set,
AUTHis not advertised on a cleartext connection at all — all four protocols were fixed to stop advertising mechanisms they would refuse, because a client that sendsAUTH PLAINand then gets530has already put the password on the wire. -
DisableAUTHListnames local TCP ports whereAUTHshould not be offered at all, for example a port 25 that only accepts inbound mail from other servers.
These protect mail you send, and all three are on by default. Configure them under TLS & certificates → Transport security.
[Settings]
MtaStsEnabled=1 ; honour recipients' MTA-STS policies
DaneEnforcementEnabled=1 ; honour recipients' DANE/TLSA records
DnssecValidationEnabled=1 ; validate DNSSEC for DANE
DnssecTrustAnchors= ; empty = the built-in IANA root anchors
flowchart TD
A["A message is due for a domain"] --> B{"MtaStsEnabled and this is<br/>an MX delivery, not a fixed relay?"}
B -- no --> PLAIN["Deliver as usual"]
B -- yes --> C["Look up the TXT record at<br/>underscore mta-sts, dot, the domain"]
C -- "no record" --> NEG["Cache no-policy for 30 minutes"]
NEG --> PLAIN
C -- "record found" --> D["HTTPS GET the policy from<br/>mta-sts dot the domain,<br/>path /.well-known/mta-sts.txt"]
D --> E{"mode?"}
E -- none --> PLAIN
E -- testing --> LOGONLY["Debug log only, not enforced"]
LOGONLY --> PLAIN
E -- enforce --> F["Drop every MX host the<br/>policy does not name"]
F --> G{"Any host left?"}
G -- no --> DEFER["Defer the message, 4.7.0"]
G -- yes --> H["Require TLS with certificate<br/>verification to those hosts"]
A policy is cached until its own max_age, revalidated by re-reading the DNS record's
policy id at most once an hour, and a missing policy is negatively cached for 30 minutes.
DNS failure or a failed fetch during revalidation keeps the cached policy — RFC 8461 §5.1.
The mx: patterns match either exactly or with a leading *. that matches exactly one
label.
flowchart TD
A["A message is due for a domain"] --> B{"DaneEnforcementEnabled and<br/>this is an MX delivery?"}
B -- no --> PLAIN["Deliver as usual"]
B -- yes --> C["DNSSEC-validated MX lookup<br/>for the recipient domain"]
C --> D{"Chain status of the MX RRset"}
D -- insecure --> PLAIN
D -- bogus --> SKIP["Skip this host entirely"]
D -- secure --> E["Per host: look up the TLSA record at<br/>underscore 25, underscore tcp, the MX host"]
E --> F{"TLSA lookup status"}
F -- bogus --> SKIP
F -- "no records" --> PLAIN
F -- validated --> G["Require TLS and pin the<br/>certificate to the TLSA records"]
SKIP --> H{"Any host attempted?"}
H -- no --> DEFER["Defer the message, 4.7.0"]
Three things are worth stating exactly:
- The MX RRset must be DNSSEC-validated too, not just the TLSA record (RFC 7672 §2.2). A TLSA record found under a host name learned from a forged MX answer proves nothing — the attacker chose the host, so its own TLSA record validates perfectly. hMailServer therefore validates the MX RRset and checks that the host it is about to contact is one of the names that RRset published.
-
Only DANE-EE, usage 3, is honoured.
DaneVerifierskips any record whose usage is not 3, ignores issuer certificates entirely (depth > 0 always passes), and matches the leaf against selector 0 (whole certificate) or 1 (SubjectPublicKeyInfo) with matching type 0 (exact), 1 (SHA-256) or 2 (SHA-512). -
The whole thing fails open except for one value.
Bogus— a published DNSSEC chain that does not validate — is the only status that can stop a delivery. Every other outcome, including anything a caller produces by accident, lands on the ordinary delivery path. That is designed in: the enum's permissive value is zero, and the value that stops mail is the highest.
Setting DnssecValidationEnabled=0 reverts to opportunistic, unvalidated TLSA use.
With DANE enforcement, a recipient whose DNSSEC chain is bogus will not receive your mail rather than receive it unencrypted — which is the point.
SMTPDeliverer - Message 4711: MTA-STS policy mode 'enforce' active for example.com. TLS with certificate validation is required.
SMTPDeliverer - Message 4711: Skipping MX host mx9.example.com - not permitted by the MTA-STS policy of example.com.
SMTPDeliverer - Message 4711: Delivery to example.com deferred. No MX host matches the domain's MTA-STS policy.
SMTPDeliverer - Message 4712: DNSSEC-validated DANE-EE TLSA records found for mx1.example.com. TLS with TLSA certificate matching is required.
SMTPDeliverer - Message 4712: TLSA records for mx1.example.com failed DNSSEC validation. Skipping this host (RFC 7672).
SMTPDeliverer - Message 4712: Delivery to example.com deferred. TLSA records of all MX hosts failed DNSSEC validation.
And in the TCP/IP log, per handshake:
DANE: Session 812: Certificate matched DANE-EE TLSA record (selector 1, matching type 1).
DANE: Session 813: Certificate did not match any DANE-EE TLSA record. Failing TLS handshake.
Both deferral paths use the enhanced status code 4.7.0 — "other or undefined security status" (RFC 3463). Neither RFC registers a code of its own, and borrowing one that means something else would be worse than saying "refused on security grounds".
The outbound half above is about other people's policies. Publishing your own is two DNS records and one switch.
MTA-STS. MtaStsHostingEnabled defaults to 1, and the built-in web services server
serves /.well-known/mta-sts.txt for any hosted, active domain when the request's Host
is mta-sts.<domain>. It generates the body from your live MX records unless
MtaStsPolicyMx overrides them:
version: STSv1
mode: enforce
mx: mail.yourcompany.com
max_age: 604800
| Setting | Default | Notes |
|---|---|---|
MtaStsHostingEnabled |
1 | On, but inert until the HTTPS listener exists |
MtaStsPolicyMode |
enforce |
enforce, testing or none; anything else is read as enforce
|
MtaStsPolicyMaxAge |
604800 (7 days) | Clamped to 86400 … 31557600 |
MtaStsPolicyMx |
empty | Comma-separated override; empty derives the list from live MX records |
WebServicesHttpsPort |
0 | Must be set. RFC 8461 §3.3 requires the policy to be fetched over HTTPS |
If
WebServicesHttpsPortis 0 the server logs, in as many words, that the policy cannot be served and sending servers will treat the domain as having no policy. Addmta-sts.yourcompany.comtoAcmeDomainsso the HTTPS listener has a certificate for the name it will be asked for.
Then publish _mta-sts.yourcompany.com. IN TXT "v=STSv1; id=20260908000000Z" and bump the
id whenever you change the policy. See DNS for Your Domain.
DANE. Every successful ACME issuance logs the exact record to publish:
ACME: DANE TLSA record for this certificate: _25._tcp.<mx-host>. IN TLSA 3 1 1 3b1f…
That is 3 1 1 — DANE-EE, SubjectPublicKeyInfo, SHA-256 — which is why AcmeReuseKey
matters: the digest is over the key, so reusing it keeps the record valid across
renewals. Your zone must be DNSSEC-signed for the record to mean anything.
Off by default, and worth turning on the day you start diagnosing anything:
| Setting | Default | What it writes |
|---|---|---|
AuthenticationResultsEnabled |
off | One Authentication-Results header per accepted inbound message (RFC 8601), carrying the SPF, DKIM and DMARC verdicts this server reached. Only checks that actually ran are reported |
ReceivedSpfHeaderEnabled |
off | A Received-SPF header (RFC 7208 §9.1), written only when the SPF check ran |
AuthenticationResultsIdentity |
empty = this computer's name | The authserv-id: the first token of every header this server writes, always lower-cased |
The identity is load-bearing in both directions. An arriving message carrying an
Authentication-Results header that claims this identity has it removed before the
server writes its own, so a sender cannot have a verdict believed downstream in this
server's name (RFC 8601 §5). A header naming any other identity is left completely
untouched.
Two aggregate reporters, both scheduled hourly and both inert until a sender address is set. They flush their statistics either way, so switching one on does not start with an empty history.
| TLS-RPT (RFC 8460) | DMARC aggregate (RFC 7489) | |
|---|---|---|
| Address setting | TlsRptFromAddress |
DmarcRptFromAddress |
| Organisation name |
TlsRptOrganizationName, default hMailServer
|
DmarcRptOrganizationName, default hMailServer
|
| Recorded by |
ExternalDelivery, per delivery attempt, classified tlsa, sts or no-policy-found
|
SpamTestDMARC, for every message evaluated against a published policy — passes included |
| Schema | — |
DmarcRptSchemaVersion, default 1 = RFC 7489 Appendix C; 2 = RFC 9990 |
[Settings]
TlsRptFromAddress=tlsrpt@yourcompany.com
DmarcRptFromAddress=dmarc@yourcompany.comDMARC reports are only sent to an address outside the policy domain when that address's own DNS says it wants them, so a domain cannot use its DMARC record to aim this server's reports at somebody else.
-
AcmeEnabled=1,AcmeDomainsnamingmail.,mta-sts.andautoconfig.— port 80 reachable. - Create ports 465, 993 and 995; set 587, 143 and 110 to STARTTLS-required and 25 to STARTTLS-optional. Confirm every one has a certificate.
- Check Transport encryption overview — it will tell you if anything still carries a password in the clear.
- Leave
MtaStsEnabled,DaneEnforcementEnabledandDnssecValidationEnabledat 1. - Set
WebServicesHttpsPortand publish_mta-stsin DNS. - Publish the TLSA record the ACME log prints, if your zone is signed.
- Turn on
AuthenticationResultsEnabled, and setTlsRptFromAddressandDmarcRptFromAddress. - Diary the first renewal: it will be two-thirds of the way through the certificate's life,
and the log line to look for is
ACME: Certificate issued successfully.
See also: Security Hardening · Ports Reference · DNS for Your Domain · Stopping Spam · Settings Reference · Monitoring and Health · Capabilities and Configuration
hMailServer 6.3.2 · AGPL-3.0-or-later · Repository · Report a documentation error
Hmail Server — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- The Control Panel
- Your First Domain and Mailbox
- Connecting a Mail Client
- DNS for Your Domain
2. Secure it
3. Operate it
- Monitoring and Health
- Backup and Restore
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Routes and Relays
- The COM API and Scripting
- The REST API
- APIs Reference
5. Contribute to it
- Project Handbook
- Architecture
- Contributing
- Release Process
- Governance
- Assurance Case
- Regression Test Environment
- Fuzzing
- Regulatory Scope
- Third-Party Binaries
Look it up — from any journey