---
title: "Settings: what is not stock, when a save takes effect, and the three levels"
url: https://www.hmailserver.co.uk/topic/28222/settings-what-is-not-stock-when-a-save-takes-effect-and-the-three-levels
author: "Progressiverobot"
published: 2026-09-27T19:04:13.731Z
updated: 2026-09-27T19:04:13.731Z
category: "General support"
tags: ["administration","database","control-panel","rest-api","control-deck"]
site: hMailServer Community Forum
---

# Settings: what is not stock, when a save takes effect, and the three levels

This topic is about the settings themselves, whichever interface you use: where a setting is stored, which of yours are not what 6.3.4 ships with, when a saved value is in effect, and how the twenty-seven settings a domain or an account may hold its own value of combine with the server's. The one thing to know first: since 6.3.4 the database is the settings store. A `[Settings]` line edited by hand in `hMailServer.ini` changes nothing; at the next start the server uses the stored value, names the key in the error log as `HM5804`, and, where the service may write the file, puts the line back. Everything below describes 6.3.4 unless it says otherwise; a server on 6.3.3 still reads the file and applies a change at the next start.

### Two stores, and how a setting is changed

**The table `hm_settings`.** Everything the COM `Settings` object has always carried, such as the anti-spam thresholds and the delivery options. Since 6.3.4 every new setting goes here, and no key is added to `[Settings]`. 6.3.4 added 193 rows for its features.

**The `[Settings]` keys.** 241 keys read by the settings loader, plus `UseLanguage`, read per call. On 6.3.3 they live in the file, mirrored into `hm_inisettings`. Since 6.3.4 (schema 6042) `hm_inisettings` is the store and the file's section is a copy, which the server keeps current where the service may write the file.

**Where the service cannot write the file, the copy goes stale.** That is a new Windows installation, whose service runs as `NT SERVICE\hMailServer`, and a Linux package installation, where `/etc/hmailserver/hMailServer.ini` is `0640 root:hmailserver`. There the copy is not refreshed and an edited line is not put back; the database's values are still the ones used. The server reports this once a start in the application log, not as an error, and `ServiceAccount.md` says the same of the Linux packages; the wiki's Installing on Linux page names `HM5806` at a save and `HM5803` at every start for them instead. On such a server read a setting from the store, not from the file.

**The migration is the first start.** A key the table has never seen is adopted with the file's value, and there is nothing to run by hand. A line edited after the old server last started, including one edited to prepare the upgrade, is taken for an edit and put back, so make such a change after upgrading. A 6.3.3 server refuses a database at schema 6042 or later with `HM5011`; see [Upgrading to 6.3.4](/topic/28193).

**What stays in the file.** `[Database]`, `[Directories]`, `[Security]` and `[GUILanguages]` are the bootstrap, read before the database is open and never in the store. `[LDAP]`, `[SendingLimits]` and `[SendingLimitsOverrides]` stay in the file. `PasswordPepper` is kept in the file only, never in the database and never in a backup. `[SettingsOverride]` is the one way to make the file win, for a database that is unreachable or holds a value the server will not start on: a key written there is applied over whatever is stored and named in the error log as `HM5807` at every start, because no page shows it and no backup carries it. Storing a value for a key an override shadows is accepted and reported as `HM5808`.

| Surface | Set a `[Settings]` key | Return it to the default |
|---|---|---|
| Control Panel (Windows only) | the key's editor, written over COM even on the server itself | No. Clearing a box stores an empty value, which a number reads as `0` |
| REST | `PUT /api/v1/settings/ini/{name}` with `{"value": "..."}` | `DELETE /api/v1/settings/ini/{name}` |
| `hmctl`, over REST | `hmctl ini set <name> <value>` | `hmctl ini delete <name>` |
| PowerShell module | `Set-HmIniSetting` | none |
| COM (Windows only) | `Settings.SetIniSetting(name, value)` | `Settings.DeleteIniSetting(name)` |
| Linux command line, as root | `hmailserver --set-setting NAME=VALUE`, with no listener running. A secret's value is refused on the command line: give the name alone and type the value at the prompt | none |
| Control Deck | only `SmtpTarpitCount`, `SmtpTarpitDelaySeconds`, `UseLanguage` and `RewriteEnvelopeFromWhenForwarding` | none |

The REST ini routes take the administrator password only; an API key of any scope is refused. Since 6.3.4 a setting the environment names, as `HM_SETTING_<key>` or `HM_PROPERTY_<name>` ([configuration from the environment](/topic/28231)), refuses an administrator's change, 409 over REST, until a start without the variable.

### What this server has changed from stock

**Since 6.3.4.** One list of every setting whose value is not the one this version ships with, with the shipped value beside it. The example in `ChangedFromStock.md`:

