Stalwart v0.16 prefix for WebUI

Your question

Does Stalwart support a prefix for the WebUI e.g. /stalwart/admin and /stalwart/account instead just /admin and /account?

Just adding it as additional urlPrefix seems not to work, Stalwart responds with 404 not found :frowning:

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

The answer is yes, custom prefixes are supported – but there’s an important nuance about how the bundle’s internal routing works. The server rewrites the <base href="/"> tag in index.html to match whatever mount path you configure, so the same bundle can be served from any path. However, the WebUI SPA itself uses its own internal routing (e.g. navigating to /admin/... sub-routes), and it must be built expecting the base path you set.

Here is what the docs say concretely:

  • The urlPrefix field on the Application object is a String[]. You can set it to e.g. ["/stalwart/admin", "/stalwart/account"] to mount the WebUI at those paths instead of the defaults.
  • The server automatically rewrites the <base href="/"> tag in the bundle’s index.html to match the configured mount path, so assets resolve correctly without repackaging.
  • The constraint is that the paths must not collide with JMAP/WebDAV/.well-known endpoints, and they must not collide with each other across Applications.

Why you’re getting 404: The most likely cause is that urlPrefix accepts a JSON array (e.g. ["/stalwart/admin", "/stalwart/account"]), not two separate entries. Also confirm your HTTP listener is actually receiving traffic at that path – if a reverse proxy is stripping the prefix before it reaches Stalwart, the server would never see the prefixed path.

You can update the Application object via the WebUI at Settings > Web Applications, or via the JMAP API (x:Application/set with an update operation changing the urlPrefix array).

Relevant docs: Applications overview and the Application object reference.

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

According to `stalwart-cli snapshot Application` I have the following set:

{
  "@type": "create",
  "object": "Application",
  "value": {
    "application-iuhrvwukaaab": {
      "description": "Stalwart Web Interface",
      "urlPrefix": {
        "/admin": true,
        "/account": true,
        "/stalwart/admin": true,
        "/stalwart/account": true
      },
      "enabled": true,
      "autoUpdateFrequency": 2592000000,
      "resourceUrl": "https://github.com/stalwartlabs/webui/releases/latest/download/webui.zip",
      "unpackDirectory": null
    }
  }
}

Do I need to reload anything?

`/stalwart` is proxied by Nginx into the stalwart container.

Trying to `curl -i https://boulder.egroupware.org/stalwart/admin/` gives the following response:

HTTP/2 404
server: nginx/1.30.2
date: Fri, 05 Jun 2026 12:54:10 GMT
content-type: application/problem+json
content-length: 120

{“type”:“about:blank”,“status”:404,“title”:“Not Found”,“detail”:“The requested resource does not exist on this server.”}

Any ideas?

Multi-segment prefixes like /stalwart/admin do not work in 0.16: the router matches on the first path segment only, so a request to /stalwart/admin is dispatched as the prefix “stalwart” while the app is registered under the full “stalwart/admin”, and the lookup misses, giving you the 404. Only single-segment prefixes are supported, which the docs do not make clear.

The workaround is to use a single-segment prefix such as /stalwart and proxy that in your reverse proxy; after changing the Application object you do need to run a settings reload for it to take effect. If you genuinely need the two-level /stalwart/admin layout, that would require a code change.

Ok, that got me a lot further :slight_smile:

I changed add just /stalwart as additional URL for the WebApp, reloaded the settings and set the following environment variable for the Docker container:

services:
  mail:
    image: stalwartlabs/stalwart:v0.16-alpine
    container_name: stalwart-mail
    environment:
    - STALWART_PUBLIC_URL=https://boulder.egroupware.org
    ports:
      - "8081:8080"

And the following proxy in nginx.conf

server {
	# set Docker's resolver so Nginx can detect changed IP's of containers
	resolver 127.0.0.11 valid=60s;
	# services need to be set for Nginx to resolve them again
	set $mail mail;

	listen 443 ssl http2 default_server;
	ssl_certificate /etc/ssl/private/certificate.pem;
	ssl_certificate_key /etc/ssl/private/certificate.pem;
	server_name boulder.egroupware.org;

	location ~^/(stalwart|jmap|.well-known/jmap|logo|api/(discover|auth|schema|oauth|principal|telemetry|reload|settings|account|dns)|login|auth ) {
		proxy_read_timeout 3600;
		proxy_http_version 1.1;
		proxy_set_header Host $http_host;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "Upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_pass https://$mail;
	}
}

Now I can login successful and everything seems to work :slight_smile:

Locking at the browser I can see a single URL to still fail, thought it’s proxied into the Stalwart container:

curl -i https://boulder.egroupware.org/logo/
HTTP/2 404 
server: nginx/1.30.2
date: Sun, 07 Jun 2026 07:33:23 GMT
content-type: application/problem+json
content-length: 120

{"type":"about:blank","status":404,"title":"Not Found","detail":"The requested resource does not exist on this server."}

curl -i http://localhost:8081/logo/
HTTP/1.1 404 Not Found
content-type: application/problem+json
content-length: 120
date: Sun, 07 Jun 2026 07:33:29 GMT

{"type":"about:blank","status":404,"title":"Not Found","detail":"The requested resource does not exist on this server."}

So far this seems just odd, but has no negativ effect as far as I can tell …

Any idea why the WebApp requests /logo/ but the container does not respond to it?

Ralf

No one an idea?

Ralf