I have no idea what I did wrong. I followed the installation instructions, but I can't connect to the server. I just keep getting "Failed to fetch."
Here's my configuration:
The server and console are running on different VMs. I use Cloudflare as my DNS provider.
I have no idea where the problem is. A regular Stalwart server works perfectly fine.
I have no idea what I did wrong. I followed the installation instructions, but I can't connect to the server. I just keep getting "Failed to fetch."
Here's my configuration:
Server:
```
services:
inbuxa-server:
image: registry.coffeylabs.org/inbuxa/inbuxa-server:2026.9.29
container_name: inbuxa-server
restart: unless-stopped
environment:
- INBUXA_ADMIN_URL=https://console.mydomain.de
ports:
- 443:443
- 127.0.0.1:8080:8080
- 25:25
- 587:587
- 465:465
- 993:993
volumes:
- inbuxa-config:/etc/inbuxa
- inbuxa-data:/var/lib/inbuxa
volumes:
inbuxa-config:
inbuxa-data:
```
Console:
```
services:
inbuxa-admin:
image: registry.coffeylabs.org/inbuxa/inbuxa-admin:latest
container_name: inbuxa-admin
restart: unless-stopped
environment:
- API_BASE_URL=https://mail.mydomain.de
ports:
-127.0.0.1:8081:8080
```
The server and console are running on different VMs. I use Cloudflare as my DNS provider.
I have no idea where the problem is. A regular Stalwart server works perfectly fine.
Thanks for the detailed report, the compose files made this quick to pin down.
Nothing in your setup is wrong: my install guide is. A new server starts in bootstrap mode, where it opens port 8080 and nothing else. In your compose file that port is bound to 127.0.0.1, so it's only reachable from the server VM itself. Nothing answers on 443 until first boot is done. The console is pointed at https://mail.mydomain.de, gets no answer, and the browser reports "Failed to fetch". Stalwart behaves differently here, which is why that worked for you.
To get through first boot:
On your own machine, open a tunnel to the setup port:
Open http://localhost:8081 and sign in as admin with the password from the server's log (docker logs inbuxa-server). Then complete the setup wizard.
Restart the server. It now opens 443 and the mail ports. Stop the temporary console and close the tunnel. From then on, https://console.mydomain.de with API_BASE_URL=https://mail.mydomain.de is the one you use.
Since you use Cloudflare, make sure mail.mydomain.de is DNS only (grey cloud), not proxied. Cloudflare's proxy doesn't carry SMTP or IMAP, so that name has to lead to the server itself.
Two smaller things. The {{id}} in the sign-in card's footer is a bug in the console, and I have a fix on the way. The admin compose file you pasted has -127.0.0.1:8081:8080 with no space after the dash, which is probably just from pasting.
Thanks for the detailed report, the compose files made this quick to pin down.
Nothing in your setup is wrong: my install guide is. A new server starts in **bootstrap mode**, where it opens port 8080 and nothing else. In your compose file that port is bound to `127.0.0.1`, so it's only reachable from the server VM itself. Nothing answers on 443 until first boot is done. The console is pointed at `https://mail.mydomain.de`, gets no answer, and the browser reports "Failed to fetch". Stalwart behaves differently here, which is why that worked for you.
To get through first boot:
1. On your own machine, open a tunnel to the setup port:
```sh
ssh -N -L 8080:localhost:8080 you@<mail-server-vm>
```
2. Run a temporary console on your machine, pointed at the tunnel:
```sh
docker run -d --rm --name inbuxa-admin-setup \
-e API_BASE_URL=http://localhost:8080 \
-p 127.0.0.1:8081:8080 \
registry.coffeylabs.org/inbuxa/inbuxa-admin:latest
```
3. Open `http://localhost:8081` and sign in as `admin` with the password from the server's log (`docker logs inbuxa-server`). Then complete the setup wizard.
4. Restart the server. It now opens 443 and the mail ports. Stop the temporary console and close the tunnel. From then on, `https://console.mydomain.de` with `API_BASE_URL=https://mail.mydomain.de` is the one you use.
Since you use Cloudflare, make sure `mail.mydomain.de` is **DNS only** (grey cloud), not proxied. Cloudflare's proxy doesn't carry SMTP or IMAP, so that name has to lead to the server itself.
Two smaller things. The `{{id}}` in the sign-in card's footer is a bug in the console, and I have a fix on the way. The admin compose file you pasted has `-127.0.0.1:8081:8080` with no space after the dash, which is probably just from pasting.
I've updated the console install guide to cover this: https://docs.inbuxa.org/install/admin/#reach-the-server-during-first-boot. Let me know if you still get stuck after these steps.
Hmmmm... it doesn't seem to be working, or maybe I'm just too dumb.
When I set up an SSH tunnel with ssh -N -L 8080:localhost:8080 you@<mail-server-vm> and start the container, I just get an ERR_CONNECTION_REFUSED error.
However, if I open an SSH tunnel using port 8081: ssh -N -L 8081:localhost:8081 you@<mail-server-vm>, I get the "Failed to fetch" error again.
Hmmmm... it doesn't seem to be working, or maybe I'm just too dumb.
When I set up an SSH tunnel with `ssh -N -L 8080:localhost:8080 you@<mail-server-vm>` and start the container, I just get an ERR_CONNECTION_REFUSED error.
However, if I open an SSH tunnel using port 8081: `ssh -N -L 8081:localhost:8081 you@<mail-server-vm>`, I get the "Failed to fetch" error again.
Not dumb at all, my instructions assumed Docker runs on the same machine as your browser, and I should have said so.
The console needs two ports on the machine where your browser runs. It's served on 8081, and from inside the page it talks to the server on 8080. Each of your tunnels covered one of the two:
Forwarding 8080 only: nothing served the console on your machine, so the browser got "connection refused".
Forwarding 8081 only: the console loaded, but then found nothing on 8080, so "Failed to fetch" again.
The simplest fix is to run the temporary console on the mail server VM and forward both ports with one SSH command:
On the mail server VM:
docker run -d --rm --name inbuxa-admin-setup
-e API_BASE_URL=http://localhost:8080
-p 127.0.0.1:8081:8080
registry.coffeylabs.org/inbuxa/inbuxa-admin:latest
On the machine with your browser:
ssh -N -L 8080:localhost:8080 -L 8081:localhost:8081 you@
Open http://localhost:8081, sign in as admin with the password from docker logs inbuxa-server, and go through the wizard.
Afterwards, restart the server, stop the temporary console with docker stop inbuxa-admin-setup, and close the tunnel.
It's fine for the console to sit on the mail server for this one step: it's bound to loopback and gone once setup is done. If it still fails, the browser's developer tools (F12 → Network) will show which request fails and why, and a screenshot of that would help me a lot.
I am also working up a bash script that may help to automate this for you if you are still stuck.
Not dumb at all, my instructions assumed Docker runs on the same machine as your browser, and I should have said so.
The console needs two ports on the machine where your browser runs. It's served on 8081, and from inside the page it talks to the server on 8080. Each of your tunnels covered one of the two:
- Forwarding 8080 only: nothing served the console on your machine, so the browser got "connection refused".
- Forwarding 8081 only: the console loaded, but then found nothing on 8080, so "Failed to fetch" again.
The simplest fix is to run the temporary console on the mail server VM and forward both ports with one SSH command:
1. On the mail server VM:
docker run -d --rm --name inbuxa-admin-setup \
-e API_BASE_URL=http://localhost:8080 \
-p 127.0.0.1:8081:8080 \
registry.coffeylabs.org/inbuxa/inbuxa-admin:latest
2. On the machine with your browser:
ssh -N -L 8080:localhost:8080 -L 8081:localhost:8081 you@<mail-server-vm>
3. Open http://localhost:8081, sign in as admin with the password from docker logs inbuxa-server, and go through the wizard.
4. Afterwards, restart the server, stop the temporary console with docker stop inbuxa-admin-setup, and close the tunnel.
It's fine for the console to sit on the mail server for this one step: it's bound to loopback and gone once setup is done. If it still fails, the browser's developer tools (F12 → Network) will show which request fails and why, and a screenshot of that would help me a lot.
I am also working up a bash script that may help to automate this for you if you are still stuck.
I found the same thing in my test harness setting up the bash script, I landing a fix for that right now in the repo, it's running through the CI. Thanks for the patience and feedback, it's really valuable.
I found the same thing in my test harness setting up the bash script, I landing a fix for that right now in the repo, it's running through the CI. Thanks for the patience and feedback, it's really valuable.
Even with both ports, the console wouldn't have shown the setup wizard. The console image was out of step with server 2026.9.29 and showed an empty page instead of the wizard. That's fixed in inbuxa-admin 2026.9.30, released today.
To make this painless, here's a script that does the whole thing. Run it on the machine with your browser (Linux, macOS, or Windows under WSL or Git Bash):
checks that the server is running and reachable on its setup port;
fetches the current console and starts a temporary copy on the mail server;
forwards both ports to your machine;
prints the admin password from the server's log.
Then open http://localhost:8081, sign in, and you'll see Welcome to inbuxa. When the wizard is done, press Ctrl-C in the script's window. That closes the tunnel and removes the temporary console. Then restart the server; it opens 443 and the mail ports.
Save this as inbuxa-first-boot.sh
#!/usr/bin/env bash
# inbuxa-first-boot.sh: open the console for a server's first boot.
#
# ./inbuxa-first-boot.sh you@mail-server
#
# Run it on the machine with your browser (Linux, macOS, or Windows under WSL
# or Git Bash). Over one SSH connection it starts a temporary console on the
# mail server, forwards the console and the server's setup port to this
# machine, and prints the bootstrap password. Open http://localhost:8081 and
# go through the wizard. Ctrl-C closes the tunnel and removes the console.
#
# Settings, all optional:
# SERVER_CONTAINER the mail server's container name (inbuxa-server)
# API_PORT this machine's port for the server (8080)
# UI_PORT this machine's port for the console (8081)
# ADMIN_IMAGE the console image (registry.coffeylabs.org/inbuxa/inbuxa-admin:latest)
set -euo pipefail
TARGET="${1:-}"
if [ -z "$TARGET" ]; then
echo "usage: $0 user@mail-server" >&2
exit 2
fi
SERVER_CONTAINER="${SERVER_CONTAINER:-inbuxa-server}"
API_PORT="${API_PORT:-8080}"
UI_PORT="${UI_PORT:-8081}"
ADMIN_IMAGE="${ADMIN_IMAGE:-registry.coffeylabs.org/inbuxa/inbuxa-admin:latest}"
# The console's port on the mail server. Loopback only, and away from 8081 in
# case something there already uses it.
REMOTE_UI_PORT=18081
# This part runs on the mail server.
read -r -d '' REMOTE <<EOF || true
set -u
SERVER_CONTAINER='$SERVER_CONTAINER'
API_PORT='$API_PORT'
REMOTE_UI_PORT='$REMOTE_UI_PORT'
ADMIN_IMAGE='$ADMIN_IMAGE'
EOF
read -r -d '' REMOTE_BODY <<'EOF' || true
if docker info >/dev/null 2>&1; then
D="docker"
else
echo "==> docker needs sudo here"
D="sudo docker"
fi
if [ "$($D inspect -f '{{.State.Running}}' "$SERVER_CONTAINER" 2>/dev/null)" != "true" ]; then
echo "!! no running container named $SERVER_CONTAINER on $(hostname)." >&2
echo " Start the server first, or set SERVER_CONTAINER to its name." >&2
exit 1
fi
if ! (exec 3<>/dev/tcp/127.0.0.1/8080) 2>/dev/null; then
echo "!! nothing answers on 127.0.0.1:8080 on $(hostname)." >&2
echo " The server's compose file needs: - 127.0.0.1:8080:8080" >&2
exit 1
fi
$D rm -f inbuxa-admin-setup >/dev/null 2>&1 || true
cleanup() {
echo
echo "==> removing the temporary console"
$D rm -f inbuxa-admin-setup >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'exit 0' INT TERM HUP
echo "==> fetching the console image"
# Pulled every time: an older copy left on this machine may predate fixes the
# first boot needs.
$D pull -q "$ADMIN_IMAGE" >/dev/null 2>&1 || echo " (couldn't pull it; using the copy already here)"
echo "==> starting the temporary console on $(hostname)"
$D run -d --rm --name inbuxa-admin-setup \
-e API_BASE_URL="http://localhost:$API_PORT" \
-p "127.0.0.1:$REMOTE_UI_PORT:8080" \
"$ADMIN_IMAGE" >/dev/null
echo
echo "==> the bootstrap administrator, from the server's log:"
creds=$($D logs "$SERVER_CONTAINER" 2>&1 | grep -E '^[[:space:]]+(username:|password)' | tail -n 2)
if [ -n "$creds" ]; then
echo "$creds"
else
echo " (not found: the server may be set up already, or its log was cleared."
echo " Set INBUXA_RECOVERY_ADMIN=admin:<password> on it and restart it.)"
fi
echo
echo "==> ready: open http://localhost:UI_PORT_HERE in your browser"
echo " Leave this window open until the wizard is done, then press Ctrl-C."
while true; do sleep 60; done
EOF
REMOTE_BODY="${REMOTE_BODY//UI_PORT_HERE/$UI_PORT}"
# base64 keeps the script intact whatever the remote login shell is.
encoded=$(printf '%s\n%s\n' "$REMOTE" "$REMOTE_BODY" | base64 | tr -d '\n')
echo "==> connecting to $TARGET"
exec ssh -t \
-o ExitOnForwardFailure=yes \
-L "$API_PORT:127.0.0.1:8080" \
-L "$UI_PORT:127.0.0.1:$REMOTE_UI_PORT" \
"$TARGET" "echo $encoded | base64 -d > /tmp/inbuxa-first-boot.\$\$ && bash /tmp/inbuxa-first-boot.\$\$; rc=\$?; rm -f /tmp/inbuxa-first-boot.\$\$; exit \$rc"
After that, update your permanent console on the other VM so it has the fix too (docker compose pull && docker compose up -d). Keep mail.mydomain.de DNS-only in Cloudflare.
If anything goes wrong, paste the script's output here and I'll take it from there.
Even with both ports, the console wouldn't have shown the setup wizard. The console image was out of step with server 2026.9.29 and showed an empty page instead of the wizard. That's fixed in inbuxa-admin 2026.9.30, released today.
To make this painless, here's a script that does the whole thing. Run it on the machine with your browser (Linux, macOS, or Windows under WSL or Git Bash):
```sh
chmod +x inbuxa-first-boot.sh
./inbuxa-first-boot.sh you@<mail-server-vm>
```
Over one SSH connection, it:
- checks that the server is running and reachable on its setup port;
- fetches the current console and starts a temporary copy on the mail server;
- forwards both ports to your machine;
- prints the admin password from the server's log.
Then open http://localhost:8081, sign in, and you'll see Welcome to inbuxa. When the wizard is done, press Ctrl-C in the script's window. That closes the tunnel and removes the temporary console. Then restart the server; it opens 443 and the mail ports.
Save this as inbuxa-first-boot.sh
```
#!/usr/bin/env bash
# inbuxa-first-boot.sh: open the console for a server's first boot.
#
# ./inbuxa-first-boot.sh you@mail-server
#
# Run it on the machine with your browser (Linux, macOS, or Windows under WSL
# or Git Bash). Over one SSH connection it starts a temporary console on the
# mail server, forwards the console and the server's setup port to this
# machine, and prints the bootstrap password. Open http://localhost:8081 and
# go through the wizard. Ctrl-C closes the tunnel and removes the console.
#
# Settings, all optional:
# SERVER_CONTAINER the mail server's container name (inbuxa-server)
# API_PORT this machine's port for the server (8080)
# UI_PORT this machine's port for the console (8081)
# ADMIN_IMAGE the console image (registry.coffeylabs.org/inbuxa/inbuxa-admin:latest)
set -euo pipefail
TARGET="${1:-}"
if [ -z "$TARGET" ]; then
echo "usage: $0 user@mail-server" >&2
exit 2
fi
SERVER_CONTAINER="${SERVER_CONTAINER:-inbuxa-server}"
API_PORT="${API_PORT:-8080}"
UI_PORT="${UI_PORT:-8081}"
ADMIN_IMAGE="${ADMIN_IMAGE:-registry.coffeylabs.org/inbuxa/inbuxa-admin:latest}"
# The console's port on the mail server. Loopback only, and away from 8081 in
# case something there already uses it.
REMOTE_UI_PORT=18081
# This part runs on the mail server.
read -r -d '' REMOTE <<EOF || true
set -u
SERVER_CONTAINER='$SERVER_CONTAINER'
API_PORT='$API_PORT'
REMOTE_UI_PORT='$REMOTE_UI_PORT'
ADMIN_IMAGE='$ADMIN_IMAGE'
EOF
read -r -d '' REMOTE_BODY <<'EOF' || true
if docker info >/dev/null 2>&1; then
D="docker"
else
echo "==> docker needs sudo here"
D="sudo docker"
fi
if [ "$($D inspect -f '{{.State.Running}}' "$SERVER_CONTAINER" 2>/dev/null)" != "true" ]; then
echo "!! no running container named $SERVER_CONTAINER on $(hostname)." >&2
echo " Start the server first, or set SERVER_CONTAINER to its name." >&2
exit 1
fi
if ! (exec 3<>/dev/tcp/127.0.0.1/8080) 2>/dev/null; then
echo "!! nothing answers on 127.0.0.1:8080 on $(hostname)." >&2
echo " The server's compose file needs: - 127.0.0.1:8080:8080" >&2
exit 1
fi
$D rm -f inbuxa-admin-setup >/dev/null 2>&1 || true
cleanup() {
echo
echo "==> removing the temporary console"
$D rm -f inbuxa-admin-setup >/dev/null 2>&1 || true
}
trap cleanup EXIT
trap 'exit 0' INT TERM HUP
echo "==> fetching the console image"
# Pulled every time: an older copy left on this machine may predate fixes the
# first boot needs.
$D pull -q "$ADMIN_IMAGE" >/dev/null 2>&1 || echo " (couldn't pull it; using the copy already here)"
echo "==> starting the temporary console on $(hostname)"
$D run -d --rm --name inbuxa-admin-setup \
-e API_BASE_URL="http://localhost:$API_PORT" \
-p "127.0.0.1:$REMOTE_UI_PORT:8080" \
"$ADMIN_IMAGE" >/dev/null
echo
echo "==> the bootstrap administrator, from the server's log:"
creds=$($D logs "$SERVER_CONTAINER" 2>&1 | grep -E '^[[:space:]]+(username:|password)' | tail -n 2)
if [ -n "$creds" ]; then
echo "$creds"
else
echo " (not found: the server may be set up already, or its log was cleared."
echo " Set INBUXA_RECOVERY_ADMIN=admin:<password> on it and restart it.)"
fi
echo
echo "==> ready: open http://localhost:UI_PORT_HERE in your browser"
echo " Leave this window open until the wizard is done, then press Ctrl-C."
while true; do sleep 60; done
EOF
REMOTE_BODY="${REMOTE_BODY//UI_PORT_HERE/$UI_PORT}"
# base64 keeps the script intact whatever the remote login shell is.
encoded=$(printf '%s\n%s\n' "$REMOTE" "$REMOTE_BODY" | base64 | tr -d '\n')
echo "==> connecting to $TARGET"
exec ssh -t \
-o ExitOnForwardFailure=yes \
-L "$API_PORT:127.0.0.1:8080" \
-L "$UI_PORT:127.0.0.1:$REMOTE_UI_PORT" \
"$TARGET" "echo $encoded | base64 -d > /tmp/inbuxa-first-boot.\$\$ && bash /tmp/inbuxa-first-boot.\$\$; rc=\$?; rm -f /tmp/inbuxa-first-boot.\$\$; exit \$rc"
```
After that, update your permanent console on the other VM so it has the fix too (docker compose pull && docker compose up -d). Keep mail.mydomain.de DNS-only in Cloudflare.
If anything goes wrong, paste the script's output here and I'll take it from there.
jcoffey-dev
self-assigned this 2026-09-30 21:06:56 +00:00
Okay, now I've run into another problem. I was able to complete the setup successfully. However, when I restart the server and try to access the console without the tunnel, I still get a "Failed to fetch" error.
When I access the server again using the tunnel, I get the following error: Failed to load the admin panel configuration.
Failed to fetch
Okay, now I've run into another problem. I was able to complete the setup successfully. However, when I restart the server and try to access the console without the tunnel, I still get a "Failed to fetch" error.
When I access the server again using the tunnel, I get the following error: Failed to load the admin panel configuration.
Failed to fetch
Great, the setup went through, so you're nearly there. For what it's worth you are operator number one so far as I know (me being operator 0). A certain amount of this was kind of expected, I appreciate the feedback though.
The tunnel failing now is expected: once set up, the server only accepts the console from the address in INBUXA_ADMIN_URL (https://console.mydomain.de), so the temporary one on localhost is refused. You won't need it again.
The "Failed to fetch" on your real console is almost certainly the server's TLS certificate. With the wizard's automatic certificate on, the server asks Let's Encrypt for one, and Let's Encrypt has to reach mail.mydomain.de on port 443 directly. Until that works, the server uses a temporary self-signed certificate. Your browser silently refuses that certificate for the console's requests, and the console can only say "Failed to fetch".
Look for the certificate request in the server's log: docker exec inbuxa-server sh -c 'grep -h -i -e acme -e certificate /var/log/inbuxa/* | tail -20'
The usual reasons it can't get a certificate:
mail.mydomain.de is proxied in Cloudflare (orange cloud). It has to be DNS only, because Let's Encrypt must reach the server itself on 443.
Port 443 isn't reachable from the internet, for example because of a firewall or missing port forwarding to that VM.
The DNS record points somewhere other than the mail VM.
Once that's fixed, restart the server so it asks again. It can take a minute or two, and after that the console should connect. If it still fails, paste the output of step 2 here.
Great, the setup went through, so you're nearly there. For what it's worth you are operator number one so far as I know (me being operator 0). A certain amount of this was kind of expected, I appreciate the feedback though.
The tunnel failing now is expected: once set up, the server only accepts the console from the address in INBUXA_ADMIN_URL (https://console.mydomain.de), so the temporary one on localhost is refused. You won't need it again.
The "Failed to fetch" on your real console is almost certainly the server's TLS certificate. With the wizard's automatic certificate on, the server asks Let's Encrypt for one, and Let's Encrypt has to reach mail.mydomain.de on port 443 directly. Until that works, the server uses a temporary self-signed certificate. Your browser silently refuses that certificate for the console's requests, and the console can only say "Failed to fetch".
Could you check two things?
1. Open https://mail.mydomain.de/healthz/live in your browser. If you get a certificate warning, that's the problem.
2. Look for the certificate request in the server's log:
`docker exec inbuxa-server sh -c 'grep -h -i -e acme -e certificate /var/log/inbuxa/* | tail -20'`
The usual reasons it can't get a certificate:
- mail.mydomain.de is proxied in Cloudflare (orange cloud). It has to be DNS only, because Let's Encrypt must reach the server itself on 443.
- Port 443 isn't reachable from the internet, for example because of a firewall or missing port forwarding to that VM.
- The DNS record points somewhere other than the mail VM.
Once that's fixed, restart the server so it asks again. It can take a minute or two, and after that the console should connect. If it still fails, paste the output of step 2 here.
mail.mydomain.de has a certificate; the following is displayed: {"detail":"OK","status":200,"title":"OK","type":"about:blank"}
However, when I run the command docker exec inbuxa-server sh -c 'grep -h -i -e acme -e certificate /var/log/inbuxa/* | tail -20', I get the following error: grep: /var/log/inbuxa/*: No such file or directory
mai.mydomain.de is set to "DNS only" on Cloudflare, and port 443 is allowed by a firewall rule.
mail.mydomain.de has a certificate; the following is displayed: {"detail":"OK","status":200,"title":"OK","type":"about:blank"}
However, when I run the command `docker exec inbuxa-server sh -c 'grep -h -i -e acme -e certificate /var/log/inbuxa/* | tail -20'`, I get the following error: `grep: /var/log/inbuxa/*: No such file or directory`
mai.mydomain.de is set to "DNS only" on Cloudflare, and port 443 is allowed by a firewall rule.
Thanks, that rules out the certificate. I ran your exact setup here (server 2026.9.29, console 2026.9.30, restart after setup) and the console signs in and loads, so we're down to something specific to your deployment. (The missing /var/log/inbuxa just means the server logs to the container output, e.g. if you chose console logging in the wizard; docker logs inbuxa-server has them.)
That error appears after sign-in, when the console asks the server for its configuration. It's the first request in the whole flow where your browser has to ask the server for permission before sending it (a CORS "preflight"), because it carries your sign-in token. Everything before it, including the health check you opened, goes without one. So the preflight is where to look.
Run this from any machine. It sends the same preflight your browser sends:
curl -si -X OPTIONS https://mail.mydomain.de/jmap/session
-H 'Origin: https://console.mydomain.de'
-H 'Access-Control-Request-Method: GET'
-H 'Access-Control-Request-Headers: authorization'
A healthy server answers 204 with a line access-control-allow-origin: https://console.mydomain.de. Please paste what you get.
Is there anything between the internet and the mail server: a reverse proxy (Nginx Proxy Manager, Traefik, Caddy…), or a firewall with HTTP inspection? If so, it may be answering or dropping that preflight instead of passing it to the server.
Is the address in your browser's address bar exactly https://console.mydomain.de? After setup, the server only answers the console at the exact address in INBUXA_ADMIN_URL, with the same scheme, host and port. If you've changed INBUXA_ADMIN_URL since the server was created, recreate the container (docker compose up -d --force-recreate inbuxa-server). A plain restart keeps the old value.
If the curl output looks right, open the console, press F12 and reload. In the Console tab, copy the red error lines. In the Network tab, note the URL of the failed request (shown in red). Paste both here, and they'll name the exact request and the reason.
Thanks, that rules out the certificate. I ran your exact setup here (server 2026.9.29, console 2026.9.30, restart after setup) and the console signs in and loads, so we're down to something specific to your deployment. (The missing /var/log/inbuxa just means the server logs to the container output, e.g. if you chose console logging in the wizard; docker logs inbuxa-server has them.)
That error appears after sign-in, when the console asks the server for its configuration. It's the first request in the whole flow where your browser has to ask the server for permission before sending it (a CORS "preflight"), because it carries your sign-in token. Everything before it, including the health check you opened, goes without one. So the preflight is where to look.
1. Run this from any machine. It sends the same preflight your browser sends:
curl -si -X OPTIONS https://mail.mydomain.de/jmap/session \
-H 'Origin: https://console.mydomain.de' \
-H 'Access-Control-Request-Method: GET' \
-H 'Access-Control-Request-Headers: authorization'
A healthy server answers 204 with a line access-control-allow-origin: https://console.mydomain.de. Please paste what you get.
2. Is there anything between the internet and the mail server: a reverse proxy (Nginx Proxy Manager, Traefik, Caddy…), or a firewall with HTTP inspection? If so, it may be answering or dropping that preflight instead of passing it to the server.
3. Is the address in your browser's address bar exactly https://console.mydomain.de? After setup, the server only answers the console at the exact address in INBUXA_ADMIN_URL, with the same scheme, host and port. If you've changed INBUXA_ADMIN_URL since the server was created, recreate the container (docker compose up -d --force-recreate inbuxa-server). A plain restart keeps the old value.
If the curl output looks right, open the console, press F12 and reload. In the Console tab, copy the red error lines. In the Network tab, note the URL of the failed request (shown in red). Paste both here, and they'll name the exact request and the reason.
Oh, oops—it actually works. This time, the mistake really was on my end. For some reason, I entered mail.mydomain.de as API_BASE_URL. Of course, that's not my real domain, so it didn't work.
Oh, oops—it actually works. This time, the mistake really was on my end. For some reason, I entered mail.mydomain.de as API_BASE_URL. Of course, that's not my real domain, so it didn't work.
Anytime, let me know if you run into any other issues or need help or just want to provide feedback. While based on Stalwart the project has diverged a lot. My environment is a three node cluster on a postgesql cluster for the store, load balanced webmail and console, so seeing other shapes would be awesome.
Cheers
Anytime, let me know if you run into any other issues or need help or just want to provide feedback. While based on Stalwart the project has diverged a lot. My environment is a three node cluster on a postgesql cluster for the store, load balanced webmail and console, so seeing other shapes would be awesome.
Cheers
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
I have no idea what I did wrong. I followed the installation instructions, but I can't connect to the server. I just keep getting "Failed to fetch."
Here's my configuration:
Server:
Console:
The server and console are running on different VMs. I use Cloudflare as my DNS provider.
I have no idea where the problem is. A regular Stalwart server works perfectly fine.
Thanks for the detailed report, the compose files made this quick to pin down.
Nothing in your setup is wrong: my install guide is. A new server starts in bootstrap mode, where it opens port 8080 and nothing else. In your compose file that port is bound to
127.0.0.1, so it's only reachable from the server VM itself. Nothing answers on 443 until first boot is done. The console is pointed athttps://mail.mydomain.de, gets no answer, and the browser reports "Failed to fetch". Stalwart behaves differently here, which is why that worked for you.To get through first boot:
http://localhost:8081and sign in asadminwith the password from the server's log (docker logs inbuxa-server). Then complete the setup wizard.https://console.mydomain.dewithAPI_BASE_URL=https://mail.mydomain.deis the one you use.Since you use Cloudflare, make sure
mail.mydomain.deis DNS only (grey cloud), not proxied. Cloudflare's proxy doesn't carry SMTP or IMAP, so that name has to lead to the server itself.Two smaller things. The
{{id}}in the sign-in card's footer is a bug in the console, and I have a fix on the way. The admin compose file you pasted has-127.0.0.1:8081:8080with no space after the dash, which is probably just from pasting.I've updated the console install guide to cover this: https://docs.inbuxa.org/install/admin/#reach-the-server-during-first-boot. Let me know if you still get stuck after these steps.
Hmmmm... it doesn't seem to be working, or maybe I'm just too dumb.
When I set up an SSH tunnel with
ssh -N -L 8080:localhost:8080 you@<mail-server-vm>and start the container, I just get an ERR_CONNECTION_REFUSED error.However, if I open an SSH tunnel using port 8081:
ssh -N -L 8081:localhost:8081 you@<mail-server-vm>, I get the "Failed to fetch" error again.Not dumb at all, my instructions assumed Docker runs on the same machine as your browser, and I should have said so.
The console needs two ports on the machine where your browser runs. It's served on 8081, and from inside the page it talks to the server on 8080. Each of your tunnels covered one of the two:
The simplest fix is to run the temporary console on the mail server VM and forward both ports with one SSH command:
docker run -d --rm --name inbuxa-admin-setup
-e API_BASE_URL=http://localhost:8080
-p 127.0.0.1:8081:8080
registry.coffeylabs.org/inbuxa/inbuxa-admin:latest
ssh -N -L 8080:localhost:8080 -L 8081:localhost:8081 you@
It's fine for the console to sit on the mail server for this one step: it's bound to loopback and gone once setup is done. If it still fails, the browser's developer tools (F12 → Network) will show which request fails and why, and a screenshot of that would help me a lot.
I am also working up a bash script that may help to automate this for you if you are still stuck.
Oh, yeah, that worked. Thanks so much for your help.
Ohh, or maybe not right now. Instead of seeing a setup screen, the following appears in the screenshot:
I found the same thing in my test harness setting up the bash script, I landing a fix for that right now in the repo, it's running through the CI. Thanks for the patience and feedback, it's really valuable.
Even with both ports, the console wouldn't have shown the setup wizard. The console image was out of step with server 2026.9.29 and showed an empty page instead of the wizard. That's fixed in inbuxa-admin 2026.9.30, released today.
To make this painless, here's a script that does the whole thing. Run it on the machine with your browser (Linux, macOS, or Windows under WSL or Git Bash):
Over one SSH connection, it:
Then open http://localhost:8081, sign in, and you'll see Welcome to inbuxa. When the wizard is done, press Ctrl-C in the script's window. That closes the tunnel and removes the temporary console. Then restart the server; it opens 443 and the mail ports.
Save this as inbuxa-first-boot.sh
After that, update your permanent console on the other VM so it has the fix too (docker compose pull && docker compose up -d). Keep mail.mydomain.de DNS-only in Cloudflare.
If anything goes wrong, paste the script's output here and I'll take it from there.
Okay, now I've run into another problem. I was able to complete the setup successfully. However, when I restart the server and try to access the console without the tunnel, I still get a "Failed to fetch" error.
When I access the server again using the tunnel, I get the following error: Failed to load the admin panel configuration.
Failed to fetch
Great, the setup went through, so you're nearly there. For what it's worth you are operator number one so far as I know (me being operator 0). A certain amount of this was kind of expected, I appreciate the feedback though.
The tunnel failing now is expected: once set up, the server only accepts the console from the address in INBUXA_ADMIN_URL (https://console.mydomain.de), so the temporary one on localhost is refused. You won't need it again.
The "Failed to fetch" on your real console is almost certainly the server's TLS certificate. With the wizard's automatic certificate on, the server asks Let's Encrypt for one, and Let's Encrypt has to reach mail.mydomain.de on port 443 directly. Until that works, the server uses a temporary self-signed certificate. Your browser silently refuses that certificate for the console's requests, and the console can only say "Failed to fetch".
Could you check two things?
docker exec inbuxa-server sh -c 'grep -h -i -e acme -e certificate /var/log/inbuxa/* | tail -20'The usual reasons it can't get a certificate:
Once that's fixed, restart the server so it asks again. It can take a minute or two, and after that the console should connect. If it still fails, paste the output of step 2 here.
Also for what it's worth this is all going to really help with the installer which, when finished, will automate all of this.
mail.mydomain.de has a certificate; the following is displayed: {"detail":"OK","status":200,"title":"OK","type":"about:blank"}
However, when I run the command
docker exec inbuxa-server sh -c 'grep -h -i -e acme -e certificate /var/log/inbuxa/* | tail -20', I get the following error:grep: /var/log/inbuxa/*: No such file or directorymai.mydomain.de is set to "DNS only" on Cloudflare, and port 443 is allowed by a firewall rule.
Thanks, that rules out the certificate. I ran your exact setup here (server 2026.9.29, console 2026.9.30, restart after setup) and the console signs in and loads, so we're down to something specific to your deployment. (The missing /var/log/inbuxa just means the server logs to the container output, e.g. if you chose console logging in the wizard; docker logs inbuxa-server has them.)
That error appears after sign-in, when the console asks the server for its configuration. It's the first request in the whole flow where your browser has to ask the server for permission before sending it (a CORS "preflight"), because it carries your sign-in token. Everything before it, including the health check you opened, goes without one. So the preflight is where to look.
curl -si -X OPTIONS https://mail.mydomain.de/jmap/session
-H 'Origin: https://console.mydomain.de'
-H 'Access-Control-Request-Method: GET'
-H 'Access-Control-Request-Headers: authorization'
A healthy server answers 204 with a line access-control-allow-origin: https://console.mydomain.de. Please paste what you get.
If the curl output looks right, open the console, press F12 and reload. In the Console tab, copy the red error lines. In the Network tab, note the URL of the failed request (shown in red). Paste both here, and they'll name the exact request and the reason.
Oh, oops—it actually works. This time, the mistake really was on my end. For some reason, I entered mail.mydomain.de as API_BASE_URL. Of course, that's not my real domain, so it didn't work.
Now the Admin Dashboard is actually loading the way it's supposed to. Thanks.
Anytime, let me know if you run into any other issues or need help or just want to provide feedback. While based on Stalwart the project has diverged a lot. My environment is a three node cluster on a postgesql cluster for the store, load balanced webmail and console, so seeing other shapes would be awesome.
Cheers