Repository navigation
Upgrading hMailServer
This page began as a chapter of the 6.2.10 manual and has been corrected for 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. The schema figures, the upgrade without a window (§18.4b) and the settings notes were brought up to master 540347500 on 24 September 2026, and the whole page to 6.3.4, the latest release (26 September 2026, schema 6103), from the tree it was cut from (92b8f9f63) and its
hmailserver/docs/Upgrading.mdandUpdateFeed.md; §18.4c is what is particular to upgrading to 6.3.4.
hMailServer upgrades in place. The installer stops the service, replaces the program files, upgrades the database schema if needed, and restarts. Your mail, accounts, domains and settings are preserved.
Two things change during an upgrade:
-
Program files in
C:\Program Files\hMailServer\Binare replaced. -
The database schema is upgraded by
DBUpdater, which the installer runs automatically. Schema version of 6.3.4, the current release: 6103 (6.3.3's was 6040, so an upgrade from 6.3.3 applies 63 steps).
Your data directory (the messages) and hMailServer.INI are left alone by the
installer. Since 6.3.4, the first start of a server at
schema 6042 or later does one thing to the INI's [Settings] section: the database becomes the
settings store - keys the 6.3.3 mirror already held keep their stored values, any key the
database never held is taken in from the file - and the section becomes a copy the server
rewrites. Nothing has to be run by hand for that - but see 18.3 step 6.
flowchart TD
A["Setup starts"] --> B{"Is the hMailServer<br/>service running?"}
B -- yes --> C["Stop it and wait<br/>ShutdownDrainSeconds plus 30 s"]
B -- no --> D
C --> E{"Reached STOPPED<br/>inside the budget?"}
E -- no --> F["Refuse to go past the Ready page.<br/>A file copy over a running service leaves<br/>the OLD binary against the NEW schema"]
E -- yes --> D["Move a pre-5.x hMailServer.ini<br/>out of the Windows directory into Bin"]
D --> G["Copy the program files"]
G --> H["Install the bundled .NET 10 Desktop Runtime,<br/>but only if it is missing"]
H --> I["Register the service"]
I --> J["Run DBSetupQuick, which drives DBUpdater"]
J --> K{"Exit code 0?"}
K -- no --> L["Dialog naming the exit code,<br/>then the install FAILS with a non-zero exit code"]
K -- yes --> M["net start hMailServer"]
M --> N{"Is the service running?"}
N -- no --> O["Dialog: check hMailServer.log<br/>and the ERROR log"]
N -- yes --> P["Completed"]
Two of those boxes are worth reading properly.
The service stop is bounded, and a failure to stop aborts the upgrade. The
budget is ShutdownDrainSeconds plus 30 seconds, derived from your own setting
rather than guessed, because that setting deliberately holds the stop open while
sessions finish. If the service will not stop, setup refuses to go past the Ready
page - because a file copy over a running service offers Retry/Ignore, and
choosing Ignore leaves the old binary in place while the schema moves to the
new version, after which the server refuses to start at all.
A failed database upgrade now fails the installation. The exit code of
DBSetupQuick is checked, a dialog names it, the service start is still
attempted, and then setup raises and finishes with a non-zero exit code. A
scripted deployment can tell. (This is a change: in earlier releases the dialog
was the only signal and the wizard still reported success.)
The database upgrade chain is continuous from every earlier hMailServer release, on MySQL, MS SQL, PostgreSQL and SQL CE. You do not need to step through intermediate versions:
flowchart LR
A["Schema 0<br/>hMailServer 1.0"] --> B["1100 - 3402<br/>hMailServer 1.x to 3.x"]
B --> C["4000 - 4402<br/>hMailServer 4.x"]
C --> D["5000 - 5708<br/>hMailServer 5.x<br/>PostgreSQL and SQL CE<br/>join at 5001"]
D --> E["6001 - 6005<br/>hMailServer 6.0 to 6.2.18"]
E --> F["6006 - 6011<br/>6.2.19"]
F --> G["6012 - 6022<br/>the 6.2.22 pre-releases"]
G --> H["6023 - 6025<br/>6.2.23-alpha1, 6.2.24"]
H --> I["6026 - 6030<br/>6.2.25"]
I --> J["6031<br/>6.2.27 to 6.3.1"]
J --> K["6032 - 6038<br/>6.3.2"]
K --> L["6039 - 6040<br/>6.3.3"]
L --> M["6041 - 6103<br/>6.3.4, the current release"]
DBUpdater walks every intermediate step for you, one at a time, in one transaction. It builds the whole path first and checks that every script file on it exists before it runs the first one, so a missing dialect script is named and refused rather than discovered halfway.
| Coming from | Its schema | Path | Notes |
|---|---|---|---|
| 6.3.4 | 6103 | Nothing to do: it is the current release | |
| 6.3.3 | 6040 | Run the 6.3.4 installer, by hand - see 18.4c | Sixty-three steps, to 6103. 6101 adds two columns to hm_messages, and on MySQL and MariaDB 6102 rebuilds every table - see 18.2a |
| 6.3.2 | 6038 | Run the current installer | Sixty-five steps |
| 6.2.27 - 6.3.1 | 6031 | Run the current installer | Seventy-two steps. Two of them add columns to hm_messages (6036, 6101) - see 18.2a |
| 6.2.25 / 6.2.26 | 6030 | Run the current installer | Seventy-three steps |
| 6.2.24 | 6025 | Run the current installer | Seventy-eight steps. One of them reads every child table - see 18.2a |
| 6.2.19 - 6.2.21 | 6011 | Run the current installer | Ninety-two steps |
| 6.2.10 - 6.2.18 | 6005 | Run the current installer | Ninety-eight steps |
| 6.0 / 6.1 | read hm_dbversion
|
Run the current installer | Schema upgraded automatically |
| 5.6 / 5.7 | read hm_dbversion
|
Run the current installer | Schema upgraded automatically. Read 18.5 - the GUI has changed |
| 5.3 - 5.5 | read hm_dbversion
|
Run the current installer | As above. Very old installs: back up first and test the restore |
| Original hMailServer (unmaintained) | any | Run the current installer | This fork is a drop-in successor. It did not branch the schema; it extended it |
How far back the chain reaches depends on your backend. The steps are registered once for all four, but the SQL files are per-dialect and they do not all exist: MySQL and MS SQL ship every step back to schema
0; PostgreSQL and SQL Server Compact ship every step from5001onwards. In practice that is not a gap, because neither was a supported backend before hMailServer 5, so no older database exists on either.
Everything from 6005 onwards, with the operational note that matters. The full "what changed" table is in Changes-Since-6210; this one answers "how long will it take and what should I watch".
| Step | Shipped in | What it does | Plan for it |
|---|---|---|---|
| 6005 to 6006 | 6.2.19 | hm_imapfolders.folderspecialuse |
Instant |
| 6006 to 6007 | 6.2.19 | Two hm_domains columns for DKIM key rotation |
Instant |
| 6007 to 6008 | 6.2.19 | Two hm_tcpipports columns for client certificates |
Instant |
| 6008 to 6009 | 6.2.19 | hm_sslcertificates.sslprivatekeypassword |
Instant |
| 6009 to 6010 | 6.2.19 | Two hm_settings rows for ARC |
Instant |
| 6010 to 6011 | 6.2.19 | New table hm_inisettings
|
Instant |
| 6011 to 6012 | 6.2.22-pre1 | Widens hm_rule_criterias.criteriamatchvalue to 2000; adds hm_accounts.accountvacationbegindate
|
Fast; rule criteria are few |
| 6012 to 6013 | pre1 |
hm_messages.messagesavedate, then UPDATE every message row to seed it |
Scales with mailbox size. On a store of millions of messages this is the first slow step |
| 6013 to 6014 | pre1 | New table hm_imap_metadata
|
Instant |
| 6014 to 6015 | pre2 |
hm_messages.messageemailid, then UPDATE every message row
|
Scales with mailbox size, same as 6013 |
| 6015 to 6016 | pre3 | New table hm_apppasswords
|
Instant |
| 6016 to 6017 | pre3 | hm_accounts.accounttotpsecret |
Instant |
| 6017 to 6018 | pre3 | New table hm_quarantine
|
Instant |
| 6018 to 6019 | pre3 |
hm_accounts.accountpasswordchanged plus an update of every account; new table hm_passwordhistory
|
Fast; accounts are few |
| 6019 to 6020 | pre6 | New table hm_messagetrace
|
Instant |
| 6020 to 6021 | pre6 | Six hm_domains.domainrelay* columns |
Instant |
| 6021 to 6022 | pre6 | Two hm_servermessages rows for quota warnings |
Instant |
| 6022 to 6023 | 6.2.23-alpha1 | New tables hm_messageindexterms, hm_messageindexstate
|
Instant; they stay empty until full-text indexing is switched on |
| 6023 to 6024 | alpha1 | Six hm_domains.domainvacation* columns; new table hm_blocked_senders
|
Instant |
| 6024 to 6025 | alpha1 |
hm_messages.messageflags tinyint to smallint, plus three hm_accounts and two hm_distributionlists columns |
A table rewrite of hm_messages on MS SQL, SQL CE and MySQL. PostgreSQL was already smallint. This is the single most expensive step in the chain on a large store |
| 6025 to 6026 | 6.2.25 | One hm_settings row |
Instant |
| 6026 to 6027 | 6.2.25 | Retention-day columns on domains and accounts | Instant |
| 6027 to 6028 | 6.2.25 | New table hm_metricsamples
|
Instant |
| 6028 to 6029 | 6.2.25 | New table hm_archiveindex
|
Instant; only copies made from then on are indexed |
| 6029 to 6030 | 6.2.25 |
Seventeen FOREIGN KEY ... ON DELETE CASCADE constraints, with orphan rows deleted first, children before parents; MySQL also gets ENGINE=InnoDB on every table involved |
Reads every child table once. Plan for it the way you would plan an index build |
| 6030 to 6031 | 6.2.27 | hm_fetchaccounts.famirrorfolders |
Instant |
| 6031 to 6032 | 6.3.2 | New table hm_contacts (the address book), cascading from hm_accounts
|
Instant |
| 6032 to 6033 | 6.3.2 | New table hm_accountprefs (the webmail's remembered choices) |
Instant |
| 6033 to 6034 | 6.3.2 | New table hm_scheduled (messages put off to later) |
Instant |
| 6034 to 6035 | 6.3.2 | New table hm_files (files sent as links) |
Instant |
| 6035 to 6036 | 6.3.2 |
hm_messages.messagekeywords, a NOT NULL DEFAULT '' column added to the message table on all four backends |
The one step here that touches hm_messages. How long an added column with a default takes depends on the backend and its version; on a large store, time it on a copy first |
| 6036 to 6037 | 6.3.2 | New table hm_smimekeys (S/MIME certificates and keys) |
Instant |
| 6037 to 6038 | 6.3.2 | hm_surblservers.surblresult |
Instant |
| 6038 to 6039 | 6.3.3 | Widens hm_domains.domainrelaypassword to 1024 |
Fast; domains are few |
| 6039 to 6040 | 6.3.3 |
hm_contacts.contacturi, contactuid, contactvcard (CardDAV) |
Fast; scales with the address books, not the message store |
| 6040 to 6041 | 6.3.4 | New tables hm_calendars, hm_calendarobjects (CalDAV) |
Instant |
| 6041 to 6042 | 6.3.4 |
update hm_dbversion and nothing else: the version is the fence that keeps a 6.3.3 server, which lets hMailServer.INI win, away from a database that is now the settings store |
Instant. The move of the [Settings] keys into the database happens at the server's first start afterwards, not in DBUpdater - see Settings Overview
|
| 6042 to 6043 | 6.3.4 | New tables hm_audit, hm_alertrules, hm_alertevents; nine rows in hm_settings for alerts and the audit trail, each inserted only if absent |
Instant |
| 6043 to 6044 | 6.3.4 | Seven hm_domains columns for the external-sender tag, the first-contact note and the disclaimer; new table hm_knownsenders
|
Fast; domains are few |
| 6044 to 6045 | 6.3.4 | New table hm_remotedomainpolicies
|
Instant |
| 6045 to 6096 | 6.3.4 | Fifty-one more steps, one per feature: new tables and columns for sender lists, roles, DKIM rotation, bounces, OpenPGP, webhooks, country blocking, devices, Web Push, complaints, mailbox moves, reputation, spam traps, out-of-office, quota policy, mail flow rules, calendars and their sharing, send-as, masked addresses, recoverable items, expiring messages, DMARC reports, compromised accounts, greylisting, the domain policy list, lists, journaling, recovery, passkeys, the directory, address books, zero-access, restricted delivery, recall, impersonation, setting levels, the send API, migrations, the sandbox, IP warm-up, gateway encryption, the S/MIME authority and SCIM. Each is listed in section 2 of Changes-Since-6210 |
None touches hm_messages. 6071 to 6072 widens the greylisting triplet table's two address columns and indexes it, which scales with that table; every other step adds tables, or columns to small tables |
| 6096 to 6097 | 6.3.4 | New tables hm_discoverycases, hm_discoverysearches, hm_discoveryholds, hm_discoveryexports (eDiscovery); the view_discovery and manage_discovery permissions added to the built-in serveradmin role, view_discovery to auditor
|
Instant |
| 6097 to 6098 | 6.3.4 | New table hm_storeblobs (de-duplicated attachment parts); hm_settings rows MessageStoreCompression 0 and MessageStoreDedupMinKB 256, each inserted only if absent |
Instant. Nothing already stored is rewritten: compression is off, and --compress-store is a separate, deliberate step |
| 6098 to 6099 | 6.3.4 | New tables hm_accountkeys and hm_domainrecovery (mailbox encryption at rest); hm_settings row MailboxKeyIdleMinutes 15 |
Instant. Off on every account |
| 6099 to 6100 | 6.3.4 | New tables hm_inboundhooks and hm_inboundhookqueue (mail in to a webhook); the manage_inbound_webhooks permission added to serveradmin
|
Instant |
| 6100 to 6101 | 6.3.4 |
hm_accounts.accountkind (0, a person); new tables hm_bookingpolicies, hm_bookinganswers, hm_bookingqueue, hm_bookingdecisions (rooms and equipment); two columns on hm_messages, messagesubmitterid (0) and messageauthdomains ('') |
The step that touches hm_messages, which has a row for every stored message. As with 6036, how long an added column with a default takes depends on the backend and its version: on a large store, time it on a copy first |
| 6101 to 6102 | 6.3.4 |
MySQL and MariaDB: every table converted to utf8mb4 under utf8mb4_general_ci, InnoDB and ROW_FORMAT=DYNAMIC, and the database's default with them. SQL Server, SQL Server Compact and PostgreSQL only move the version |
On MySQL and MariaDB, the slowest step since 6025: every table is rebuilt, the message tables taking the time, with free disk of the order of the largest table. It needs MySQL 5.7.9 or MariaDB 10.2.2 and the ALTER privilege on the database, and it can stop on error 1062 - see 18.4c |
| 6102 to 6103 | 6.3.4 |
hm_accounts.accounttotpsecret widened to 1024 characters, to hold a sealed second factor (secrets at rest) |
Fast; accounts are few. Nothing is sealed by the step - see 18.4c |
So: from 6.2.24 or later, the upgrade is quick whatever the size of your store, with
the caveats of 6036 and 6101 on a very large hm_messages, 6072 on a very large
greylisting table, and - on MySQL and MariaDB only - 6102, which rebuilds every table.
From 6.2.21 or earlier it crosses 6012, 6014 and 6024, all three of which touch
every message row - which is the reason to do it in a maintenance window rather
than at lunchtime.
flowchart TD
A["Read hm_dbversion"] --> B{"Newer than<br/>this build needs?"}
B -- yes --> C["Refuse: downgrading a database<br/>is not supported"]
B -- no --> D{"Equal?"}
D -- yes --> E["Nothing to do; exit code 0"]
D -- no --> F["Build the whole path,<br/>checking every script file exists"]
F --> G["BEGIN TRANSACTION"]
G --> H["Run the prerequisites script<br/>MS SQL and PostgreSQL only"]
H --> I["For each step: EnsurePrerequisites,<br/>then run Upgrade<from>to<to><dialect>.sql"]
I --> J["Run the schema PROBES<br/>on the same connection,<br/>so they see the uncommitted DDL"]
J --> K{"Every probe satisfied?"}
K -- no --> L["ROLLBACK where the backend can,<br/>and report which step"]
K -- yes --> M["COMMIT"]
M --> N["Read hm_dbversion back"]
N --> O{"Equals the required version?"}
O -- no --> P["Report: the scripts ran without error<br/>but the database is at the wrong version"]
O -- yes --> Q["Success; exit code 0"]
The probe step exists because of a specific trap. Three of the four dialects mark
their ALTER TABLE statements [IGNORE-ERRORS] so that re-running a step is
harmless - but the update hm_dbversion statement in the same script carries no
marker and always runs. A marked ALTER that failed for a real reason would
therefore stamp the new version onto a database that does not have the column.
Each such step now has a probe that must be satisfied before the commit, and
build/check-schema-versions.ps1 proves that every step has one.
DBUpdater's exit codes, which is what a scripted install reads:
| Code | Meaning |
|---|---|
| 0 | Success, or nothing to do |
| 1 | The upgrade failed |
| 2 | A configuration error - unknown database type, missing script directory, or no path to the required version |
| 3 | Authentication was cancelled, or under /silent could not be satisfied |
-
Back up. Database, data directory and
hMailServer.INI. See chapter 15. The database half is the one that makes rollback possible; the data directory is the half that cannot be reconstructed from anything else. -
Write down two numbers. The server version (Help → About, or the Control Panel's About page) and the schema version. The Control Panel shows the schema on Server status; when the server will not start, read it directly:
select value from hm_dbversion;
hm_dbversionis a single-column, single-row table of its own - not a row in the settings table. If you ever open an issue, those two numbers are the first two questions. -
Estimate the outage from 18.2a. From 6.2.24 or later, a minute or two. From 6.2.21 or earlier on a large store, longer, because three steps touch every message row.
-
Pick a quiet time. Senders retry, so nothing is lost, but users will notice.
-
Know your administrator password. A silent upgrade needs it on the command line - see 18.4a.
-
(Upgrading 6.3.3 or earlier to 6.3.4.) Restart the service once after your last change to a
[Settings]key, before you upgrade. The new build's first start adopts the file's values only for keys the database has never held; for the rest it keeps the stored value, and a file line that differs from what the old server last recorded is treated as an edit - reported as HM5804 and put back. A 6.3.3 server records a change at its next start, and a 6.3.3 Control Panel running on the server writes the file directly, so a change made in the file or on that Control Panel and never followed by a restart would be undone. If HM5804 appears after the upgrade, set the named keys again in the Control Panel.
- Download the installer from the latest release
(
hMailServer-x64.exealways fetches the latest; for 6.3.4 the file ishMailServer-6.3.4-x64.exe). Once https://updates.progressiverobot.com serves HTTPS, the project's own link,https://updates.progressiverobot.com/download/hMailServer-x64.exe, always fetches the newest stable release's installer, never a pre-release's. - Run it as administrator. For 6.3.4 this is the only way: no server's live update installs it (18.4c).
- The installer detects the existing installation and offers to upgrade. Accept.
- Keep your existing database settings when asked.
- Let it finish, then confirm the service is running.
- Open the Control Panel and check About shows the version you just installed (6.3.4 today).
- Send a test message in and out, and read it back over IMAP or POP3. A service that starts is not the same as a server that delivers.
- Read the ERROR log for the window around the upgrade. An empty ERROR log is the signal you want.
$p = Start-Process -Wait -PassThru -FilePath .\hMailServer-6.3.4-x64.exe `
-ArgumentList '/silent', '/SUPPRESSMSGBOXES', '/adminpassword=<password>'
if ($p.ExitCode -ne 0) { throw "hMailServer upgrade failed with exit code $($p.ExitCode)" }-
With an administrator password set,
/adminpassword=is required. DBUpdater has to authenticate before it can move the schema. Without it the database upgrade fails and the installer reports the non-zero exit code rather than waiting - which is the fix for a real defect: until 6.2.23 a silent upgrade of an installation with a password set hung, on a modal password dialog nobody was there to answer. - With an empty administrator password, a silent upgrade works with no extra argument.
- The password must be at least five characters, or setup refuses it up front.
- (New in 6.2.28: the live update's helper passes a single-use
/upgradetoken=<hex>in place of the password, so an unattended self-update never handles the administrator password at all.) - (New in 6.3.3: the first live update started from an open Control Panel failed with Inno's exit code 5, and so did its rollback - the installer replaces the Control Panel's own files and, silent, aborts at the first one in use. The helper now ends whatever runs from under the installation before each installer run, the Control Panel closes itself once the helper has started, and a failed run's outcome quotes the installer's own log. Because the helper that runs an update is the one already installed, an installation on 6.3.1 or 6.3.2 has to be updated by hand once - close the Control Panel, run the installer - and live updates work from then on.)
New in 6.3.4. Three things change how an upgrade feels. The drain is done by the server
being stopped, so it first serves the upgrade from 6.3.4 to a later release; the upgrade
to 6.3.4 stops a 6.3.3 or older service the way it always did. UpgradeOnStart is read by
the binary that starts, so it can serve the upgrade to 6.3.4 itself.
-
The server drains before it stops. The SMTP, IMAP, POP3 and ManageSieve listeners
are closed at the socket - a connection attempted then is refused, which every sending
server treats as try again later - and the delivery queue is paused, so what is in
flight finishes and nothing new is accepted only to be cut. The webmail, CalDAV and
CardDAV, the REST API and the metrics listener stay up;
/readyzanswers503 not ready: draining. The live update drains first by itself; for maintenance of your own,POST /api/v1/server/drainenters the state,GETreports it andDELETEleaves it. SetShutdownDrainSeconds(30 is a reasonable figure) so that the stop waits for what is still in flight. -
The service can bring its own schema forward. With the new bootstrap key
[Database] UpgradeOnStart=1inhMailServer.ini, a service that finds the database older than its binary applies the upgrade chain itself as it starts - the same code ashmailserver --upgrade-database, one log line per step - and then carries on starting. It is for binaries replaced by something other than this installer (a deployment tool, a container, the second node of a pair). It is off by default, it backs nothing up first, and it belongs on one node at a time: set it on the node you start first. It is in the file, not the settings store, because it has to be read before the database is at a version the binary can open. -
A rolled-back update says so. When the live update's helper had to reinstall the
previous version, the outcome is reported when the service comes back - in the log, on the
status page and in
GET /api/v1/update- rather than only in the helper's file. On Linux the live update now applies too, through a root path unit that verifies the package again before installing (Installing on Linux).
What a single node still cannot promise is written in the repository's Upgrading.md: a restart is still a restart, and the listeners are closed while the binary is replaced.
6.3.4 (26 September 2026, build 43, schema 6103) is the first release built after GitHub suspended the project's account, and it is upgraded to differently from every release since 6.2.28.
Every server installs it by hand. 6.3.4 is Authenticode-signed but carries no Sigstore bundle: it was built and signed by hand, before the project's GitLab CI signing job existed. A server's live update verifies every installer against the Sigstore bundle published beside it, so a server of any version that is offered 6.3.4 reports Release 6.3.4 carries no Sigstore bundle for its installer, so it cannot be verified. and downloads nothing. So:
-
On Windows, run
hMailServer-6.3.4-x64.exeover the existing installation (18.4), or silently (18.4a). -
On Linux, install
hmailserver_6.3.4_amd64.debwithaptorhmailserver-6.3.4-1.x86_64.rpmwithdnf; the package runshmailserver --upgrade-databaseitself. 6.3.4's Linux packages are x86-64 only: it has no AArch64 package and no AppImage (the project has no ARM machine, and the AppImage tool is published only on GitHub). An AArch64 or AppImage installation stays on 6.3.3's packages or builds 6.3.4 from source (Installing on Linux).
A server from 6.2.28 to 6.3.3 still looks at GitHub for updates, which has answered
404 since 18 September 2026. To have its update check work again - and see 6.3.4 - put
exactly this line in the [Settings] section of its hMailServer.ini, then restart the
service:
[Settings]
UpdateFeedUrl=https://updates.progressiverobot.com/releases.jsonThe address without .json is a directory, and the server, which follows no redirect,
reports The update feed answered HTTP 302. Its live update can then install the
re-hosted releases up to 6.3.3, which carry the bundles GitHub Actions made; it cannot
install 6.3.4 (above), nor the first release signed on GitLab, which it does not trust.
Keep UpdateAutoDownload off on such a server until it is upgraded by hand, or it will
download a GitLab-signed installer at every check and refuse it. 6.3.4 reads the new feed
without being told and trusts both signers, so after the upgrade the line can go: clear it
in the Control Panel, with DELETE /api/v1/settings/ini/UpdateFeedUrl, or on Linux with
hmailserver --set-setting UpdateFeedUrl= - not in the file, where a changed line is put
back (HM5804). The whole account is the repository's
UpdateFeed.md.
The releases after 6.3.4 are to be signed by the project's own GitLab CI, for their v*
tag, and a 6.3.4 server's live update is to install them; none has been signed there yet.
A Windows service that runs as the virtual account NT SERVICE\hMailServer checks for
updates but cannot apply one: an administrator downloads the installer from the release page,
checks its Authenticode signature and runs it (the copy the server downloaded is not offered).
Take the backup first (18.3), and plan for the two steps that cost time (18.2a):
-
6101 adds two columns to
hm_messages, the table with a row for every stored message. On a large store it is not instant on every backend. -
6102, on MySQL and MariaDB, converts every table to
utf8mb4underutf8mb4_general_ci, InnoDB andROW_FORMAT=DYNAMIC, so that an emoji or any character outside the Basic Multilingual Plane is stored instead of refused. It rebuilds every table, so it takes about as long as copying the database, and needs free disk space of the order of the largest table. It needs MySQL 5.7.9 or MariaDB 10.2.2 or later (on an older server its first statement fails with error 1071 and nothing has been changed) and the ALTER privilege on the database itself, whichGRANT ALL PRIVILEGES ON <database>.*gives (without it, error 1044). It can stop on error 1062, naming an index and a value, where two rows the old collation told apart are one to the new - on MariaDB 11.8straßeandstrasein one address book, on MySQL 8.4 two names that differ only in an emoji: rename or remove one and run the upgrade again. Every statement of the step can be run twice, so a step stopped part of the way is finished by running it again. Afterwards atextcolumn may readmediumtext; the server reads both alike. SQL Server, SQL Server Compact and PostgreSQL only move the version.
What stays as it was on an upgrade, where a new installation differs:
-
The service account. A new Windows installation runs the service as
NT SERVICE\hMailServer; an upgrade keeps the account it has (LocalSystem, for an installation from before 6.3.4) unless the installer's Service account page, or/serviceaccount=virtual|localsystem|keep, says otherwise. - The secure defaults. Only a database the installation created gets the self-signed certificate, STARTTLS on 25, 587, 110 and 143 and TLS required for authentication on the Internet range; an upgraded database is left as it was, and the setup checklist's Passwords only over TLS item judges it.
-
Sealed secrets are off until an administrator makes the installation's
stored-secrets key:
hMailServer.exe --create-stored-secrets-keyfrom an elevated prompt (sudo hmailserver --create-stored-secrets-keyon Linux), then--export-stored-secrets-key <file>and keep the file apart from the backups, and--import-stored-secrets-keyon every other server that shares the database. Until then second factors and the[Settings]secrets are stored as written, as before, one application-log line at each start says so, and the setup checklist offers the step. The upgrade's own 6103 step only widens the column.
What changes on upgrade without a switch - the settings store, SPF per RFC 7208, the
audit trail and alerts, the IMAP answers, the database connection encrypted where the
database server offers TLS, the administrator password re-hashed at sign-in, a COM change
to a shared configuration object taking effect at Save(), and more - is listed in
Changes Since 6.2.10 section 5 and in the repository's
CHANGELOG.md.
Read it before you upgrade a server that scripts itself over COM or relies on how IMAP
answered.
Coming from 5.x or early 6.x, the administration GUI is different.
The classic hMailServer Administrator (hMailAdmin.exe) was retired in 6.2 and its
source removed in 6.2.10. It is replaced by the Control Panel (hMailCP.exe), which
does everything the old tool did - domains, accounts, aliases, distribution lists,
routes, rules, IP ranges, TCP/IP ports and SSL bindings, server settings, status, queue,
logs, backup, certificates, scripts and public folders - plus a live dashboard, complete
settings coverage and optional two-factor authentication.
- The navigation tree deliberately mirrors the old Administrator's layout, so muscle memory mostly transfers.
- Your TOTP secret carries over - the Control Panel uses the same one.
- The Control Panel needs the .NET 10 Desktop Runtime, which the installer installs silently if missing.
Coming from before hMailServer 5, hMailServer.ini moves. It used to live in
the Windows directory; the installer copies it to Bin\hMailServer.ini, leaves a
.old copy behind and deletes the original. The server reads only the one in
Bin.
The PHP WebAdmin is gone. It stored the administrator password in plaintext in a PHP session and needed DCOM opened up for the web server account. It was 2008-era unmaintained code. If you used it to administer the server remotely, use the Control Panel instead - it connects to a remote host directly, which is both simpler and safer.
"Administrative tools" means something narrower now. With both old front-ends gone, the component's only job is registering the COM type library so scripts on another machine can administer this server. If you do not script remotely, you do not need it.
The .NET runtime moved from 8 to 10 in the first release after 6.2.18. The
installer bundles windowsdesktop-runtime-10.0-win-x64.exe and installs it
/quiet /norestart, only when it is missing. Nothing about the server itself
changed - it is native code with no .NET dependency - so an upgrade that fails to
install the runtime still leaves you with a working mail server, just without the
Control Panel and the setup tools.
The server does not paper over a half-finished upgrade. On startup it compares the
database's version with the version it requires and refuses to run if they
differ, in either direction, reporting HM5011 with both numbers appended:
| Message in the ERROR log | What it means | What to do |
|---|---|---|
The database is too old for this version of hMailServer. Please run hMailServer Database updater (DBUpdater.exe) to upgrade it. Database version: N, Required database version: M |
The schema did not get all the way up | Run DBUpdater.exe from the installation's Bin folder. It picks up from whatever version the database actually reports, so a retry after a partial upgrade resumes rather than restarting. If it fails again, the log names the step it stopped on - that number is the useful thing to put in an issue |
The database is too new for this version of hMailServer. Please upgrade hMailServer. Database version: N, Required database version: M |
You are running an older server against a newer schema - usually an old binary left in place, or a reinstall of a previous release | Install the matching or newer server. Do not try to downgrade the database |
Database version could not be detected. (HM5010) |
hm_dbversion could not be read at all |
Check the connection details in [Database] and that the login can read the table |
DBSetupQuick.exe and DBUpdater.exe do the same job; the installer drives the
first, and the error message names the second because that is the one you run by
hand.
There is no downgrade path, and this is the one genuine sharp edge. The schema
upgrade is one-way: an older server refuses to run against a newer dbversion
rather than misinterpret it, which is correct - and means "just reinstall the old
version" does not work on its own.
- Uninstall the version you just installed.
- Install your previous version.
- Restore the database from your backup. This is the essential step.
- Restore
hMailServer.INIif you changed it.
Rolling back from 6.3.4 to 6.3.3 or earlier: the
[Settings] section the newer server kept current is a complete copy of its stored
settings, and an older server reads that file as the truth, so any setting changed after the
upgrade survives the rollback through the file even though the restored database predates it -
wherever the server could write that file, which a Linux package installation's unit does not
allow (Installing on Linux, step 7).
The [SettingsStore] section the newer server added is ignored by older builds and can be
deleted. A [SettingsOverride] section is ignored by older builds too, so a value only it
was supplying is lost - move it into [Settings]. Where the 6.3.4 server had sealed the
stored secrets (a stored-secrets key made, 18.4c), restore the pre-upgrade
hMailServer.ini with the database: the newer server writes the [Settings] secrets into
the file as HMK1: envelopes, which an older server reads as they are - its /Register
would hand ServiceAccountPassword's envelope to Windows as the service's password, and
the service would not start. Where you have no such copy, set those secrets again with the
older version's Control Panel once it runs, and run hMailServer.exe /Register again
(Upgrading.md).
The data directory does not normally need restoring - message files are not rewritten by an upgrade - but note the consequence: mail that arrived after the backup exists on disk with no metadata row, and will be invisible. That is what rollback costs, and it is why the cutover is worth doing in a quiet window.
This is why 18.3 step 1 is not optional. Without a database backup there is no rollback.
- Watch the project on GitLab to be notified of releases: the notification bell on https://gitlab.com/Progressiverobot/hmailserver, Custom, then New release.
- Every release lists exactly what changed and why, and carries SBOMs.
- Security fixes are called out explicitly in the release notes.
-
Let the server check for you (new in 6.2.28). With
UpdateCheckEnabled=1inhMailServer.INIthe server reads the project's release feed at startup and then everyUpdateCheckHours(default 24), compares the newest release onUpdateChannel(stable, the default, orprerelease) with its own version, and reports a newer one in the application log, on the Control Panel's Server status page, inStatus.UpdateState/Status.AvailableVersionover COM and inGET /api/v1/update. The check is off by default;Status.CheckForUpdateandPOST /api/v1/update/checkrun one on demand whether or not it is on. Every setting the feature reads has an editor on the Updates card of the Control Panel's API & monitoring page. The feed. Up to 6.3.3 the feed a server read by default was GitHub's API, which has answered 404 since GitHub suspended the project's account on 18 September 2026, so such a server's check reportsThe update feed answered HTTP 404.untilUpdateFeedUrlnameshttps://updates.progressiverobot.com/releases.json(18.4c). Since 6.3.4 the default ishttps://updates.progressiverobot.com, which GitLab Pages serves from the project's releases (UpdateChecker.cpp'sSTABLE_FEEDandPRERELEASE_FEED), and nothing needs setting. -
What the live update installs. It verifies every installer against its Sigstore
bundle and the signers compiled into the server. 6.3.4 carries no bundle, so no
server's live update installs it (18.4c); the releases after it are to be signed by the
project's GitLab CI, which a 6.3.4 server trusts beside the GitHub workflow that signed
every release up to 6.3.3 - at the project's present path, and at the path of the group
hmailserverit is to move to, where the certificate must carry the project's id. A server that still has the three GitLab valuesUpdateSigningIdentity,UpdateSigningIssuerandUpdateSourceRepositoryset to the GitLab signer's values, which let a server from 6.2.28 to 6.3.3 install a GitLab-signed release, trusts the present path alone and would refuse what is signed after the move: clear them once the new version is running (UpdateFeed.md). -
If this machine reaches the web through a forward proxy, set
HttpProxy=<host>:<port>(or[ipv6]:<port>) in the[Settings]section ofhMailServer.INI- empty, the default, means a direct connection. The update check, the installer download, and the JWKS and token-introspection fetches all go through it; anhttpstarget is reached withCONNECTand the TLS handshake runs inside the tunnel, so the certificate is verified exactly as it would be on a direct connection, and plainhttpis fetched by putting the absolute URL in the request line. A proxy that refuses is reported by name:The proxy <proxy> refused CONNECT to <host:port>:followed by the proxy's own status line. A value with no port is an error, and proxy credentials are not supported. The editor sits beside the feed URL on the Updates card. -
Since 6.3.4,
UpdateCheckEnabled,HttpProxyand the rest are stored in the database rather than read fromhMailServer.INI: use the Updates card,PUT /api/v1/settings/ini/{name}orhmctl ini set. Since 23 September 2026 such a save applies when it is saved - the update check reads its hours, window and channel when it runs - and the field says so. A value typed into the file for a key the database already holds is reported as HM5804 and put back at the next start.
For the mechanism behind all of this - what the chain does, why rollback is what it is, and how to move to a different machine or a different backend - see Upgrading Guide and Migrating the Database Backend.
hMailServer 6.3.8, the latest release (2 November 2026, schema 6108) · AGPL-3.0-or-later · Repository · Mirror on GitHub · Report a documentation error (a GitLab issue naming the page)
Home — full index
Start here
1. Install and run
- Before You Install
- Installing hMailServer
- Installing on Linux
- Running in a Container
- Profiles and Environment
- The Control Panel
- The Control Deck
- Your First Domain and Mailbox
- Connecting a Mail Client
- The Webmail
- DNS for Your Domain
- Migration
2. Secure it
- Encryption and Certificates
- Database TLS
- Encryption for People
- Stopping Spam
- Stopping Viruses
- Security Hardening
- Secure Defaults
- The Service Account
- Secrets at Rest
- Sign-in and Identity
- Security Policy
3. Operate it
- Monitoring and Health
- The SNMP Agent
- Backup and Restore
- Message Storage
- Compliance and eDiscovery
- Troubleshooting
- Diagnosing Stalled Mail
- Relocating an Installation
- Upgrading hMailServer
- Upgrading Guide
- The Update Feed
- Migrating the Database Backend
- High Availability Runbook
- Warm Standby
- Runbooks Digest
4. Extend it
- Rules and Sieve
- Aliases Lists and Public Folders
- Rooms and Equipment
- Mailing Lists
- Routes and Relays
- Mail In to a Webhook
- Outbound Shaping
- The COM API and Scripting
- Event Scripts
- The REST API
- Command Line
- 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
- Settings Reference
- Settings Overview
- Capabilities and Configuration
- Control Panel Reference
- Control Panel Pages
- Ports Reference
- Changes-Since-6210
- Version-History-to-6210
- Glossary
About this wiki
- hMailServer 6.3.8 is the latest release (2 November 2026, schema 6108)
- Pages say since 6.3.4 where a feature is new in it
- AGPL-3.0-or-later
- Repository, the one place the project is worked on
- Its mirror on GitHub, since 18 October 2026, with no Issues
- Questions: a GitLab issue with the Question template, GitHub Discussions Q&A, or the forum
- Report a documentation error: a GitLab issue naming the page