You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I see a lot of confusion about how to properly configure Stalwart behind a proxy. I also had a lot of trouble with this, so I wanted to share my functional setup, to help others. This should act as a complete guide to set up stalwart.
Troubleshooting tools:
The most helpful tool I used through this project was: https://mxtoolbox.com/
It can help tremendously to work out email spam flags and DNS/PTR issues.
OS-level setup (outside of docker)
Due to everything running inside docker, there should be no setup required.
I do not explicitly cover generation of SSL certificates in this guide, but certbot/letsencrypt is very well documented.
However, make sure that if you have any firewalls (such as UFW), that there is an exception made for the necessary ports.
Port Forwarding:
In your router's configuration UI, forward the following ports for TCP:
25 #to receive emails from other email servers
465 #to submit emails to your email server
587 #alternative port to submit emails to your email server
993 #to log into your email
Docker configs:
The compose file defines which programs to install, and some configurations for those running program(s), configuration includes: mapping files/directories, mapping ports, automatic start/restart, etc.
Here are some helpful commands to run Stalwart and NGINX in docker.
Ensure you are in the same directory as docker_compose.yml
Run: Run the command docker compose up -d
-d detaches from the console output of the running programs
Restart: docker compose up -d --force-recreate
Stop: docker compose down
Docker compose
./docker_compose.yml
services:
stalwart:
container_name: stalwart
image: stalwartlabs/stalwart:latest
networks:
- docker_default #the docker default network is kinda redundant here, but might be useful if you have an NGINX http/reverse proxy instance running from a separate docker compose.
volumes:
- ./stalwart-mail:/opt/stalwart #filepath for all stalwart data, configs, database, logs, etc.
- /etc/letsencrypt:/etc/letsencrypt:ro #contains ssl certs for my domain. These certs are generated by certbot
restart: unless-stopped #docker will start this container after reboots, unless you stop the container (e.g. docker compose down)
#if you want to use Stalwart without NGINX as a reverse proxy, remove the NGINX section, and uncomment these ports:
#ports:
# - 25:25 #SMTP (receives emails from other email servers)
# - 465:465 #SMTP submissions (users sending emails)
# - 587:587 #SMTP submissions with STARTTLS #i wasn't able to properly/fully test this one, so if you end up having issues, try disabling this block and port 587 altogether.
# - 993:993 #IMAP (for logging in, checking emails, clients)
nginx:
image: nginx:stable
container_name: nginx-stalwart
networks:
- docker_default #the docker default network is kinda redundant here, but might be useful if you have an NGINX http/reverse proxy instance running from a separate docker compose.
volumes:
#these are marked :ro for read only mode, not 100% necessary, but they are there just to avoid potential issues.
- ./nginx/conf.d:/etc/nginx/conf.d/:ro #*.conf files placed in this folder will be appended into the default nginx.conf inside the http directive. Needed to configure reverse proxy for the Stalwart web UI
- ./nginx/stream_conf.d:/etc/nginx/stream_conf.d/:ro #*.conf files placed in this folder will be appended into the default nginx.conf inside the stream directive. Needed to configure reverse proxy for email protocols
- /docker/configs/nginx/nginx.conf:/etc/nginx/nginx.conf:ro #default config that includes things like SSL certs
- /etc/letsencrypt:/etc/letsencrypt:ro #contains ssl certs for my domain. These certs are generated by certbot
ports:
- 25:25 #SMTP (receives emails from other email servers)
- 465:465 #SMTP submissions (users sending emails)
- 587:587 #SMTP submissions with STARTTLS #i wasn't able to properly/fully test this one, so if you end up having issues, try disabling this block and port 587 altogether.
- 993:993 #IMAP (for logging in, checking emails, clients)
restart: unless-stopped #docker will start this container after reboots, unless you stop the container (e.g. docker compose down)
networks:
docker_default: #this docker network refers to my "main" docker compose network. This is only necessary because I host a website and other services on the same server.
external: true
NGINX configs:
./nginx/nginx.conf
Most of this is default, I only added the resolver and ssl_certificate/key lines.
#these upstream blocks with the resolve argument are used to ensure that NGINX still functions even if the stalwart container is stopped or otherwise inaccessible.
#without resolve, nginx will give an emerg error and refuse all connections.
#see nginx.conf, where the resolver is configured
upstream stalwart_upstream {
zone stalwart_upstream 64k;
server stalwart:8080 resolve;
}
server {
server_name mail.example.com; #this allows connecting to the stalwart web UI
#this allows for automatic discovery and configuration for your email server.
#There's also autodiscover.example.com, but I found that address has compatability issues with some clients.
server_name autoconfig.example.com;
listen 443 ssl;
location / {
proxy_pass http://stalwart_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
./nginx/stream_conf.d/stalwart.conf
#these upstream blocks with the resolve argument are used to ensure that NGINX still functions even if the stalwart container is stopped or otherwise inaccessible.
#without resolve, nginx will give an emerg error and refuse all connections.
#see nginx.conf, where the resolver is configured
upstream stalwart_smtp {
zone stalwart_smtp 64k;
server stalwart:25 resolve;
}
upstream stalwart_smtps {
zone stalwart_smtps 64k;
server stalwart:465 resolve;
}
#i wasn't able to properly/fully test this one, so if you end up having issues, try disabling this block and port 587 altogether.
upstream stalwart_smtp_starttls {
zone stalwart_smtp_starttls 64k;
server stalwart:587 resolve;
}
upstream stalwart_imaps {
zone stalwart_imap 64k;
server stalwart:993 resolve;
}
#mapping to the internal plain-text ports (i.e. 143) gives security errors, Stalwart disables these unencrypted ports for security, so that people don't accidentally use them
server {
#this uses starttls
listen 25; #ssl or proxy_protocol shouldn't be used here. I couldn't get the example from the stalwart docs to work properly.
proxy_pass stalwart_smtp;
proxy_protocol on;
}
server {
listen 465;
proxy_pass stalwart_smtps;
proxy_protocol on;
}
server {
#this uses starttls
#i wasn't able to properly/fully test this one, so if you end up having issues, try disabling this block and port 587 altogether.
listen 587;
proxy_pass stalwart_smtp_starttls;
proxy_protocol on;
}
server {
listen 993;
proxy_pass stalwart_imaps;
proxy_protocol on;
}
Configuration in Stalwart Web UI:
Management > Directory > Domains:
Set Domain Name as your plain domain name ex: example.com
Management > Directory > Accounts:
Click + Create account
Set Login name to postmaster@example.com
Make sure the login name includes the domain, if it doesn't you can have login issues with some email clients
Set Email to postmaster@example.com
(Optional) At the top, click on the Authentication tab
Set a password
Click Save changes
Standards like RFC 5321 require the postmaster email address to be valid.
Settings > Server > Network:
Hostname should be mail.example.com.
The mail. part is non-negotiable, you will get spam flags on emails if you do not structure it like this.
Do not use the "Proxy Networks" feature on this page. It will cause http/https to require proxy protocol which isn't (and maybe shouldn't be) set up for those connections. The NGINX reverse proxy config provided above already does all of the necessary proxy conversion. If you do enable this, the web UI will be unreachable unless you have enabled proxy protocol for http/s in NGINX.
Settings > Server > Listeners:
For all listeners except http/https
Edit the listener you want to apply the proxy to
Under the Proxy Protocol header, enable Override proxy networks
At Proxy networks (optional), click + Add
Enter the NGINX proxy IP: (ex: 172.18.0.16)
To obtain the NGINX container IP: docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx-stalwart - In my case, this IP is 172.18.0.16
I suggest using the full block of Docker internal network bridge IPs, i.e. 172.18.0.0/16, because docker might change the internal IP address at some point.
Settings > Server > TLS > Certificates:
Even though we are using NGINX as a reverse proxy, I couldn't figure out how to nicely configure it so that Stalwart doesn't need any TLS certificate config.
Click + Create certificate
Certificate ID: name it whatever you want, I chose letsencrypt-porkbun so that I know it's referring to the certs that letsencrypt pulls from porkbun
Certificate: Use Stalwart's file macro to place the certificate into the config:
Refer to the compose file where I map the letsencrypt directory to give access to certs: /etc/letsencrypt:/etc/letsencrypt
Subject Alternative Names:
This may not be necessary, but just in case I entered mail.example.com
Settings > SMTP > Inbound > AUTH Stage:
Make sure Require Authentication and Allowed Mechanisms cover all submission ports (i.e. 465 and 587), if the submission ports aren't covered, you will be an open relay, which is bad. In my case, these values were defaulted to local_port == 587, I changed both to: local_port != 25, which seems to be the new default.
Settings > SMTP > Outbound > Connection:
Edit default
Set EHLO Hostname as mail.example.com
This might not actually be necessary configuration, if it's empty it should default to the server's hostname
By default, you should see 2 DKIM signatures, labelled as ed25519-example.com and rsa-example.com (example.com being your domain name, as defined in Management > Directory > Domains)
In both signatures, ensure that Domain Name is set to your domain (ex: example.com)
Generate the key pair using the openssl command, or another tool:
# For the rsa-example.com signature
openssl genrsa -out rsa_private.pem 2048
openssl rsa -in rsa_private.pem -pubout -out dkim_public.pem
# For the ed25519-example.com signature
openssl genpkey -algorithm ed25519 -out ed25519_private.pem
openssl pkey -in dkim_private.pem -pubout -out ed25519_public.pem
For both signatures, place the generated private key into the Private Key entry box.
Best practice is to keep these keys as files, and include them into this config using Stalwart's file macro. (ex: %{file:/opt/stalwart/rsa-private.pem}%)
Double check the docker volume mapping, in the provided compose file, the mapping is: ./stalwart-mail:/opt/stalwart.
Make sure these private key files aren't publicly accessible, change file access permissions to limit access.
You can copy/paste the key directly, including the -----BEGIN PRIVATE KEY----- and corresponding -----END PRIVATE KEY-----
DNS record configuration:
For email delivery to work properly, you will need the following records:
A record:
host: mail.example.com
answer: your server's static WAN IP (public IP)
This must be a unique record, and cannot be shared by a wildcard record (i.e. *.example.com)
This record gives the public IP of your server when looking up the mail.example.com domain.
MX record:
host: example.com
answer: mail.example.com
This record tells email servers that the email server for example.com is at mail.example.com
TXT record:
host: example.com
answer: v=spf1 ip4:[your server's static WAN IP (public IP)] mx ~all
This record serves to verify that your email server at the public IP address is authorized to send emails on behalf of your domain.
This record gives other email servers directions on how to submit usage/conformance reports to your server
Remember those DKIM signatures you generated earlier using the openssl command? Now we need to configure the public key side of those.
TXT record:
host: rsa-default._domainkey.example.com
answer: v=DKIM1; k=rsa; p=[Your RSA public key here, do not include the formatting of ---BEING PUBLIC KEY---- or ---END PUBLIC KEY----]
This record allows other email servers to verify that an email originated from your server
TXT record:
host: ed25519-default._domainkey.example.com
answer: v=DKIM1; k=ed25519; p=[Your ED25519 public key here, do not include the formatting of ---BEING PUBLIC KEY---- or ---END PUBLIC KEY----]
PTR record/static IP:
For these security methods to work, your IP address must have a PTR record that correlates the WAN IP back to your domain name. You cannot directly create this record, and you must contact your ISP to set it for you. Though, it requires a static IP.
You may need to wait an hour or more before the PTR record updates.
Finale
If you haven't already, create yourself a personal user account Management > Directory > Accounts
Click + Create account
Set Login name to my_login_name@example.com
Make sure the login name includes the domain, if it doesn't you can have login issues with some email clients
Set Email to my_login_name@example.com
At the top, click on the Authentication tab
Set a password
Click Save changes
Assuming you followed the instructions correctly (and assuming I wrote the instructions correctly), you should now be able to log into your Stalwart email account from an email client. For testing I used Thunderbird.
If your email client asks for a full name, enter any name you would like
Enter your email address
The email client should automatically find the server configuration
Login using the password you set earlier.
Send a test email to yourself, I sent testing emails to my private Gmail account.
After you receive the email in Gmail, or any other email client, find the option to "show original". In Gmail, with the email open, press the 3 dots in the top-right of the email, and click Show original.
This gives more information about the delivery of your email. If you see a section labelled X-Spam-Status or X-Spam-Result, it means you have some problems to fix.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I see a lot of confusion about how to properly configure Stalwart behind a proxy. I also had a lot of trouble with this, so I wanted to share my functional setup, to help others. This should act as a complete guide to set up stalwart.
Troubleshooting tools:
The most helpful tool I used through this project was: https://mxtoolbox.com/
It can help tremendously to work out email spam flags and DNS/PTR issues.
OS-level setup (outside of docker)
Due to everything running inside docker, there should be no setup required.
I do not explicitly cover generation of SSL certificates in this guide, but certbot/letsencrypt is very well documented.
However, make sure that if you have any firewalls (such as UFW), that there is an exception made for the necessary ports.
Port Forwarding:
Docker configs:
The compose file defines which programs to install, and some configurations for those running program(s), configuration includes: mapping files/directories, mapping ports, automatic start/restart, etc.
Here are some helpful commands to run Stalwart and NGINX in docker.
docker compose up -ddocker compose up -d --force-recreatedocker compose downDocker compose
./docker_compose.ymlNGINX configs:
./nginx/nginx.conf./nginx/conf.d/stalwart_http.conf./nginx/stream_conf.d/stalwart.confConfiguration in Stalwart Web UI:
Management > Directory > Domains:
Domain Nameas your plain domain nameex: example.comManagement > Directory > Accounts:
+ Create accountLogin nametopostmaster@example.comEmailtopostmaster@example.comSave changesSettings > Server > Network:
Hostnameshould bemail.example.com.Settings > Server > Listeners:
Proxy Protocolheader, enableOverride proxy networksProxy networks (optional), click+ Add172.18.0.16)docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx-stalwart- In my case, this IP is172.18.0.16172.18.0.0/16, because docker might change the internal IP address at some point.Settings > Server > TLS > Certificates:
+ Create certificateletsencrypt-porkbunso that I know it's referring to the certs that letsencrypt pulls from porkbunmail.example.comSettings > SMTP > Inbound > AUTH Stage:
Require AuthenticationandAllowed Mechanismscover all submission ports (i.e. 465 and 587), if the submission ports aren't covered, you will be an open relay, which is bad. In my case, these values were defaulted tolocal_port == 587, I changed both to:local_port != 25, which seems to be the new default.Settings > SMTP > Outbound > Connection:
defaultEHLO Hostnameasmail.example.comSettings > SMTP > Sender Authentication > Signatures:
ed25519-example.comandrsa-example.com(example.combeing your domain name, as defined inManagement > Directory > Domains)Domain Nameis set to your domain (ex:example.com)opensslcommand, or another tool:Private Keyentry box.DNS record configuration:
For email delivery to work properly, you will need the following records:
mail.example.comyour server's static WAN IP (public IP)example.commail.example.comexample.comv=spf1 ip4:[your server's static WAN IP (public IP)] mx ~all_dmarc.example.comv=DMARC1; p=none; rua=mailto:postmaster@example.com;Remember those DKIM signatures you generated earlier using the openssl command? Now we need to configure the public key side of those.
rsa-default._domainkey.example.comv=DKIM1; k=rsa; p=[Your RSA public key here, do not include the formatting of ---BEING PUBLIC KEY---- or ---END PUBLIC KEY----]ed25519-default._domainkey.example.comv=DKIM1; k=ed25519; p=[Your ED25519 public key here, do not include the formatting of ---BEING PUBLIC KEY---- or ---END PUBLIC KEY----]PTR record/static IP:
For these security methods to work, your IP address must have a PTR record that correlates the WAN IP back to your domain name. You cannot directly create this record, and you must contact your ISP to set it for you. Though, it requires a static IP.
You may need to wait an hour or more before the PTR record updates.
Finale
Management > Directory > Accounts+ Create accountLogin nametomy_login_name@example.comEmailtomy_login_name@example.comSave changesAssuming you followed the instructions correctly (and assuming I wrote the instructions correctly), you should now be able to log into your Stalwart email account from an email client. For testing I used Thunderbird.
If your email client asks for a full name, enter any name you would like
Enter your email address
The email client should automatically find the server configuration
Login using the password you set earlier.
Send a test email to yourself, I sent testing emails to my private Gmail account.
Show original.X-Spam-StatusorX-Spam-Result, it means you have some problems to fix.Beta Was this translation helpful? Give feedback.
All reactions