```text
STORE     NAME                       VALUE           DEFAULT
ini       LogLevel                   5               9
ini       MaxLogLineLen              2000            500
ini       SMTPDMaxTimeout            600             1800
ini       OAuth2HmacSecret           (set)
ini       DnsServer                  10.0.0.1 (forced from [SettingsOverride])
database  smtpnoofretries            6               4
database  maxsmtpconnections         200             0

7 of 391 settings are not what this version ships with.
```

| Where | What to ask |
|---|---|
| Control Deck | **Changed from stock**, in the navigation beside Settings |
| REST | `GET /api/v1/settings/changed` |
| Command line | `hmctl config changed` |
| PowerShell | `Get-HmChangedSetting` |
| COM | `Settings.GetChangedFromDefault` |

The REST route and the COM call answer the same document. `store` is `ini` (`hm_inisettings`) or `database` (`hm_settings`).

**What "default" means.** The value the server behaves as though the setting has when nothing has been stored for it, not what a fresh installation's files contain. `hMailServer.ini` ships with lines the packaging wrote, such as `RestApiBindAddress=127.0.0.1`, and a line holding the shipped value is not a change; one holding another value is listed.

**An upgraded installation lists more than a new one.** `database` defaults are what a database created by this version holds. An installation upgraded from an old schema got its values from the upgrade scripts of the time, and where those differ (`maxmessagesize`, `usecache`, `protocolimap`, `smtpallowincorrectlineendings` and half a dozen more) the older installation is genuinely different and is listed. A row nothing reads any more (`useordb`, `usespamhaus`, `tarpitcount`, `filters`, `imapport`, `relaymode`) is not reported at all.

**The marks on an entry.**

- `secret`: a setting whose name says its value is a secret is listed by name, with `(set)` or empty, and never shown. A key file, such as `RestApiPrivateKeyFile`, is a path and is shown.
- `known` false: the server holds a value and ships no default for it, such as a key stored for a script of your own. `hmctl` prints `(none: this server does not read it)`.
- `overridden`: `[SettingsOverride]` is forcing the value. Such a key is listed even when neither store holds anything for it; `hmctl` prints `(forced from [SettingsOverride])`.
- `environment` is `variable` or `file`, and `hmctl` prints `(set by the environment)`. `profile` names the setting profile whose apply wrote the value, while the setting still holds it.

**Which values these are.** The value stored, not the value the server settled on where the two differ: a `PasswordHashIterations` outside the range the server accepts is reported as `HM5561` and the built-in work factor used; an unusable `PreferredHashAlgorithm` is reported as `HM5528` (or `HM5607`, for Argon2id on a build whose OpenSSL has none).

**Who may see it.** The administrator password, and nothing else. An API key of any scope is refused with a 401, and an account holding an administrative role, an `auditor` included, with a 403, because the list includes commands the server runs: `AutoBanCommand` runs as the service account. The feature changes nothing. It does not compare two servers (two `hmctl config changed --json` answers and `diff` do), and it does not say when or by whom a setting was changed; [the audit trail](/topic/28273) does (`GET /api/v1/audit?object_type=setting`).

### When a saved setting takes effect

**Since 6.3.4.** Until then every `[Settings]` key was latched when the service started. Now a save publishes: the store takes the value, the server rebuilds its settings from the rows, and every key whose readers can take a new value is in effect when the save returns, whichever door saved it. Every key carries one of three marks:

| Mark | Keys | What it means |
|---|---|---|
| `now` | 211 | A save applies it |
| `reinitialize` | 28 | Bound into something the servers build when they start, so it waits for a reinitialise: **Apply now** in the Control Panel or the Deck, `POST /api/v1/server/reinitialize`, or `Application.Reinitialize` over COM |
| `restart` | 31 | Read once per process: 2 `[Settings]` keys and the 29 bootstrap keys (20 `[Database]`, 5 `[Directories]`, 3 `[Security]`, 1 `[GUILanguages]`) |

**The 28 that wait for a reinitialise.**

| What is rebuilt | Keys |
|---|---|
| The REST listener | `RestApiBindAddress`, `RestApiPort`, `RestApiCertificateFile`, `RestApiPrivateKeyFile` |
| The metrics listener | `MetricsServerBindAddress`, `MetricsServerPort`, `MetricsServerCertificateFile`, `MetricsServerPrivateKeyFile`, `MetricsServerAuthPassword`, `MetricsServerAuthToken`, `MetricsServerAuthUsername` |
| The web-services listener | `WebServicesBindAddress`, `WebServicesHttpPort`, `WebServicesHttpsPort`, `WebServicesCertificateFile`, `WebServicesPrivateKeyFile` |
| The ManageSieve listener | `ManageSieveServerBindAddress`, `ManageSieveServerPort` |
| The SSL contexts of the SMTP, IMAP, POP3 and ManageSieve listeners and the outbound client | `TlsCipherSuites13`, `TlsKeyExchangeGroups`, `TlsSessionCacheSize`, `TlsSessionTicketsEnabled`, `TlsSessionTimeoutSeconds`, `TlsTicketKeyRotationSeconds` |
| ACME, the database pool, the fetch queue | `AcmeEnabled`, `AcmeCertificateDirectory`, `DatabaseStatementTimeout`, `MaxNumberOfExternalFetchThreads` |

