Docker container setup (using Porkbun for automatic DNS) on Debian 13 results in broken HTTPs, no logs

Issue Description

I am carefully following Docker | Stalwart to install Stalwart on a freshly-provisioned Debian 13 VM [1]. Setup goes smoothly, with me choosing the default options in the wizard of everything except

  • host/domain name (page 1)
  • My choosing automatic DNS management via Porkbun. I believe that Porkbun is set up correctly, with an API key and with API access enabled for the domain in question

However, when the instructions request me to visit https://my-hostname/admin I receive an error page in Firefox with the complaint that

mailbox.12rocks.io uses an invalid security certificate.
 
The certificate is not trusted because it is self-signed.
 
Error code: MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT
 
View Certificate

I know that stalwart is up: I can access http://my-hostname:8080/admin/ just fine. However logs are completely uninformative, with docker logs stalwart only reporting logs until the restart I issued as part of the guide (side note: where the hell are the logs???)

mailbox# docker logs stalwart

════════════════════════════════════════════════════════════
🔑 Stalwart bootstrap mode - temporary administrator account

   username: admin
   password: [REDACTED]

Use these credentials to complete the initial setup at the
/admin web UI. Once setup is done, Stalwart will provision a
permanent administrator and this temporary account will no
longer apply.

This password is shown only once. To pin a credential
instead, set STALWART_RECOVERY_ADMIN=admin:<password> in the
env file.
════════════════════════════════════════════════════════════

2026-06-01T04:02:17Z WARN Server started in bootstrap mode (server.bootstrap-mode) hostname = "e264acdf3a86", details = "No configuration file was found. Port 8080 is open for initial setup.", version = "0.16.7"
2026-06-01T04:02:18Z INFO Application resource updated (resource.application-updated) url = "https://github.com/stalwartlabs/webui/releases/latest/download/webui.zip", details = "Stalwart Web Interface"
2026-06-01T04:02:18Z INFO Network listener started (network.listen-start) listenerId = "http-recovery", localIp = ::, localPort = 8080, tls = false
2026-06-01T04:08:30Z ERROR Authentication error (auth.error) listenerId = "http-recovery", localPort = 8080, remoteIp = 172.17.0.1, remotePort = 38976, details = "Failed to decode token. If you are using an external OIDC provider, make sure it is configured as the default directory under the Authentication object.", causedBy = "crates/common/src/auth/oauth/token.rs:210", reason = "aead::Error", causedBy = "crates/http/src/auth/oauth/token.rs:235", details = "Failed to validate refresh token"
2026-06-01T04:08:36Z INFO Authentication successful (auth.success) listenerId = "http-recovery", localPort = 8080, remoteIp = 172.17.0.1, remotePort = 38976, accountName = "admin"
2026-06-01T04:14:03Z INFO Shutting down Stalwart Server (server.shutdown) causedBy = "SIGTERM"

Obviously TLS is broken: at a total guess, I suspect that it might have something to do with Porkbun integration being broken: I don’t see any records created for the relevant domain

Expected Behavior

I should be able to navigate to https://my-domain/admin without error like the guide instructs

Relevant Log Output

As mentioned earlier, docker logs stalwart is useless. From inside the container, nothing in /var/lib/stalwart/LOG appears to be interesting either (though it’s quite noisy so who knows?)

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Here is a breakdown of what is happening and how to fix it:


What went wrong

There are two separate issues here: the self-signed cert, and the missing logs.

1. Self-signed certificate / ACME not running yet

After the setup wizard completes and you restart the container, Stalwart needs to complete an ACME DNS-01 challenge against Let’s Encrypt to get a real certificate. Until that challenge succeeds, it serves a self-signed cert. The self-signed cert is expected initially - it is not a permanent failure.

For the DNS-01 challenge to succeed with Porkbun, two things must be in place after the wizard runs:

  • A DnsServer object of type Porkbun must exist in the WebUI under Settings › Network › DNS › DNS Providers, with both apiKey and secretApiKey filled in. The wizard creates this from what you enter in Step 5, but confirm it is actually there.
  • The Domain object for your hostname must have dnsManagement set to Automatic, pointing at that DnsServer. This is what authorises Stalwart to publish the _acme-challenge TXT record that Let’s Encrypt checks.
  • The AcmeProvider object must have challengeType set to Dns01 (not TlsAlpn01). When you select automatic DNS in Step 5, the wizard should configure this automatically, but it is worth checking in the WebUI under Settings › TLS › ACME Providers.

If any DNS records were not created in Porkbun, that is the symptom of one of the above not being wired correctly. Check all three objects through the WebUI (accessible on port 8080 with your admin credentials).

