Skip to content
View in the app

A better way to browse. Learn more.

Unraid

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (โ‹ฎ) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Self-host email using Stalwart, Bind and Wireguard

Featured Replies

I wrote up some docker compose and terraform to enable self-hosting email using Stalwart, Bind for DNS, and Wireguard to let you host everything on Unraid and only use AWS to provide a static IP and forward traffic to you. Check out https://markfalk.github.io/docs/self-hosting-mail if you're interested. I welcome any feedback or suggestions. ๐Ÿป

self-hosted-mail-dark.png

Hi Mark,
I'm currently testing this solution and will get back with results soon.

For now, I'm having an issue with the initial login at https://domain.com/login.

I get the following error:

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

image.png

I still need to double-check everything, but according to the official documentation, port 8080 is required for login.
In your WireGuard setup, only port 443 is being forwarded.
On the other hand, I do see some exposed ports in the Unraid WireGuard Docker container, but there's no routing to port 8080 inside the Stalwart container.

If logging in via https://domain.com/login worked for you โ€” please share the magic behind it ๐Ÿ˜Š

Overall, the solution looks solid and it should work well. Great job on the documentation on your blog, by the way!
It doesnโ€™t look like WordPress โ€” what engine are you using?

Best regards!

image.png

After some restarts i manage to run.

The error above concluded.

I have changed to lastest docker image but no luck.

I need to check this deeper.

  • Author

Looks like a matter of bad timing. Stalwart has just had a major release starting with v0.12.0 they've added Collaborationย features includingย Calendars over CalDAV,ย Contacts over CardDAVย andย File Storage over WebDAV.

This is a breaking change from the v0.11 series and they do have an upgrade path. If you're just getting started you can jump right to v0.12.2, they've also changed the dockerhub location from https://hub.docker.com/r/stalwartlabs/mail-server to https://hub.docker.com/r/stalwartlabs/stalwart

I've updated my docker-compose to reference the latest version:

image: stalwartlabs/stalwart:v0.12.2

It looks like there isn't a safeguard to prevent the v0.11.x releases from updating to an incompatible webadmin release. Webadmin v0.1.26 was just released yesterday and requires the new v0.12.2 server.

The other option would be to update the webadmin resource in /mnt/user/appdata/stalwart-data/etc/config.toml to point to the last compatible version
webadmin.resource = "https://github.com/stalwartlabs/webadmin/releases/download/v0.1.25/webadmin.zip" documented here https://stalw.art/docs/management/webadmin/update/

I've mentioned this as an issue on their Discord channel and I'll see what they say. For now erasing your /mnt/user/appdata/stalwart-data/ directory and doing a docker compose update with the new image location should recreate everything and give you a new admin password to log in with. Let me know how it goes.

  • Author

It looks like your got the TLS working. That's great. I updated my docs and fixed the links for the Stalwart configuration (they changed their doc URLs).

It is not WordPress it's a static site generator, incidentally I use the same one Stalwart does, Docusaurus (https://docusaurus.io/) FTW! Site code here.

8 hours ago, chmuri said:

I still need to double-check everything, but according to the official documentation, port 8080 is required for login.
In your WireGuard setup, only port 443 is being forwarded.
On the other hand, I do see some exposed ports in the Unraid WireGuard Docker container, but there's no routing to port 8080 inside the Stalwart container.

If logging in via https://domain.com/login worked for you โ€” please share the magic behind it ๐Ÿ˜Š

Overall, the solution looks solid and it should work well. Great job on the documentation on your blog, by the way!
It doesnโ€™t look like WordPress โ€” what engine are you using?

Hi Mark,

Just circling back after a bit more tinkering and a couple of coffee-fuelled debugging sessions.

What went wrong (and how I fixed it)

The culprit turned out to be a stray DNS entry in my WireGuard configuration. It stopped Stalwartโ€™s admin UI from launching, which is why I kept hitting that 404 on /login. Removing the entry let the stack start cleanly. Because Iโ€™d already seen the v0.12 breaking changes, I went straight to stalwartlabs/stalwart:latest; the web interface is now behaving.

How my deployment differs from yours

  • Colo host with multiple public IPv4s โ€“ WireGuard runs in its own Docker container there.

  • Inbound vs. outbound traffic โ€“ inbound mail and HTTPS hit the Unraid box exactly as in your guide, but outbound packets leave via the serverโ€™s primary IP instead of the WireGuard-mapped address. Still tweaking NAT rules to tidy that up.

Odd container behaviour

Both Bind and Stalwart containers occasionally shut down and donโ€™t auto-restart. I havenโ€™t had time for a proper post-mortem, but Stalwartโ€™s logs already show the first SMTP auth probes and directory-harvest attempts, so I suspect the crashes might be tied to those early attacks on the fresh install.

image.png

First impressions of Stalwart

I really like the direction theyโ€™re headingโ€”if the release cadence calms down, I could see myself ditching my current mail stack for this. The only piece I miss is a built-in webmail client (something Roundcube-ish), but I imagine that will appear once the new DAV stack matures.

Terraform, or lack thereof

I skipped Terraform because my infra is already managed elsewhere, but your repo still taught me a few neat tricksโ€”especially around the WireGuard + AWS static-IP pattern.

