Installation and configuration of mail server with Postfix ands devcot
Date: May 2026 Version:
- postfix : 3.10.5
- dovecot : v2.4.1-4
Begin with: https://neptunet.fr/messagerie-debian/
=> but use postfix-admin web service to configure postfix
After:
https://www.digitalocean.com/community/tutorials/how-to-configure-a-mail-server-using-postfix-dovecot-mysql-and-spamassassin
=> here all in command line
For the next:
- Assuming you are ‘root’ or you use ‘sudo’
- Mail domain is : ‘peanutsworld.org’
- Mail server is referenced by : ‘mailp’
- Firewall : use ‘ufw’
Package installation
#> apt update#> apt install mariadb-server tree mailutils postfix postfix-mysql -y
mariadb-server: fork de MySql
#> dpkg-reconfigure postfix#> apt-get install dovecot-mysql dovecot-pop3d dovecot-imapd dovecot-managesieved dovecot-lmtpd -y
Open ports used by mailserver:
- On mailserver add rule in firewall:
- smtp port 25:
#> ufw allow 25 - imap port 143:
#> ufw allow 143
- smtp port 25:
- Port 25: hosting provider
- If your mail server is hosted on an hosting provider (like vultr)
YOU MUST REQUEST (ie. ticket) the outing port 25 opening
Provider close out port 25 to avoid to host mail spammers - If your provider refuse (like in my case (Fuck off vultr) … ) : you must configure a relay in postfix.
- If your mail server is hosted on an hosting provider (like vultr)
YOU MUST REQUEST (ie. ticket) the outing port 25 opening
Configuration of SGBD:
Database and users:
#> mariadb-secure-installation#> mariadb -u root -p
CREATE DATABASE postfix;
CREATE USER 'postfix'@'localhost' IDENTIFIED BY '<password>';
GRANT ALL PRIVILEGES ON `postfix` . * TO 'postfix'@'localhost';
CREATE USER 'mailuser'@'localhost' IDENTIFIED BY '<password>';
GRANT SELECT ON `postfix`.* TO 'mailuser'@'localhost';
FLUSH PRIVILEGES;
QUIT ;
Create 3 tables:
#> mariadb -u root -pMariaDB [(none)]> USE postfix;- Domains:
CREATE TABLE `virtual_domains` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- User:
CREATE TABLE `virtual_users` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`use_id` VARCHAR(120) NOT NULL,
`password` VARCHAR(106) NOT NULL,
`email` VARCHAR(120) NOT NULL,
`home` VARCHAR(255) NOT NULL,
`uid` INTEGER NOT NULL,
`gid` INTEGER NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- Aliases:
CREATE TABLE `virtual_aliases` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- Add domains, user and alias:
INSERT INTO `postfix`.`virtual_domains`
(`id` ,`name`)
VALUES
('1', 'peanutsworld.org');
=> for instance manage only ‘peanutsworld.org’ not ‘micro-cosme.com’ because it seems to be a mess ton manage ssl certificat for multi domains
INSERT INTO `postfix`.`virtual_users` (`id`, `domain_id`, `usernameid`, `password` , `email`, `home`, `uid`, `gid`) VALUES
('1', '1', 'snoopy', ENCRYPT('<password>', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'snoopy@peanutsworld.org', '/var/mail/vhosts/peanutsworld.org/snoopy', 5000, 5000),
('2', '1', 'charlie', ENCRYPT('<password>', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'charlie@peanutsworld.org', '/var/mail/vhosts/peanutsworld.org/charlie', 5000, 5000);
INSERT INTO `postfix`.`virtual_aliases` (`id`, `domain_id`, `source`, `destination`) VALUES
('1', '1', 'dog@peanutsworld.org', 'snoopy@peanutsworld.org'),
('2', '1', 'flyingace@peanutsworld.org', 'snoopy@peanutsworld.org'),
('3', '1', 'boy@peanutsworld.org', 'charlie@peanutsworld.org'),
('4', '1', 'master@peanutsworld.org', 'charlie@peanutsworld.org'),
Postfix configuration
main.cf
- copy /etc/postfix/main.cf to /etc/postfix/main.cf.orig
- Edit: /etc/postfix/main.cf
- Add in mainc.cf:
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
- Comment:
smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
- Change:
smtpd_tls_security_level = none
smtp_tls_security_level = none
mydestination = localhost
Domain(s): mysql-virtual-mailbox-domains.cf
- Edit: /etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = <password>
hosts = 127.0.0.1
dbname = postfix
query = SELECT 1 FROM virtual_domains WHERE name='%s'
- Test with:
#> service postfix restart#> postmap -q peanutsworld.org mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
Mails/users: postfix/mysql-virtual-mailbox-maps.cf
- Edit: /etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser
password = <password>
hosts = 127.0.0.1
dbname = postfix
query = SELECT 1 FROM virtual_users WHERE email='%s'
- test with:
#> service postfix restart#> postmap -q snoopy@peanutsworld.org mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf#> postmap -q charlie@peanutsworld.org mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
SMTP Relay:
Cf: https://superuser.com/questions/280165/how-do-i-change-postfix-port-from-25-to-587
Alias : /etc/postfix/mysql-virtual-alias-maps.cf
- Edit: /etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = Fr3dL!k3M4ri4DB
hosts = 127.0.0.1
dbname = postfix
query = SELECT destination FROM virtual_aliases WHERE source='%s'
- Test with:
#> service postfix restartmail_privileged_group = mail#> postmap -q flyingace@peanutsworld.org mysql:/etc/postfix/mysql-virtual-alias-maps.cf#> postmap -q boy@peanutsworld.org mysql:/etc/postfix/mysql-virtual-alias-maps.cf
Dovecot configuration
- copy /etc/dovecot/dovecot.conf to /etc/dovecot/dovecot.conf.orig
- copy /etc/dovecot/conf.d/10-mail.conf to /etc/dovecot/conf.d/10-mail.conf.orig
- copy /etc/dovecot/conf.d/10-auth.conf to /etc/dovecot/conf.d/10-auth.conf.orig
- copy /etc/dovecot/conf.d/10-master.conf to /etc/dovecot/conf.d/10-master.conf.orig
- copy /etc/dovecot/conf.d/10-ssl.conf to /etc/dovecot/conf.d/10-ssl.conf.orig
/etc/dovecot/dovecot.conf
Edit: /etc/dovecot/dovecot.conf
Add:
!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap lmtp
/etc/dovecot/conf.d/10-mail.conf
- Edit: /etc/dovecot/conf.d/10-mail
- Cf: https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html
- Add :
mail_driver = maildir
mail_path = /var/mail/vhosts/%{user | domain}/%{user | username}
mailbox_list_layout = fs
mail_inbox_path = /var/mail/vhosts/%{user | domain}/%{user | username}/INBOX
mailbox_directory_name = DataMail
/var/mail
#> mkdir -p /var/mail/vhosts/peanutsworld.org#> groupadd -g 5000 vmail#> useradd -g vmail -u 5000 vmail -d /var/mail#> chown -R vmail:vmail /var/mail
/etc/dovecot/conf.d/10-auth.conf
- Edit: /etc/dovecot/conf.d/10-auth.conf
- Uncomment:
auth_allow_cleartext = yes - Uncomment:
auth_mechanisms = plain login - Comment :
!include auth-system.conf.ext - Uncomment:
!include auth-sql.conf.ext
/etc/dovecot/conf.d/auth-sql.conf.ext
- SQL base is used to manage authentification and user space, as user
‘login’ is too the name of mail path (eg:
/var/mail/vhost/
/ )
mail adress can not be use as user ‘login’:
eg : for ‘example.org’ with ‘guy’ user ->- /var/mail/vhost/example.org/guy@example.org is ugly
- /var/mail/vhost/example.org/guy is better. So, in database thereis two field: one for email and another for userid. It is this ‘userid’ used as ‘user’ in mails clients, not the e-mail.
- Edit: /etc/dovecot/conf.d/auth-sql.conf.ext
- Add:
sql_driver = mysql
mysql localhost {
user = mailuser
password = Fr3dL!k3M4ri4DB
dbname = postfix
#ssl = yes
#ssl_client_ca_dir = /etc/ssl/certs
}
passdb sql {
passdb_default_password_scheme = SHA512-CRYPT
query = SELECT user_id as username, \
password, \
(SELECT name FROM virtual_domains WHERE id = domain_id) as domaine_name \
FROM virtual_users \
WHERE user_id = '%{user | username}' AND \
domain_id = (SELECT id FROM virtual_domains WHERE name = '%{user | domain}');
}
userdb sql {
query = SELECT home, uid, gid \
FROM virtual_users \
WHERE user_id= '%{user | username}' AND \
domain_id = (SELECT id FROM virtual_domains WHERE name = '%{user | domain}');;
# For using doveadm -A:
iterate_query = SELECT user_id AS username FROM virtual_users;
}
/etc/dovecot ownership
#> chown -R vmail:dovecot /etc/dovecot#> chmod -R o-rwx /etc/dovecot
/etc/dovecot/conf.d/10-master.conf
- Edit: /etc/dovecot/conf.d/10-master.conf
- Add / modify:
service imap-login {
inet_listener imap {
port = 143
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
#inet_listener lmtp {
# Avoid making LMTP visible for the entire internet
#address =
#port =
#}
}
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
unix_listener auth-userdb {
mode = 0600
user = vmail
group = vmail
}
#unix_listener /var/spool/postfix/private/auth {
# mode = 0666
#}
user = dovecot
}
service auth-worker {
# Auth worker process is run as root by default, so that it can access
# /etc/shadow. If this isn't necessary, the user should be changed to
# $SET:default_internal_user.
user = vmail
}
/etc/dovecot/conf.d/20-lmtp.conf
- Edit: /etc/dovecot/conf.d/20-lmtp.conf
- change /add :
protocol lmtp {
auth_username_format = %{user}
}
/etc/dovecot/conf.d/10-ssl.conf
- Edit: /etc/dovecot/conf.d/10-ssl.conf
- change :
ssl = no
Mailserver secure
To secure mailserver you need to have a certificat for your domain associated with email: eg: snnopy@peanutsworld.org -> a certificat for ‘peanutsworld.org’
DNS entry domain.name <-> ip of mailserver
CERTBOT.EFF process (see below) work well, but you need to have a DNS entry associating ‘domain’ and the machine (in this case mailserver) from you make the certificat request.
Get a SSL certificat:
- Go to: https://certbot.eff.org/instructions?ws=other&os=pip for instructions, but it is explain too here again:
#> apt update#> apt install python3 python3-dev python3-venv libaugeas-dev clang -y#> python3 -m venv /opt/certbot#> /opt/certbot/bin/pip install --upgrade pip#> /opt/certbot/bin/pip install certbot#> ln -s /opt/certbot/bin/certbot /usr/local/bin/certbot#> certbot certonly --standalone- After to have enter domain(s) name(s), this is the result:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Please enter the domain name(s) you would like on your certificate (comma and/or space separated) (Enter 'c' to cancel): peanutsworld.org peanuts.world
Requesting a certificate for peanutsworld.org and peanuts.world
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/<domain/fullchain.pem
Key is saved at: /etc/letsencrypt/live/<domaon/privkey.pem
This certificate expires on year-month-day.
These files will be updated when the certificate renews.
NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps
to enable that functionality . See https://certbot.org/renewal-setup for instructions.
SSL Certificat renewal
Cf : https://eff-certbot.readthedocs.io/en/latest/using.html#setting-up-automated-renewal
- Add automatically certification renewal:
#> SLEEPTIME=$(awk 'BEGIN{srand(); print int(rand()*(3600+1))}'); echo "0 0,12 * * * root sleep $SLEEPTIME && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null
- Manage mail server during renewal :
#> sh -c 'printf "#!/bin/sh\nservice postfix stop\nservice dovecot stop\n" > /etc/letsencrypt/renewal-hooks/pre/mailserver.sh'
#> sh -c 'printf "#!/bin/sh\nservice postfix start\nservice dovecot start\n" > /etc/letsencrypt/renewal-hooks/post/mailserver.sh'
#> chmod 755 /etc/letsencrypt/renewal-hooks/pre/mailserver.sh
#> chmod 755 /etc/letsencrypt/renewal-hooks/post/mailserver.sh
Postfix: /etc/postfix/main.cf
- Edit: /etc/postfix/main.cf
- Add / update:
smtpd_tls_key_file = /etc/letsencrypt/live/peanutsworld.org/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/peanutsworld.org/fullchain.pem
smtpd_tls_security_level = encrypt
Postfix: /etc/postfix/master.cf
- To use 587 port uncomment:
submission inet n - y - - smtpd
Dovecot: /etc/dovecot/conf.d/10-ssl.conf
- Edit: /etc/dovecot/conf.d/10-ssl.conf
- Add /update:
ssl = yes
ssl_server_cert_file = /etc/letsencrypt/live/peanutsworld.org/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/peanutsworld.org/privkey.pem
Port 993, 587
- Add a firewall rule:
#> ufw allow 993
#> ufw allow 587
Test SSL configuration:
To be sure all is well configured before to try with your email client (sometimes its sucks, like thunderbird, because other thinks that ssl ..):
IMAP:
#> openssl s_client -crlf -connect <mail_server_ip>:993
:
All information about ssl certificat are displaye and:
OK [CAPABILITY IMAP4rev1 LOGIN-REFERRALS ID ENABLE IDLE SASL-IR LITERAL+ AUTH=PLAIN AUTH=LOGIN] Dovecot ready.
01 login <user> <password> :
01 OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS [...] NOTIFY LITERAL+] Logged in
SMTP :
#> openssl s_client -connect mailp.peanutsworld.org:25 -starttls smtp -crlf#> openssl s_client -connect mailp.peanutsworld.org:587 -starttls smtp -crlf
All information about ssl certificat are displayed