oximail setup brings the
server up: it generates the config, the DKIM key, the first organization, and the
admin account, writes a systemd unit, starts the service, escrows the server key,
and verifies it all with a doctor round. The wizard is the supported path for a
first boot. Everything it does can also be run from a setup file or as
individual subcommands for headless deployments.
This page documents what the wizard in OxiMail v0.30.0 actually does, step by
step, in the order it runs. Where a step is conditional or optional, that is
called out. Steps the wizard does not perform are listed at the end.
Running the wizard
/etc/oximail/oximail.toml. It needs to bind privileged ports and write under
/etc/oximail and /var/lib/oximail, so run it as root.
Flags:
Exit codes:
0 done, 1 done but the verification reported a degraded
point, 2 a hard failure (the verification failed, or a refusal before any
write), 130 cancelled by the operator (Escape then Quit, Ctrl-C, or declining
to overwrite an existing configuration), 143 stopped by SIGTERM. A cancelled
run is never 0: oximail setup … && systemctl start oximail stops there.
The wizard takes the whole screen on a capable terminal (a terminal on both
ends, TERM set and not dumb, at least 80×24): one dialog per question, the
steps listed on the left, the probes, the review and the engine’s output in the
body. Escape asks whether to quit, and quitting is clean: nothing is written
before the review is confirmed, and the terminal is given back on every exit.
Everything the full-screen wizard prints also goes to /var/log/oximail-setup.log
(the temporary directory when that file cannot be opened), so nothing the screen
showed is lost when it closes. While the screen is up, anything else written to
the terminal (a command the wizard runs, a notice from another part of the
program) goes to the same pane and log, never over the screen. --no-tui is the way out of a TERM that claims
more than the terminal can do; --config-file and the subcommands always run on
the line.
If /etc/oximail/oximail.toml already exists, the wizard warns and asks whether
to overwrite. Declining cancels setup and leaves the existing config untouched.
Interactive flow (primary mail server)
The wizard runs in steps: Checks, Identity, Topology, Configuration, Review, Apply, Start, Recovery, Doctor, Import. Every question comes before the review; nothing is written until the review is confirmed.Host checks and preflights
The wizard first measures the host and shows it: the distribution, whether systemd is the init system, the memory, the free space on the volume of/var/lib/oximail, the architecture. These are recommendations, never a
refusal: a distribution outside those tested (Debian 12 and 13, Ubuntu 24.04), less
than 2 GiB of memory or less than 10 GiB free are shown as warnings.
Then, before either path (interactive or --config-file), and before writing
anything, it runs three preflights. A dry run measures nothing, so it gates
nothing.
-
Clock. It reads the kernel clock state and refuses when no time source
is disciplining it: a mail server whose time is wrong fails silently and
late (inbound DKIM signatures refused as future-dated, ACME renewals and TLS
validity broken, token and fail2ban windows expiring wrong). In a container
the fix is on the host (chrony on the hypervisor);
timedatectlshowingNTP service: inactiveinside the container is expected.oximail setup --accept-unmanaged-clockcontinues anyway and records the acknowledgement in the wizard state. -
Volume. It walks the device-mapper stack under the data path and
refuses a bare volume: since ADR-148
the database, blobs and search index are written in clear, and the
encrypted volume is the only at-rest protection. It continues if the volume
is encrypted, continues without asserting anything if it cannot measure,
and otherwise prints the six-point reference configuration and stops.
oximail setup --accept-bare-volumelifts the refusal knowingly: the consent is written as[security] storage_encryption_attested = true, anddoctorkeeps measuring. -
Resolver. The server asks every DNS question to
unboundon127.0.0.1and believes a DNSSEC answer only once that resolver is proven. The wizard runs the server’s own probe: the identity setup wrote in unbound’s configuration, the root validated, an unsigned zone not, zones that do not validate refused, denials of existence proven. When it does not pass, setup refuses: DANE would be blind (see email authentication). The server itself never refuses to start for a resolver reason, but until its own probe has proven the resolver it believes no DNSSEC answer and holds the mail to DANE domains. There is no “continue anyway”. On Debian the wizard offers to install it:apt-get update,unboundwith its root trust anchor (dns-root-data), and the configuration carrying the box’s identity (an unbound already running keeps its own configuration; the file is added beside it). It then verifies the resolver (active, listening on loopback, trust anchor present, the probe passing) before touching/etc/resolv.conf, and ordersoximail.serviceafterunbound.service. The server never reads/etc/resolv.conf; the box’s other DNS readers do. The wizard rewrites it to name127.0.0.1only, saving the previous content beside it, where the rewrite sticks: a plain file, the systemd-resolved symlink, or a Proxmox container once setup has written/etc/.pve-ignore.resolv.conf. Under resolvconf, NetworkManager or cloud-init, which regenerate the file, it leaves it and prints that manager’s own step. A--config-filerun installs only withoximail setup --install-resolver. On a host withoutapt-get, setup prints the unbound configuration the probe recognises and where to put it (the file, its include,/etc/resolv.conf, thenoximail resolver check), keeping an identity already written. The configuration setup writes turns aggressive NSEC off (aggressive-nsec: no): with it on, a denial cached before a record was published hid that record for the zone’s whole negative TTL (1800 s at Cloudflare), past the five-minute cap. A box installed by an earlier binary keeps its old text:oximail doctornames it (resolver_config), andoximail resolver ensurenames the first line that differs, then with--applyrewrites the file (identity kept), checks it and restarts unbound (exit 0 current or rewritten, 1 a rewrite pending, 2 a failure;--json).
Identity and topology
-
Primary email domain (for example
example.com) — the domain part of your addresses. -
Mail hostname (defaults to
mail.<domain>). Both names are checked where they are typed: letters, digits and hyphens only (no hyphen at either end of a label), labels of 1 to 63 characters, at most 253 in all, at least two labels. They are lower-cased and a final dot is dropped; an internationalised name is converted to its ASCII form (bücher.examplebecomesxn--bcher-kva.example). A setup file andoximail setup dnsare held to the same rules. -
Deploy mode:
Primary mail server,Backup MX, orSmarthost relay (dedicated hub). (Backup MX and the smarthost hub each have their own flow, described later.) -
Deployment topology:
Direct(OxiMail handles TLS on port 443) orBehind reverse proxy(Caddy, Nginx, and so on). Choosing the proxy option also asks for a local base port (default8080; OxiMail’s ACME challenge listener binds that port + 1, and a port typed wrong is asked again, never replaced by a default) and for the system group of the reverse proxy (proposed:caddy, elsewww-data). The proxy reaches OxiMail’s web surface through a Unix socket only that group may open, never a TCP port: on a loopback port every process of the machine could claim a client address inX-Forwarded-For, get someone banned by fail2ban or claim atrusted_ipsexemption.
Configuration
- Cloudflare API token (leave empty to configure DNS manually).
- Admin email (defaults to
admin@<domain>) and, for a direct deployment, the ACME (Let’s Encrypt) email (defaults to the admin email). Behind a reverse proxy the ACME email is not asked (it isadmin@<domain>), but ACME stays on: OxiMail orders the certificate of the mail ports, and the proxy forwards the HTTP-01 challenge to it. - Outbound send mode:
DirectorThrough a relay. The wizard first measures what direct delivery would face: whether outbound port 25 is open, the reverse DNS of the public IP (missing, not forward-confirmed, or a provider’s default name counts as weak), and whether the IP is already on a public blocklist. It recommends a relay when any of the three is against direct delivery (most clouds block the port). Outbound port 25 is shown for IPv4 and IPv6 separately, with the reverse DNS of the IPv6 address: mail leaves over IPv4 first, and over IPv6 only toward a destination without IPv4. When the box’s IPv4 exit is a shared NAT address that is not its own (see the DNS step), a relay is recommended: no SPF record can authorise that address. See Outbound relay & smarthost for the whole relay workflow, including the exit preflight for cutting over to direct later. This question is about the leaf side (sending through a relay) and is skipped on the smarthost-hub flow — the two ends of a relay never conflate. - Default time zone of the organization (an IANA name). This is the last
tier of the calendar fallback chain (event zone → user zone → organization
zone → UTC), so floating events resolve in local time instead of UTC. For a
primary server it is asked once, after the country of establishment: the
default is the host’s OS zone when it is a precise one, else the zone of
that country (a rented server usually says UTC), else UTC; an unknown zone
is asked again. On the smarthost flow it is asked here, defaulting to the
host’s zone. Change it later with
oximail tenant update --default-timezone. - Webmail origins on another host (empty for none), written to
[server] allowed_origins. - Reports:
Send reports? [y]es / [c]rashes only / [n]o / [d]etails, with no answer preselected: Enter alone asks again,dshows what is sent. The answer is recorded before the config is written; see community telemetry. - How the server will be backed up:
Hypervisor snapshot(the default),oximail backupon a schedule, orLater. Nothing here arranges a backup; the answer is written as[backup] strategy(hypervisor-snapshot,oximail-backupornone), andoximail doctorreports a failure until a plan is declared, or while a scheduledoximail backuphas not run (backups).
oximail.toml:
the wizard asks it after these questions and shows it in the review, and it is
written with the core file.
Review
The wizard probes the ports this configuration will bind (see port preflight), then shows the whole configuration as a setup file would carry it, secrets named but never shown, and offers three choices: apply, save the answers as asetup.toml, then apply (the file,
mode 0600, carries no secret: replay it with oximail setup --config-file once
the *_file references it names are in place), or abort, which leaves nothing
written.
Step: port preflight check
The wizard tries to bind each required port locally and reportsOK or FAIL
with the reason (permission denied, already in use, cannot bind). The required
ports are:
When you chose “Behind reverse proxy”, ports 80 and 443 are skipped (the proxy
owns them). Blocked ports do not stop the wizard, but the server may not work
until they are opened in your firewall. A port already in use names the
process that holds it and its systemd unit, with the command that frees it,
and the review then pre-selects abort; the ports OxiMail’s own service
holds on a re-run are not reported as blocked.
Step: data directories
The wizard creates/var/lib/oximail, /var/lib/oximail/blobs,
/etc/oximail, /etc/oximail/dkim, and /etc/oximail/tls. There is no
/var/log/oximail: the server’s logs go to stdout and are captured by journald.
Step: DNS
The wizard establishes which public addresses are the server’s own, then checks the domain’s DNS records and prints a pass/fail report.- IPv6: a global IPv6 address is carried by the box itself and is taken as detected.
-
IPv4: the address seen from outside is the server’s only when it is on
one of its interfaces, or when a forward to this box is proven. Two proofs,
both read-only: a connection to that address on port 25 comes back to the box
with a one-time token (NAT loopback), or the router’s UPnP table maps port 25
to the box. Nothing is ever written on the router. When nothing proves it,
the wizard asks whether the router forwards the mail ports to this machine,
“no” by default. For a shared exit (a NAT used by other services), no
Arecord and noip4:in SPF are published: the server is published and authorised over IPv6 only. - If no address can be detected, the wizard asks you to type one.
-
With a Cloudflare token, it can create the missing records via the
Cloudflare API: A, MX (priority 10), SPF, DMARC, MTA-STS (TXT + CNAME),
TLS-RPT, autoconfig and autodiscover CNAMEs, a CAA record restricting issuance
to Let’s Encrypt, and SRV records for client autodiscovery (
_autodiscover,_imaps,_submission,_caldavs,_carddavs). If a conflicting MX record exists it asks before replacing it. An SPF or DMARC record already published is never doubled (two SPF records make every receiver’s check fail): an identical one is left, a different one is shown. For SPF the wizard proposes a single record that keeps your mechanisms and youralland adds this server’s, and publishes it only if you accept; a DMARC record is replaced only if you accept. It then re-checks DNS, after emptying the local resolver’s cache of the zone (unbound-control flush_zone): the check before publishing had cached “no such name”, and the re-check and the end-of-install verification used to call missing, for up to five minutes, the records just published. The token is also written to/etc/oximail/cloudflare.token(mode0600, owned by the service user), and the config gets[dns] provider = "cloudflare"with that file and the zone id, resolved through the API, so the server republishes the DANE TLSA records at every certificate issuance and renewal. -
Without a token, it prints the check results and tells you to create the
records yourself; the final summary reminds you to publish the TLSA records
after each certificate (
oximail dane publish).
Step: DKIM
The wizard generates an RSA DKIM keypair (selectordefault) at
/etc/oximail/dkim/<domain>.default.key and prints the DNS record to publish.
If a Cloudflare token was supplied, it also publishes the DKIM TXT record at
default._domainkey.<domain> (deleting any stale record for that selector
first). The published key is in SPKI format (v=DKIM1; k=rsa; p=...).
The Cloudflare DKIM publish only happens inside the full wizard. Domains added
later, or setups where no Cloudflare token was provided, have no DKIM TXT
record published automatically — generate and publish those manually (see
DKIM key generation below).
Step: configuration file
The reports answer is recorded first, in the consent marker next to the installation id (/var/lib/oximail/.telemetry_consent.json). The wizard then
writes /etc/oximail/oximail.toml for a primary server. Key contents:
[server]hostname, base URL, bind addresses (direct:0.0.0.0:443/0.0.0.0:80; proxy:bind = "unix:/run/oximail-http/jmap.sock"andhttp_bind = "127.0.0.1:<port + 1>"for the ACME challenge, with notrusted_proxies, since the peer on the socket is the proxy and itsX-Forwarded-Foris the client), and the admin surface on127.0.0.1:9090.[storage]SQLite at/var/lib/oximail/data.db, blobs at/var/lib/oximail/blobs(plain SQLite; the at-rest protection is the volume).[security] storage_encryption_attested = trueonly if you ransetup --accept-bare-volume: the wizard measures the volume under the data path and refuses a bare one without that consent.[auth] default_tenant = "default".[mode] role = "primary".[smtp]bind ports plus a[[smtp.dkim_keys]]block for the domain/selector.[tls]with ACME on, in both topologies (behind a proxy it serves the mail ports; the proxy keeps the certificate of the web names). On a direct boxacme_domainsholds the hostname, with themta-sts.,autoconfig.andautodiscover.names below it, commented: an ACME order fails as a whole on a name that does not resolve yet.[legacy]IMAP/CalDAV/CardDAV enabled.[spam],[security](fail2ban + trusted IPs),[rate_limit], and[logging](JSON to journald).[metrics] dnsbl_self_ipswith the public IP the wizard detected, sodoctorwatches the sending address on the blocklists.[dns](with a Cloudflare token, above).[admin] metrics_token_file = "/etc/oximail/metrics.token": the wizard creates a random token for/metrics, so the administration token no longer opens the metrics (operations).[backup] strategy, the declared plan.[telemetry] enabled,falsewhen you answered no; the comments point to the public page listing every field of the report and tooximail telemetry purge.
127.0.0.1:<port + 1>, everything else to
unix//run/oximail-http/jmap.sock) and notes that SMTP (25, 587, 465) and IMAP
(993) bind directly and are not proxied. It also says what the proxy is: it
terminates TLS for the names it serves and reads the web traffic in clear, so
if another entity runs it, that entity is a sub-processor of the data it
carries. The snippet proxies everything to OxiMail, with the sole
exception of the ACME HTTP challenge path — a route allowlist is deliberately
avoided, because one missing route family silently breaks client discovery or
DAV. When the webmail is served from a different origin, the wizard also fills
[server] allowed_origins; CORS is answered by OxiMail itself, never by the
proxy (a blanket OPTIONS interception at the proxy silently breaks DAV client
discovery). To tune any of these values afterwards, see
Configuration.
Step: organization + admin account
The wizard creates the first organization (a tenant in the storage model: iddefault, name Default, domain derived from the admin email) and then the
admin account. Creation is idempotent: if it already exists the wizard reports
so and continues.
You are prompted for the admin password, with confirmation. The password
must be at least 8 characters and include an uppercase letter, a lowercase
letter, and a digit; the prompt loops until it is strong enough. The account is
created with the admin role and language fr, and the organization gets the
time zone asked earlier.
Step: systemd unit
The wizard writes/etc/systemd/system/oximail.service. The unit runs
oximail serve --config /etc/oximail/oximail.toml, restarts on failure, raises
the file-descriptor limit, and sends output to journald under the oximail
identifier. If it cannot write the file (not root), it prints the unit content
so you can install it manually. See Installation for the unit
details. Behind a proxy it also writes /etc/tmpfiles.d/oximail-http.conf and
creates /run/oximail-http at once (setgid, the proxy’s group; tmpfiles
recreates it at every boot), and the unit’s ReadWritePaths gains that
directory. A group that does not exist makes systemd-tmpfiles fail: the
wizard says so with the line to fix.
It also writes oximail-health.service and oximail-health.timer, which record
the doctor round every 15 minutes, and prints the command that enables them
(systemctl enable --now oximail-health.timer); see
the doctor round.
The unit is written after the organization and admin step, because that step creates the database and the wrapped server key, and the unit declares LoadCredentialEncrypted= only for a key that exists: written earlier, it lacked the directive and the service could not unwrap its own key. The service user is created before the first file handed to it, and the data is handed to it last (data.db* in mode 0600), once everything is written, so a fresh install starts. The wizard says “owner oximail” only after that hand-over succeeded.
Step: start the service
The wizard runssystemctl daemon-reload, systemctl enable oximail, and
systemctl start oximail, then waits up to 90 seconds until systemd reports the
service active and port 443 answers (the ACME certificate is provisioned in that
time). When the service is not running, it says so with the state systemd
reports and quotes the unit’s last error line; it blames ACME only when the
service runs and port 443 is still waiting.
On a direct box it then activates, in acme_domains, each of the mta-sts.,
autoconfig. and autodiscover. names that already resolves to the box (the
DNS step published them minutes before) and has the certificate reissued: the
running server holds port 80, so it is restarted and orders the names itself,
keeping the certificate it serves if that order fails; the wizard waits up to
90 seconds for the new certificate and says which happened. A server that is
not running leaves the port to oximail cert renew --apply. The names still
waiting for DNS stay commented, and the summary names them.
The start is a systemctl restart: on a re-run over an installed box, the
service picks up the configuration just written. The ports it holds are not
reported as blocked, and the topology step recognises its own service on port
443 instead of guessing a reverse proxy.
Step: verification
It connects to ports 25, 587, 993, and 443, checks the JMAP endpoint athttps://<hostname>/.well-known/jmap (a 401 counts as up), and confirms the
DKIM directory and config file exist. A failed verification does not end the
run: the recovery, doctor and import steps still happen, the summary says the
verification failed, and the run exits with its code at the end.
Step: recovery
The server has just created its root key; the wizard escrows it. It shows the recovery key once (full screen on a capable terminal, never written to the setup log), asks you to type it back, which proves it was stored and is the recovery drilldoctor ages, and ends with the scp command that takes the
escrow blob off the box. A wrong key is asked again. The plaintext copy of the
key that waited for the escrow is removed only once the key was typed back;
leaving keeps the escrow and that copy, says the key was not confirmed and
names oximail key purge-pending, which checks the key and then removes the
copy. oximail key escrow --guided replays the
same ceremony on a box installed without it, and oximail doctor reports a
failure on a server with no escrow (encryption at rest).
Step: doctor
The last check is a fulloximail doctor round with --reachability: the web
names and their certificates, this box on the blocklists, and the rest of the
round, shown as rows with their tally.
Step: optional data import
Finally the wizard offers to import existing mail. Choices: Skip, From IMAP server (Dovecot, Exchange, Gmail, any IMAP4 server), From Stalwart (JMAP API), or From mbox/Maildir files. Each choice prompts for the relevant connection details. This step runs after the server is already configured and running, so a failed import does not break the install. You can retry any time withoximail migrate. See Migration for
the standalone import workflow.
The summary
The wizard ends with what outlives the screen: the login URL (https://<hostname>/auth/login), the config path, the log command, the outcome
of the recovery step, the certificate names activated or still waiting, and what
remains to do: DANE (automatic with the Cloudflare provider, oximail dane publish otherwise), the metrics endpoint and its token file for Prometheus,
oximail doctor --reachability with the round’s tally, and the backup plan
(for a hypervisor snapshot, the vzdump hook that confirms each run with
oximail backup mark --source hypervisor --apply).
Its last lines say how a device gets its settings. On an iPhone, iPad or Mac,
open https://<hostname>/apple/mobileconfig?email=<address> and install the
profile the server serves, pre-filled with the administrator’s address (the
address is percent-encoded in the link, since a + would read as a space; the
profile is not signed). Thunderbird and Outlook need only the address: they
find the settings themselves through autoconfig and autodiscover
(autodiscovery).
Backup MX flow
If you choose Backup MX as the deploy mode, the wizard runs a dedicated flow through the same engine as a setup file: it asks for the primary MX hostname (defaults tomail.<domain>), additional relay domains, the forwarding
mode (queue to accept locally and retry to the primary, with DSN bounces on
permanent reject; or proxy to relay in real time and propagate the primary’s
reply, falling back to queue if the primary is unreachable), the admin email, a
Cloudflare token, and the reports question. After the review it creates the
admin account, writes a role = "backup" config, installs the systemd unit,
optionally creates its DNS records through Cloudflare (its MX beside the
primary’s, priority 20, and its A and AAAA, each read first and never
doubled), and verifies port 25 and the config.
A backup MX signs nothing for the domain and points no client at itself, so
the flow runs no DKIM step and publishes no client SRV record: the
domain’s DKIM key and its _imaps / _submission records are the primary’s.
An earlier wizard generated a key at the default selector and, with a
Cloudflare token, replaced the primary’s public key with it, so every message
the primary signed then failed DKIM. Without a token, the printed instructions
list the same records (MX, A, AAAA) and say why there are no others.
Smarthost flow
If you choose Smarthost relay (dedicated hub), the wizard runs the same primary flow with curated deltas — a smarthost is a full outbound MTA, not a separate install mode (there is deliberately no dedicated boot path). What changes:- You are asked for the hub’s egress IP(s) (comma-separated). Only IP literals are accepted: they feed the DNSBL self-monitor and the published SPF record, where a hostname or a typo would silently break every client’s SPF.
- The generated config declares
role = "smarthost"under[mode]. The role closes the mailbox surfaces and adds no boot path of its own; at every boot the server warns (as a boot-audit issue your monitoring can alert on) if[legacy]is still enabled or the DNSBL self-monitor is empty. A combined mailbox-plus-hub server staysrole = "primary". - Mailbox surfaces are curated off (
[legacy] enabled = false) and[metrics] dnsbl_self_ipsis filled with the egress IPs. - The hub SPF record clients will include (
spf.<domain> TXT "v=spf1 ip4:… -all") is published through Cloudflare when you provided a token, or printed for manual publication. The name belongs to the hub: it is read first, an identical record is left, a differentv=spf1one is replaced, and a second is never added (two SPF records at one name are a permerror for every domain that includes it). - No relay account is created. Relay clients enter through minted tokens
only; the wizard prints the
oximail account relay mint-tokencommand as the next step. See Outbound relay & smarthost for the whole hub workflow.
Non-interactive setup
For headless or automated installs, write a setup TOML file and pass it with--config-file:
config/setup.example.toml, the reference of every key
the file accepts with its meaning; an unknown key is refused. The file has a
[server] section (domain, hostname, optional deploy_mode = "primary",
"backup" or "smarthost", topology = "direct" or "proxy", proxy_port,
webmail_origins, proxy_group (the reverse proxy’s system group, detected
when absent), public_ipv4 = "auto" | "forwarded" | "none" for a box
behind a NAT, and the required telemetry = "yes" | "crashes" | "no"),
plus optional [dns] (cloudflare_api_token_file or cloudflare_api_token_env,
cloudflare_zone_id, replace_existing_mx, replace_existing_spf,
replace_existing_dmarc), [tls] (acme_email), [admin]
(email plus password_file or password_env, and an optional
default_timezone), [relay] or [relay_enrolment] for a leaf that sends
through a relay, [backup_plan] (strategy), [import]
(source = "imap" | "jmap" | "mbox" | "skip" with the matching connection
fields), for backup mode, [backup] (primary_hostname, accepted_domains,
mode, config_output_path), and, for the smarthost flow, [smarthost]
(egress_ips = ["203.0.113.25", …] — validated as IP literals, fail-loud). An
extension’s section is given as [extension.<table>].
A committed setup file carries no secret: an inline [admin] password,
[dns] cloudflare_api_token, [relay] password or [relay_enrolment] token is
refused before anything is written. A question the file does not answer is
refused by name, never prompted on a pipe: a file without [server] telemetry
stops the run and names the key, and so does a box behind a NAT whose file
leaves public_ipv4 on "auto" when nothing proves the forward. A
cloudflare_api_token_file the service cannot read (under /root, /home or
/tmp, which its sandbox hides, or not readable by the oximail user) is not
written into the server configuration: the token is copied to
/etc/oximail/cloudflare.token (0600, owner oximail), and the run says so.
The run starts the service, then verifies it, as the interactive wizard does.
With --no-start it writes everything, starts nothing and skips the
verification, saying so.
The same steps run without prompts. If there is no [admin] section, the wizard
skips admin creation and tells you to create one with oximail account create.
Passwords passed non-interactively are still strength-checked, and a weak one
aborts the run. Nobody is there to receive a recovery key, so the escrow is not
created: the run says so as its last word, and oximail doctor reports a
failure until oximail key escrow --guided has run.
Use --dry-run with any of these to preview the generated config and actions
without writing, starting or verifying anything.
Individual setup subcommands
Every wizard step is also a standalone subcommand underoximail setup, useful
for re-running one piece:
--dry-run.
oximail setup dns also runs without a terminal, so a script, an agent or
ssh without a tty can republish a box’s records. Flags answer every question
it can ask: --replace-mx, --replace-spf (the existing SPF is merged when it
can be, your mechanisms kept), --replace-dmarc, and --public-ipv4 (auto
measures and asks when nothing proves the address is the box’s, which is
refused without a terminal; forwarded says the router forwards the mail
ports here; none publishes IPv6 only). An undecided public IPv4 stops the
run before anything is read or published, naming the flag; a replace question
no flag answered keeps the existing record and is named at the end.
Run again on a configured zone, it doubles nothing. It reads the zone first
and creates a record only where the zone holds none of its kind: a second
_mta-sts TXT (which leaves the domain with no MTA-STS policy, RFC 8461 §3.1)
or a second TLS-RPT record can no longer appear, and an MTA-STS record is kept
whatever its id. MX records naming another server that you keep are the
domain’s only MX: this server’s is not added beside them. The zone decides,
not the checks, since a check reads a resolver cache that can lag behind a
record just published. --dry-run reads the zone and lists each record: would
create, already there, or differs and kept. The run ends naming the checks
still failing. Exit codes: 0 done, 1 something left (a record kept for want of its
flag, a check failing), 2 the public IPv4 left undecided.
DKIM key generation
There is also a top-level DKIM command (separate fromoximail setup dkim),
which is the one referenced in the Quick Start:
This generates the keypair under
/etc/oximail/dkim/ and prints the DKIM DNS
record to publish. It does not publish the record to Cloudflare; only the
full wizard does that, and only when a token is supplied. Publish the printed
v=DKIM1; k=rsa; p=... value at <selector>._domainkey.<domain> yourself.
Starting and verifying manually
If you skipped the wizard’s start step, or installed the unit by hand:What the wizard does not do
- It does not install the binary. Get
oximailonto the host first; see Installation. - It does not publish DNS without Cloudflare. With no Cloudflare token it prints the records (or check results) for you to add at your provider.
- It does not publish DKIM outside the full wizard. The standalone
setup-dkimandsetup dkimcommands generate and print the record but do not push it to DNS. - It does not configure ed25519 DKIM. Only RSA-2048 keys are generated and loaded at runtime.
- It does not manage your firewall. The port preflight only reports blocked ports; opening them in the VPS firewall or security group is manual.
- It does not arrange the backup. It records the plan you declare; the
hypervisor or a scheduled
oximail backuptakes the copies, anddoctorchecks that they happen. - It does not create extra organizations or accounts. It provisions exactly
one
defaulttenant and one admin account. Add more withoximail tenant createandoximail account create.
Next steps
- Configuration — tune the values the wizard wrote.
- TLS & ACME — certificate handling, direct and behind a proxy.
- Email authentication & security — SPF, DKIM, DMARC details.
- Migration — import mail from an existing server.
- Operations — backups, the doctor round, telemetry.
- CLI reference — the full command list.