May 20, 20251 yr 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. ๐ป
May 27, 20251 yr 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." }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!
May 27, 20251 yr 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.
May 27, 20251 yr 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.2It 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 versionwebadmin.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.
May 27, 20251 yr 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?
May 29, 20251 yr 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 yoursColo 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 behaviourBoth 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.First impressions of StalwartI 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 thereofI 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 costsCan 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.
May 30, 20251 yr 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 IssuesI 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: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 -gAWS CostsIt'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.
June 3, 20251 yr 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 โ solvedOn 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 attacksThings 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 tweaksStill 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!
June 3, 20251 yr 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:Remove the IP from the block listFrom a console in the container, either obtained from the Unraid docker UI or executing docker exec -it stalwart-mail bashUsing 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 Stop the stalwart-mail docker container.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.tomlserver.allowed-ip.###.###.###.### = "" 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.