**The 2 that wait for a restart** are `ServiceAccountName` and `ServiceAccountPassword`, which are retired: since 6.3.4 `hMailServer.exe /Register` takes the account from its command line alone, and a stored value is shown as pending until the next start. The bootstrap keys are never in the store, so none is ever pending.

**Some keys are `now` because a subscriber re-latches them.** `MaxSubmissionsPerIPPerMinute` and `MaxOutboundPerDestinationPerMinute` start each limit's one-minute window again, so a saved limit counts from when it was saved. A cache of data is not a setting: a changed `DNSServer` is used for the next lookup, and an answer already cached stays until its TTL runs out.

**Pending.** A `reinitialize` or `restart` key whose stored value is not the value the server is running on is pending. The list is recomputed at every request and never remembered, so a value put back before anything applied it is not pending.

| Where | What |
|---|---|
| REST | `GET /api/v1/server/settings/pending` answers `[{name, stored, running, applies, secret}]`, and `applies` and `pending` come on every `/api/v1/settings/ini/{name}` answer. The administrator password's route |
| COM | `Settings.IniSettingApplies(name)`, `Settings.IniSettingAppliesAll`, `Settings.PendingIniSettings` |
| Control Panel | **Applies on save**, **After reinitialize** or **After a restart** under each field kept in the store, and a pending bar with **Apply now** |
| Control Deck | The same words as a badge, and **Apply now** above its Settings view |

A name the loader does not read (`UseLanguage`, a key a script of your own reads, a misspelling) has no mark: COM's `IniSettingApplies` refuses it and REST answers `applies: null`.

**Apply now is a reinitialise.** Every service stops and starts again in place, which takes every `reinitialize` key and not a `restart` key. It asks first. A reinitialise ends maintenance mode, and `POST /api/v1/server/reinitialize` answers `{"reinitializing": true, "drained": true}` when it ended a drain. The Deck's session ends with the REST listener it was using; the Control Panel's COM session survives.

**What a save does not do.** It does not re-read the bootstrap sections or `[SettingsOverride]`; a reinitialise or a start re-reads the override section and announces it as `HM5807`. It does not reconcile the file against the table; that is a start's and a reinitialise's only. And a value stored by another process (another node on the same database, or `hmailserver --set-setting` while the service runs) reaches this server at the next save here, a reinitialise or a restart, because nothing watches the table. `hmctl` has no pending verb.

**On Linux** the regression fixture alone proves the marks, with the three COM members answered over REST; the internal tester that swaps settings under load does not run there.

### One value, three levels

**Since 6.3.4** (schema 6088). Twenty-seven settings may be set for the server, for a domain and for an account, and every place that shows one shows what is in effect and where it came from. For an account: its own value, else its domain's, else the server's. Every setting on the list is exactly one kind:

| Kind | How many | The rule | Who sets a level below the server |
|---|---|---|---|
| Limit | 15 | A level below may tighten the level above and never loosen it. A looser value is refused when it is written; a value the level above has since tightened past is capped when it is read | Anybody who administers that level: a domain administrator, a key restricted to the domain |
| Gate | 4 | The deepest level that holds a value wins, either way | The server administrator alone; a domain-restricted key is refused with 403 |
| Preference | 8 | The deepest level that holds a value wins | The domain's administrators, and a domain-restricted key |

The last column is the rule for the levels held in `hm_settingoverrides`. A field the domain already had keeps the authority it had, whatever its kind: the domain's maximum message size and the domain's retention are the server administrator's.

| Kind | Settings |
|---|---|
| Limits | `max_message_size_kb`, `session_idle_seconds`, `session_lifetime_seconds`, `session_one_per_device`, `session_bind_to_address`, `quota_grace_percent`, `quota_send_stop_percent`, `recall_days`, `recover_deleted_days`, `sending_max_messages`, `sending_max_recipients`, `reader_fetch_accounts_max`, `reader_sender_addresses_max`, `reader_masked_addresses_max`, `reader_offline_days_max` |
| Gates | `plus_addressing_enabled`, `openpgp_portal_enabled`, `zero_access_allowed`, `directory_user_editable` |
| Preferences | `quarantine_digest`, `quarantine_digest_hour`, `external_tag_subject`, `external_tag_header`, `impersonation_action`, `quota_warning_percent`, `country_policy`, `message_retention_days` |