One open question: AWS costs

Can you share a rough monthly figure for the EC2 instance that acts as the WireGuard โ€œrouterโ€? Iโ€™m thinking along the lines of a t4g.nano/micro plus the Elastic IP fee (~US-$3) and whatever egress the mail flow generates, but Iโ€™d love to know what youโ€™re actually seeing in practice.

Thanks again for the excellent guide and the rapid updatesโ€”they saved me hours of head-scratching.

  • Author

Interesting. You said "outbound packets leave via the serverโ€™s primary IP instead of the WireGuard-mapped address". I'm curious what you're doing there. My original plan was to have all in bound and outbound map through the WireGuard containers and transit over the AWS EIP which for sending mail particularly is so important so that the SPF and reverse DNS lookups all come from the same place.

Container Issues

I haven't had any issue with my containers shutting down. I've run weeks with them operating and only had them stop when doing server maintenance. I expect that Stalwart is robust enough to handle the Auth probes and other malicious traffic. I know it's not fail2ban, but I do see a number of IPs (4192 at current count) getting blocked by these default rules:
image.png

Hopefully the docker logs give you some indication of why the containers are crashing. I've had to run BIND in debug mode initially when I was working out the configurations. I've updated my debug section with the note about running it in debug:

docker run --rm -it --entrypoint /bin/sh -v /mnt/user/appdata/bind/etc:/etc/bind -v /mnt/user/appdata/bind/cache/bind:/var/cache/bind -v /mnt/user/appdata/bind/lib/bind:/var/lib/bind -v /mnt/user/appdata/bind/var/log:/var/log internetsystemsconsortium/bind9:9.18

/usr/sbin/named -u bind -g

AWS Costs

It's been averaging $7.42 a month over the last 6 months. The bulk of the cost is ~$3.80 for the EC2 (t4g.nano) and $3.72 for the VPC which is purely the $0.005 per hour for the IPv4 address. This is still less then my ISP was going to charge for a static IP, which I was doing before I made this switch. Maybe someday we will all move to IPv6 and I can save the money.

image.png

Hey Mark,

Thanks for the detailed response and cost breakdownโ€”$7.42/month is quite reasonable, especially considering how important it is to maintain consistent SPF and rDNS for outbound mail.

Outbound IP issue โ€“ solved

On my external host, Iโ€™ve got a dedicated Docker host running WireGuard with the required ports exposed. Incoming mail worked fine after basic config, but sending was a problem. It turned out the outgoing packets were leaving via the machineโ€™s primary IP, not the additional one I mapped specifically for inbound traffic to the Docker host.

To fix it, I had to adjust the NAT rules on the host so that traffic from the WireGuard container (running on Unraid) would leave with the correct source IP. The key was to insert a higher-priority SNAT rule:

sudo iptables -t nat -I POSTROUTING 1 -s 172.17.0.2 -o team0 -j SNAT --to-source <my-mapped-external-ip> 

Now outbound connections correctly use the designated external IP, and SPF + rDNS checks pass as expected.

Container instability & ongoing attacks

Things are mostly working now, but I'm seeing repeated attacks against the containers. Stalwart does a great job blocking them, but Iโ€™ve noticed that after running for a while, the web admin UI and IMAP access become unavailableโ€”even though the container is still running.

Example logs:

2025-06-03T15:41:50Z INFO Blocked IP address (security.ip-blocked) listenerId = "imaptls", localPort = 993, remoteIp = 83.7.37.137, remotePort = 57749 
... 
2025-06-03T15:54:18Z INFO Blocked IP address (security.ip-blocked) listenerId = "https", localPort = 443, remoteIp = 83.7.37.137, remotePort = 58289 

These flood in pretty consistently. Stalwart blocks them, but Iโ€™m wondering if the web/IMAP availability issues might be related (resource exhaustion or rate-limiting?). Iโ€™ll dig into the container logs more thoroughly, but itโ€™s a strange one.

Remaining tweaks

Still need to configure the timezone and polish off some minor system settings. But overall Iโ€™m really impressed with the flexibility of this setupโ€”and learned a ton along the way. Thanks again for sharing it!

  • Author

You may be running into the same issue I had where the reverse proxy IP get's blocked. I documented the work around with a link to a similar GitHub issue. Essentially the proxy IP get's blocked and you need to remove the blocked IP from the list, and prevent it from getting re-added. Here's the process:

You can follow these steps to unblock the IP and prevent it from getting added:

  1. Remove the IP from the block list

    1. From a console in the container, either obtained from the Unraid docker UI or executing docker exec -it stalwart-mail bash

    2. Using the correct PASSWORD run:

      apt-get update && apt-get -y install curl
      curl -u 'admin:PASSWORD' \
        --header 'Content-Type: application/json' \
        --data '[{"type": "clear", "prefix": "server.blocked-ip."}]' \
        http://127.0.0.1:8080/api/settings
      
  2. Stop the stalwart-mail docker container.

  3. Add the following line to /mnt/user/appdata/stalwart-data/etc/config.toml where ###.###.###.### is the blocked IP your trying to access the console from or the public reverse proxy IP.

    config.toml

    server.allowed-ip.###.###.###.### = ""
    
  4. Start the stalwart-mail docker container.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions โ†’ Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.