VII. Setup Mailserver
In this guide, we will setup Mailserver containerized inside docker. I choose Docker Mailserver because it's a production-ready fullstack but simple containerized mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.). Only configuration files, no SQL database. Keep it simple and versioned. Easy to deploy and upgrade.
Before we setup a mail server, make sure hostname and hosts already set.
If you are not sure please read this Initial Setup guide.
In this guide will choose /srv/ as path for Docker applications.
1. Deploy Docker-Mailer
We will setup a mailer server that support:
- SSL with Let's Encrypt
- Fail2Ban Protection
- Correct Timezone
- DKIM, DMARC and SPF
- RSPAMD Spam Filtering System
a. Create a directory
mkdir /srv/mailserver/
Then go to its directory
cd /srv/mailserver/
b. Create Docker Compose
nano docker-compose.yml
Then paste this
services:
mailserver:
image: ghcr.io/docker-mailserver/docker-mailserver:latest
container_name: mailserver
# Provide the FQDN of your mail server here (Your DNS MX record should point to this value)
hostname: mail.yourdomain.com
ports:
- "25:25" # SMTP (explicit TLS => STARTTLS)
- "143:143" # IMAP4 (explicit TLS => STARTTLS)
- "465:465" # ESMTP (implicit TLS)
- "587:587" # ESMTP (explicit TLS => STARTTLS)
- "993:993" # IMAP4 (implicit TLS)
- "11334:11334" # Rspamd Web Interface
volumes:
- ./docker-data/dms/mail-data/:/var/mail/
- ./docker-data/dms/mail-state/:/var/mail-state/
- ./docker-data/dms/mail-logs/:/var/log/mail/
- ./docker-data/dms/config/:/tmp/docker-mailserver/
- ./docker-data/certbot/certs/:/etc/letsencrypt
- /etc/localtime:/etc/localtime:ro
environment:
# Brute Force Protection
- ENABLE_FAIL2BAN=1
# Using letsencrypt for SSL/TLS certificates:
- SSL_TYPE=letsencrypt
# Allow sending emails from other docker containers:
# Beware creating an Open Relay: https://docker-mailserver.github.io/docker-mailserver/latest/config/environment/#permit_docker
- PERMIT_DOCKER=network
# Mail spoofing denied: https://docker-mailserver.github.io/docker-mailserver/latest/config/environment/#spoof_protection
- SPOOF_PROTECTION=1
# Supporting MTA-STS for outbound mail: https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/mta-sts/
- ENABLE_MTA_STS=1
# Spam messages will be delivered to the Junk mailbox.
- MOVE_SPAM_TO_JUNK=1
# Set Timezone
- TZ=Europe/London
# Using RSPAMD
- ENABLE_RSPAMD=1
- ENABLE_CLAMAV=1
- RSPAMD_GREYLISTING=1
# Rspamd replaces the functionality of all these anti-spam services, disable them:
- ENABLE_OPENDKIM=0
- ENABLE_OPENDMARC=0
- ENABLE_POLICYD_SPF=0
- ENABLE_AMAVIS=0
- ENABLE_SPAMASSASSIN=0
# Provided you've set `RSPAMD_GREYLISTING=1`, also disable Postgrey:
- ENABLE_POSTGREY=0
cap_add:
- NET_ADMIN # For Fail2Ban to work
restart: unless-stopped
As you can see from above,
- The container name is
mailserver. So for the next, we will call it asmailserverinstead of Docker Mailserver. - Don't forget to adjust the timezone, date time on mailer is very important.
c. Deploy Mailserver
Now deploy the Docker Mailserver in background mode.
docker compose up -d
d. Add new default email
# we will use this email for asking ssl let's encrypt
docker exec mailserver setup email add [email protected] yourpassword
e. Try to see the logs
docker logs mailserver -f
2. Adjusting Firewall
We have to open the ports for mailer.
ufw allow 25/tcp && ufw allow 143/tcp && ufw allow 465/tcp && ufw allow 587/tcp && ufw allow 993/tcp && ufw allow 11334/tcp
3. Configure SSL
a. Getting a Certificate
docker run --rm -it \
-v "${PWD}/docker-data/certbot/certs/:/etc/letsencrypt/" \
-v "${PWD}/docker-data/certbot/logs/:/var/log/letsencrypt/" \
-p 80:80 \
certbot/certbot certonly --standalone -d mail.yourdomain.com
If you get error like this
docker: Error response from daemon: driver failed programming external connectivity on endpoint cranky_brattain (73b5db63d2323d754b209484d13efa55da9322eee67bd65a9082f65c3ff7cc92): Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use.
it means, the port 80 already in use.
maybe nginx use this, so we have to stop nginx service
systemctl stop nginx
Now run this again
docker run --rm -it \
-v "${PWD}/docker-data/certbot/certs/:/etc/letsencrypt/" \
-v "${PWD}/docker-data/certbot/logs/:/var/log/letsencrypt/" \
-p 80:80 \
certbot/certbot certonly --standalone -d mail.yourdomain.com
There is interactive action needed to answer, just press y and enter.
Done, now your mail server already support SSL.
Now don't forget to run nginx again
systemctl start nginx
b. Make it Automatically Renew
We have to create a bash script and make it execute scheduled with Cronjob.
Still on directory /srv/mailserver/.
Create a new file autorenewssl.sh.
nano autorenewssl.sh
Then paste this
#!/bin/sh
cd /srv/mailserver/
systemctl stop nginx
docker run --rm \
-v "${PWD}/docker-data/certbot/certs/:/etc/letsencrypt/" \
-v "${PWD}/docker-data/certbot/logs/:/var/log/letsencrypt/" \
-p 80:80 \
certbot/certbot renew
systemctl start nginx
Then save it by press ctrl+x then press y and enter.
Now give an executable permission on it.
chmod +x autorenewssl.sh
Make it auto renew with Cronjob
crontab -e
0 1 * * * /usr/bin/sh /srv/mailserver/autorenewssl.sh >> /var/log/autorenewssl.log 2>&1
0 1 * * * /usr/bin/sh /srv/mailserver/autorenewssl.sh >> /var/log/autorenewssl.log 2>&1
Explanation:
- Cronjob will check and auto renew the certificate for every day at 01.00 AM.
- Cronjob will log any output at /var/log/autorenewssl.log
If this is the first time you're setting the cronjob, you can test it by running this command.
env -i /bin/sh /srv/mailserver/autorenewssl.sh
4. Setup Fail2Ban
We don't need to setup Fail2Ban on email, because it was already did automatically from docker-compose.yml.
So here is the way to manage it.
a. See All Banned IP
docker exec mailserver setup fail2ban
b. Manage a Banned IP
docker exec mailserver setup fail2ban [<ban|unban> <IP>]
c. View the Fail2Ban Log
docker exec mailserver setup fail2ban log
d. Prevent container from self banned (optional)
Get self IP address and Gateway mailserver
docker inspect -f '{{range.NetworkSettings.Networks}}{{.Gateway}} {{.IPAddress}}{{end}}' mailserver
You will see the output of ip address.
172.18.0.1 172.18.0.2
Then edit the jail.local fail2ban
docker exec -it mailserver nano /etc/fail2ban/jail.local
Look for ignoreip then modify it like this
ignoreip = 127.0.0.1/8 172.18.0.1 172.18.0.2
Save it by press ctrl+x then press y and enter.
Now restart the mail server
docker restart mailserver
5. Create Email Account
a. Create email for dmarc
We will use this email for setup DMARC later.
docker exec mailserver setup email add [email protected] yourpassword
Then you will asked to input it password.
b. Create no_reply email
docker exec mailserver setup email add [email protected] yourpassword
We will use [email protected] for OpenSSO later.
6. Setup DKIM
Mailserver v14.0.0 doesn't include DKIM as default.
so we have to generate it.
a. Generate new DKIM
Now we generate new DKIM with keysize 1024
docker exec -it mailserver setup config dkim keysize 1024 domain 'yourdomain.com'
Then you will see the output result of generated new DKIM in your screen.
b. Add it in your DNS Cloudflare.
Type: TXT
Name: mail._domainkey
Proxy: DNS Only
TTL: 3600 / 1hr
Content:
v=DKIM1; h=sha256; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsDYT31TLY8vfXEzT2CnQ3/SBf8krZFtZgkdbeEUbfvnCc64vsplACJnAjfKUx7QgVyf134hdhFy8bdJe2tzbe2dyNJ8AX/jSDDoFD+ocXqo6uNZLD+4xi0/xiHdv8yWeB7KUbzi0/u2nJWFAzGyZdR3i7O8FwTfTx1v+IJFHitwIDAQAB
Make sure you remove any double quotes and concat it carefully like the example above.
Because it could lead invalid signature if you didn't care about this.
Incase you want to delete DKIM
a. Go to the mailserver container
docker exec -it mailserver bash
b. Delete current DKIM
rm -rf /tmp/docker-mailserver/opendkim
Now exit from mailserver container.
exit
7. Setup DMARC
Go to your clouflare and then add this in DNS section.
Type: TXT
Name: _dmarc
Proxy: DNS Only
TTL: 3600 / 1hr
Content:
v=DMARC1; p=reject; sp=reject; fo=0; adkim=r; aspf=r; pct=100; rf=afrf; ri=86400; rua=mailto:[email protected]; ruf=mailto:[email protected]
Done.
8. Setup SPF
Go to your clouflare and then add this in DNS section.
Type: TXT
Name: yourdomain.com
Proxy: DNS Only
TTL: 3600 / 1hr
Content:
v=spf1 mx include:_spf.google.com ~all
Done.
9. Test your email
We have already setup the mail server. There is no web email client in this server.
So you need an email client software like Mozilla Thunderbird.
Here is the information detail about your email server.
Username : [email protected]
Password : [yourpassword]
IMAP Server : mail.yourdomain.com
IMAP Port : 993
Connection : SSL/TLS
SMTP Server : mail.yourdomain.com
SMTP Port : 465
Connection : SSL/TLS
10. Test the security
We already setup the basic security DKIm, DMARC and SPF.
Now you can test it using website:
Now you already have the secured mail server.
11. Setup Roundcube [Optional]
Roundcube is an email client based web app.
a. Create new directory roundcube
mkdir /srv/roundcube
Now go to its directory
cd /srv/roundcube
b. Create new file docker-compose.yml
nano docker-compose.yml
and then paste this
version: '2'
services:
roundcubemail:
image: roundcube/roundcubemail:latest
container_name: roundcubemail
restart: unless-stopped
volumes:
- ./www:/var/www/html
- ./db/sqlite:/var/roundcube/db
ports:
- 9002:80
environment:
- ROUNDCUBEMAIL_DB_TYPE=sqlite
- ROUNDCUBEMAIL_SKIN=elastic
- ROUNDCUBEMAIL_DEFAULT_HOST=tls://mail.yourdomain.com
- ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.yourdomain.com
now save it by press ctrl+x then press y and enter.
Don't forget to rename tls://mail.yourdomain.com with your domain.
c. Deploy Roundcube
docker compose up -d
d. Add config to nginx
nano /etc/nginx/conf.d/domain.conf
add this to very below
server {
listen 80;
listen [::]:80;
server_name webmail.yourdomain.com;
root /srv/roundcube/www;
location / {
proxy_pass http://172.17.0.1:9002;
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;
}
}
now save it by press ctrl+x then press y and enter.
e. Make a Test
Make sure your domain.conf is correct.
nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
f. Restart NGINX
systemctl restart nginx
Done, now you can access your webmail by visiting: https://webmail.yourdomain.com
Your informasi login is same with your email.
Example:
Username : [email protected]
Password : [yourpassword]
12. Setup Rspam Web UI [Optional]
Rspamd offer a web interface as default.
You can visit directly to the page http://123.123.123.123:11334.
But it will require a password to open it.
a. Generate a password for WEB UI
docker exec -it mailserver rspamadm pw
it'll ask you to input your password for Web UI.
$2$gg7f7cjxt9ky7dcounhac91jcki4uo48$brtzi1s5rimhpz56geh5o7uwup131bxxx
b. Setup your password for WEB UI
Now we will going to setup the password.
Make sure you're still in right path
cd /srv/mailserver
Create new config custom-commands.conf
nano docker-data/dms/config/rspamd/custom-commands.conf
set-option-for-controller password "$2$gg7f7cjxt9ky7dcounhac91jcki4uo48$brtzi1s5rimhpz56geh5o7uwup131bxxx"
Then save it by press ctrl+x and enter.
c. Restart Mailserver
docker compose restart
Now you can visit to http://123.123.123.123:11334.