2. docker logs stops after the restart

This is a logging configuration issue. During bootstrap mode the server logs to the console (which docker logs captures). After the wizard completes and Stalwart restarts with a full configuration, if the wizard’s Step 4 was left on Log File instead of Console, all post-restart logs go to /var/lib/stalwart/LOG/ inside the container - not to stdout - so docker logs shows nothing new.

The docs are explicit about this:

Docker users should select Console here. A file-based logger inside a container writes to an ephemeral filesystem that is lost on every container restart; the Console logger emits structured log lines to standard output, which Docker captures through its log driver.

Fix: in the WebUI go to Settings › Telemetry › Tracers and change the tracer destination to Console. After saving, you will see live ACME events (including any errors) via docker logs stalwart -f. Look for acme.auth-start, acme.auth-valid, acme.error events which will tell you exactly what is failing.

Relevant docs

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Good bot: thanks for cluing me in about the logs.

I checked in the UI, the DnsServer/Domain/AcmeProvider all appear to be configured correctly.

Now, docker logs shows something more interesting

```

2026-06-01T06:03:51Z INFO DNS record created (dns.record-created) hostname = "mailbox.mydomain.com.", details = "mydomain.com", type = "TXT", value = ["v=spf1 a -all"]                 
2026-06-01T06:03:51Z INFO DNS record created (dns.record-created) hostname = "ua-auto-config.mydomain.com.", details = "mydomain.com", type = "CNAME", value = ["mailbox.mydomain.com."]
2026-06-01T06:03:52Z INFO DNS record created (dns.record-created) hostname = "mta-sts.mydomain.com.", details = "mydomain.com", type = "CNAME", value = ["mailbox.mydomain.com."]
2026-06-01T06:03:52Z INFO DNS record created (dns.record-created) hostname = "_ua-auto-config.mydomain.com.", details = "mydomain.com", type = "TXT", value = ["v=UAAC1; a=sha256; d=ijP
fREOc2Vvihl4sxEUPtk7HPCbOO4fINCxrbj50yGw="]
2026-06-01T06:03:53Z INFO DNS record created (dns.record-created) hostname = "_imaps._tcp.mydomain.com.", details = "mydomain.com", type = "SRV", value = ["0 1 993 mailbox.mydomain.com."
]
2026-06-01T06:03:53Z INFO DNS record created (dns.record-created) hostname = "_caldavs._tcp.mydomain.com.", details = "mydomain.com", type = "SRV", value = ["0 1 443 mailbox.mydomain.com
."]
2026-06-01T06:03:53Z WARN No TLS certificates available (tls.no-certificates-available) total = 0
2026-06-01T06:03:53Z INFO DNS record created (dns.record-created) hostname = "_dmarc.mydomain.com.", details = "mydomain.com", type = "TXT", value = ["v=DMARC1; p=reject; rua=mailto:po
[email protected]"]
2026-06-01T06:03:54Z INFO DNS record created (dns.record-created) hostname = "mydomain.com.", details = "mydomain.com", type = "TXT", value = ["v=spf1 mx -all"]
2026-06-01T06:03:55Z INFO DNS record created (dns.record-created) hostname = "_mta-sts.mydomain.com.", details = "mydomain.com", type = "TXT", value = ["v=STSv1; id=4460418528456974238
"]
2026-06-01T06:03:55Z INFO DNS record created (dns.record-created) hostname = "_jmap._tcp.mydomain.com.", details = "mydomain.com", type = "SRV", value = ["0 1 443 mailbox.mydomain.com."]
2026-06-01T06:03:56Z INFO DNS record created (dns.record-created) hostname = "_smtp._tls.mydomain.com.", details = "mydomain.com", type = "TXT", value = ["v=TLSRPTv1; rua=mailto:postma
[email protected]"]
2026-06-01T06:03:56Z INFO DNS record created (dns.record-created) hostname = "autodiscover.mydomain.com.", details = "mydomain.com", type = "CNAME", value = ["mailbox.mydomain.com."]
2026-06-01T06:03:57Z INFO DNS record created (dns.record-created) hostname = "_carddavs._tcp.mydomain.com.", details = "mydomain.com", type = "SRV", value = ["0 1 443 mailbox.12rocks.i
o."]
2026-06-01T06:03:57Z INFO DNS record created (dns.record-created) hostname = "autoconfig.mydomain.com.", details = "mydomain.com", type = "CNAME", value = ["mailbox.mydomain.com."]
2026-06-01T06:03:57Z WARN Task failed during processing (task-manager.task-failed) id = 310407389180329984, details = "DnsManagement", reason = "Failed to set DNS RRSet for _pop3s.
_tcp.mydomain.com./SRV: Failed to set DNS RRSet: API error: HTTP 503: <html>\r\n<head><title>503 Service Temporarily Unavailable</title></head>\r\n<body>\r\n<center><h1>503 Service T
 emporarily Unavailable</h1></center>\r\n<hr><center>openresty</center>\r\n</body>\r\n</html>"
2026-06-01T06:04:11Z ERROR Authentication error (auth.error) listenerId = "http", localPort = 8080, remoteIp = 172.17.0.1, remotePort = 59360, details = "Failed to decode token. If
 you are using an external OIDC provider, make sure it is configured as the default directory under the Authentication object.", causedBy = "crates/common/src/auth/oauth/token.rs:2
10", reason = "aead::Error", remoteIp = 172.17.0.1
2026-06-01T06:04:16Z ERROR Authentication error (auth.error) listenerId = "http", localPort = 8080, remoteIp = 172.17.0.1, remotePort = 59360, details = "Failed to decode token. If
 you are using an external OIDC provider, make sure it is configured as the default directory under the Authentication object.", causedBy = "crates/common/src/auth/oauth/token.rs:2
10", reason = "aead::Error", remoteIp = 172.17.0.1
2026-06-01T06:04:18Z ERROR Authentication error (auth.error) listenerId = "http", localPort = 8080, remoteIp = 172.17.0.1, remotePort = 59360, details = Authentication error (auth.
error) { details = "Invalid account ID", causedBy = "crates/common/src/auth/oauth/token.rs:254" }, causedBy = "crates/http/src/auth/oauth/token.rs:235", details = "Failed to valida
te refresh token"
2026-06-01T06:04:23Z WARN No TLS certificates available (tls.no-certificates-available) total = 0
2026-06-01T06:04:26Z INFO Authentication successful (auth.success) listenerId = "http", localPort = 8080, remoteIp = 172.17.0.1, remotePort = 37808, accountName = "[email protected]
", accountId = 1
2026-06-01T06:04:50Z WARN DNS record propagation timeout (dns.record-propagation-timeout) hostname = "v1-ed25519-20260601._domainkey.mydomain.com.", details = "mydomain.com", type = "T
XT", value = "v=DKIM1; k=ed25519; h=sha256; p=OI5C1lN4GG+iOmerr25EgaG4M7cYxi4rKRRrPGM+0tg="
2026-06-01T06:04:50Z INFO DKIM signature created (dkim.signature-created) id = "v1-ed25519-20260601", details = "mydomain.com"
2026-06-01T06:04:51Z INFO DNS record created (dns.record-created) hostname = "v1-rsa-20260601._domainkey.mydomain.com.", details = "mydomain.com", type = "TXT", value = ["v=DKIM1; k=rs
a; h=sha256; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSe8QNsxMA6yhi68smMmBP8L2l3yG1tLTPuqw/3/j8dKQ9RW8NcatwapPWzhvAR4TFFfJ9xWZDiPyQVq/wBL2J7ZaxV1aNR/D0/u9qgoXu3TGelOc2Q+1+OzM
+mvaqBRkJ3V0MtQ6+DYM8OiY1znUcdq2oDLxUnNudM/lRiC3ZrjYqw477qQPvixGjOiNhLgangWHmEtJQ6fcSRnMbdAWEF46LG+k2bR9s5st1KQvd8sm+w+zsJTMS4tQdaijAScntZID6PISQ9vvN96OvVwnVxIHaglKVcCRQ9fzjfmrXeV8
a9DNA8jwG6pYQdXYguHgxAYfmqZ3kL9juYExx86LwIDAQAB"]
2026-06-01T06:04:53Z WARN No TLS certificates available (tls.no-certificates-available) total = 0
2026-06-01T06:04:54Z WARN No TLS certificates available (tls.no-certificates-available) total = 0

the 503s and no TLS certificates available both look relevant. I can’t really tell what is actually causing the 503 though, perhaps this is a Porkbun API-side error? But it seems that there are many successful record creations…

This has been fixed on the main branch. The fix will be included in v0.16.8.

@stalwart I appreciate the help, but now it’s broken in in another way. Now when I try to set up automatic Porkbun DNS management the setup wizard complains that my key fails to validate. I made sure to check that I was pasting the public/private keys into the right fields.

As an aside, what kind of idiocy is a setup wizard accessible from the public internet WITHOUT HTTPS? The installation guide is literally recommending that we shout our secrets over the internet for all to see.

We don’t have a Porkbun API key to test, we will have to wait until someone contributes a fix.