OAuth endpoints return HTTP 404 and OAuth does not work

Issue Description

Fresh new installation of version 0.16.3

https://mail.example.com/.well-known/oauth-authorization-server

Returns the endpoints for Oauth but all of them (/auth/token, /auth/register etc.) return HTTP 404 with this json body:

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

Therefore OAuth does not work.

Since it is fresh new, the only HTTP endpoint setting is {"else": "200"}

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

PostgreSQL

Blob Storage

PostgreSQL

Search Engine

PostgreSQL

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

Those endpoints are working; they just only accept POST. /auth/token, /auth/register and /auth/device are POST-only, so fetching them in a browser (a GET) returns exactly the 404 you saw, while the metadata endpoints you tried first are GET, which is why those worked. Probing the endpoints by URL is misleading here.

Your {“else”:“200”} (empty allowed-endpoints) is the correct default and allows everything, so it isn’t blocking anything. OAuth is driven by an actual client flow: the authorization endpoint is /login, and the token is obtained by POSTing to /auth/token. If you test with a real OAuth client (or a POST) rather than URL fetches, you’ll see it work. If a real client still fails after that, send me the client, the exact request it makes, and the server log at that moment.

Thank you, but actually I am testing them through browser just because the clients do not work anyway.

I am trying Bulwark Webmail and Sterna Mobile client.

Here is what happens with Bulwark:

It could “automagically” set all JMAP settings for Stalwart and successfully registered itself as a client at Stalwart. Now opening the webmail,

In the UI, it says:

SSO is enabled but the identity provider could not be reached. Check your OAuth configuration.

In the logs it says:

[OAuth] Discovery failed for https://mail.example.com: https://mail.example.com/.well-known/oauth-authorization-server returned non-public or invalid endpoint URL; https://mail.example.com/.well-known/openid-configuration returned non-public or invalid endpoint URL

[WARN ] 2026-07-20T07:49:45.880Z OAuth metadata discovery returned no usable endpoints {“discoveryUrl”:“https://mail.example.com”}

And here what happens with Sterna:
It gives a one-time code to enter in the Stalwart login page and redirects to Stalwart opening a browser. Then I enter the credentials along with this code Sterna created. Then Stalwart says login is successful and that I can return to the client; but when I get back to Sterna, it says OAuth is not working and that I should try again.

Right now, Sterna doesn’t even open the login page and it says:

Couldn’t connect: This server doesn’t support OAuth sign-in. Use e-mail and password instead.

Now I tried manually testing the endpoints with Postman. I created a client at Stalwart portal. And in Postman I selected OAuth 2.0 as authorization type, “openid profile email” as scope and received the tokens successfully. This process created this log entry on Stalwart side:

INFO Authentication successful (auth.success) listenerId = “https”, localPort = 443, remoteIp = xxx.xxx.xxx.xxx, remotePort = 32822, accountName = “[email protected]”, accountId = 1

Then I sent a POST request to https://mail.example.com/auth/profile with the Access Token that Postman automatically added. The result is very same with the other clients:

Header is HTTP 404 and body is:

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

And this produces zero log on the server side for this behavior. No logs other than the auth-successful message when I first logged in.

Here is the problem:

Stalwart is behind a software firewall (Endian Community Edition). Webmail is on another VPS, but behind the same firewall, which makes the two in the same local network.

To prevent loopback, I have to edit hosts files of all the host machines which need to interconnect each other with their respective domains. Like for example on the machine which runs webmail:

mail.example.com 192.168.1.100

All of the other apps work the same way. But now I know there’s a problem here. Because when I checked this option in Bulwark’s admin panel:

Allow private OAuth endpoints
Permit discovery to resolve to RFC-1918 / loopback hosts. Enable only for split-DNS deployments where the mail server’s public hostname resolves to an internal IP.

SSO-OAuth started working.

But the problem is still not understood. Why would Stalwart return HTTP 404 to other clients and log nothing about it?

I can’t still use mobile clients right now.

By the way “Clusters” page shows one and only member with the Stalwart machine’s local hostname (rather than the domain name) and it shows “inactive”. I don’t know if this has anything to do with it but mail and things already work.

The split-DNS resolution to an internal IP was the actual blocker, and the “Allow private OAuth endpoints” toggle is the right fix for that topology. The server was never rejecting anything here.

On the three loose ends: /auth/profile isn’t an endpoint that exists, which is why a POST to it 404s. The equivalent is GET /auth/userinfo with the Bearer token. And a 404 for an unknown path isn’t logged, so that silence is normal (only the real, matched endpoints emit auth events). The Clusters page showing the node by its OS hostname and marked “inactive” on a single-node install is cosmetic: there’s no cluster heartbeat updating that record, so it never flips to active, and it has no bearing on mail or OAuth.

For the mobile clients, that’s the one thing I’d still want to see. Send me the exact request a client makes and the server log at that moment; most likely it’s the same private-endpoint discovery problem on the mobile side, but the log will tell us for sure.