13. Setup Queue Rate Postfix [Optional]
The reason we should set up this is to control how many emails the server sends at once and how fast it sends them, so we can:
- Avoid overloading the server
- Prevent get blacklisted as spam
- Respect rate limits of destination servers (like Gmail, Outlook, etc.)
- Lower memory and CPU usage on high email traffic
Now we will going to setup this,
Make sure we still on the right path
cd /srv/mailserver/
Create new config postfix-main.cf to override the default postfix config
nano docker-data/dms/config/postfix-main.cf
then paste this
# Disable local recipient delivery for system users
local_recipient_maps =
# Concurrency limits
# ---
# Max emails at once per domain
default_destination_concurrency_limit = 2
# Max SMTP connections per domain
smtp_destination_concurrency_limit = 2
# Start with 1, increase if successful
initial_destination_concurrency = 1
# Delay between deliveries
# ---
# Wait between batches to same domain
smtp_destination_rate_delay = 5s
# Rate limiting
# ---
# Maximum messages per connection
smtp_destination_recipient_limit = 100
# Retry strategies
# ---
# Max wait before retry: ~4 hrs
maximal_backoff_time = 14400s
# Give up after 3 days
maximal_queue_lifetime = 3d
# Caching
# ---
# Keep SMTP connection cache for 5s
smtp_connection_cache_time_limit = 5s
# Bounce and Error handling
# ---
# Be conservative with retries
bounce_queue_lifetime = 3d
Done, now you have to down the container and then start the mailserver
docker compose down -v
docker compose up -d
Make a test to make sure default postfix config has been override
docker exec -it mailserver postconf default_destination_concurrency_limit
The output should like this below
default_destination_concurrency_limit = 2 # Max emails at once per domain
14. Uninstall Completely
If you has been failed to configure the Mailserver or just in case want to completely uninstall Mailserver.
a. Remove Mailserver Service
Go to the mailserver directory
cd /srv/mailserver/
Take down, remove the service and delete its volume on docker.
docker compose down --volumes
- This only remove the mailserver service and remove all its data.
- This won't uninstall your docker system.
b. Delete its directory
Go up directory.
cd ..
Delete mailserver directory.
rm -rf mailserver
c. Verify the service
To make sure it has been removed.
docker container ls -a
Make sure there is no container name mailserver on the list.
Done.