A server setting not on the list is the server's alone: its name is refused on the domain and account routes (400, `unknown field`) and by COM's `SettingOverride`. Of the `[Settings]` keys only `QuotaWarningPercent` has levels.

**Where each level is held.** A field the domain or the account already had, such as the domain's `MaxMessageSize` or `MessageRetentionDays`, stays where it was, with the meaning it always had; its 0 or -1 is the level holding nothing. The rest are rows of `hm_settingoverrides`, where no row means "take the level above". An owner's rows are cached for sixty seconds; every write through the interfaces drops the cache at once, and so does `POST /api/v1/settings/cache/clear` or COM `Cache.Clear`. A row put in by hand that the interfaces would refuse is treated as holding nothing and reported as `HM7311`. Nothing changes for a domain or an account that holds no value of its own.

| Where | What |
|---|---|
| REST | `GET /api/v1/settings/levels` lists the twenty-seven. `GET /api/v1/domains/{domain}` and `GET /api/v1/accounts/{address}` answer `<name>_effective` and `<name>_source`, and `<name>` where the level is held in the overlay. For a level held in the overlay a `PUT` takes a value of the setting's own JSON type, and `null` makes it inherit; for a level held in a field of its own `null` leaves it unchanged, and the field's own 0 or -1 is "take the level above" |
| COM (Windows only) | `Domain.SettingOverride(name)` and `Account.SettingOverride(name)`, an empty string for "take the level above", written at once and not by `Save`; `EffectiveSetting(name)` and `EffectiveSettingSource(name)` |
| `hmctl` | `hmctl domain set <domain> <name> <value\|inherit>` and `hmctl account set <address> <name> <value\|inherit>` |
| Control Panel (Windows only) | A **Levels** tab on the domain and account dialogs, with "In effect: ..., from the ..." beside each setting and **Save levels now** |
| Control Deck | The same card in its domain and account editors |

**A saved level applies at once**, at every level and from every door, because the resolver is asked when the value is used (at `MAIL FROM`, when a session is checked) and never latched.

**Deleting, backing up and moving.** Deleting an account or a domain deletes its rows; a delete that cannot remove them is reported as `HM7310` for a domain and `HM6109` for an account. The XML backup carries the rows, and on restore a row the build refuses is left out and reported as `HM7312`. A mailbox moved to another domain keeps its own values.

**What is thinner than it sounds.**

- Attachment blocking, web push, passkeys and account recovery have no levels.
- The domain's session seconds and switches and its quota grace and send-stop shares could always be set looser than the server's and still can; the ceiling binds the account below them.
- The maximum message size is resolved at `MAIL FROM` and at IMAP `APPEND`; the EHLO `SIZE` advertisement, the webmail's own send and the send API stay the server's.
- The session policies are the browser sessions' (the webmail, the portal, the Deck); IMAP, POP3 and SMTP sessions have none.
- `country_policy` cannot be set to empty over COM, where an empty string means "take the level above"; REST's `""` does it.
- Twelve of the twenty-seven are proved by what the account and its domain answer rather than by driving the behaviour per account, and the levels' own regression fixture is Windows-only.

### When a setting appears to do nothing

1. Is it pending? A `reinitialize` key waits for **Apply now** and a `restart` key for a service restart.
2. Was it typed into `hMailServer.ini`? The stored value wins and `HM5804` names the key. Store it instead.
3. Is the key spelled exactly as the code reads it, and is a boolean literally `1`? An unknown key is the default, and only `1` is true: `2`, `yes` and `true` all read as false.
4. Does it need a companion? `SRSEnabled` needs `SRSSecret`, ACME needs `AcmeDomains`, autoconfig needs `WebServicesHttpPort` or `WebServicesHttpsPort`, and MTA-STS hosting needs the HTTPS one.
5. Does the error log show `HM5807` or `HM5808`? A `[SettingsOverride]` section is forcing a value.

### Where to read more

- The tour: https://gitlab.com/hmailserver/hmailserver/-/wikis/Settings-Overview. Every key with its default, validation and mark: https://gitlab.com/hmailserver/hmailserver/-/wikis/Settings-Reference. Where the two disagree, the reference is generated from the code and is right.
- The three documents behind this topic: https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/ChangedFromStock.md, https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingsApply.md and https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingLevels.md
- The public documentation's settings chapter: https://www.progressiverobot.com/hmailserver-documentation/#21-settings-reference-hmailserverini
- On this forum: [the Control Panel on Windows](/topic/28223), [`hmctl` and the PowerShell module](/topic/28265), [the `hmailserver` command line](/topic/28245), [the Control Deck](/topic/28192), [Upgrading to 6.3.4](/topic/28193) and [hMailServer on Linux](/topic/28189).
