Пожалуй начнем
- Определяемся с именем основного сервера и в моем случае будет mail.bds.su
- Настраиваем Master-Slave репликацию PostgreSQL (На почтовом сервере будет реплика, запись будет на основном)
- Регистрируем у провайдера (хостинга) обратную DNS-запись
- Получаем SSL-сертификат в Let’s Encrypt (например по инструкции Proxmox использование сертификата Let’s encrypt вместо стандартного (самоподписанного))
Настройка PostfixAdmin
PostfixAdmin это вообще какой-то привет из темного прошлого, но ничего лучше не завезли. Поэтому будем пользоваться тем, что дали и бесплатно.
Как показывает практика, самым простым решением для управления, пользователями, паролями и алиасами является Postfix и давайте тогда не будет отходить от этой практики. PostfixAdmin это обычное PHP-приложение и для подготовки виртуального хоста и соответствующего окружения мы можем использовать например инструкцию от NextCloud.
- За основу берем стабильный релиз (двух недельной давности в моем случае) из официального гита https://github.com/postfixadmin/postfixadmin
- Создаем базу данных и пользователя (на хосте rw)
- Генерируем Setup password
# php -r 'echo password_hash("xxxSecretPasswordxxx", PASSWORD_DEFAULT);'
Пример virtual-host для запуска:
server {
listen 80;
server_name postfixadmin.interlan.xyz;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name postfixadmin.interlan.xyz;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
add_header Referrer-Policy no-referrer always;
# Path to the root of your installation
root /var/www/vhosts/postfixadmin.interlan.xyz/public/;
access_log /var/log/nginx/postfixadmin.interlan.xyz-access.log;
error_log /var/log/nginx/postfixadmin.interlan.xyz-error.log warn;
ssl_certificate /etc/letsencrypt/live/postfixadmin.interlan.xyz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/postfixadmin.interlan.xyz/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
client_max_body_size 1024M;
fastcgi_buffers 64 4K;
proxy_connect_timeout 6000;
proxy_send_timeout 6000;
proxy_read_timeout 6000;
send_timeout 6000;
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
if ($host ~ ^www\.(?<domain>.+)$) {
return 301 $scheme://$domain$request_uri;
}
location = /robots.txt {
allow all;
}
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php {
fastcgi_index index.php;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
fastcgi_param front_controller_active true;
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
}
# Prevents caching of css/less/js/images, only use this in development
location ~* \.(css|less|js|jpg|png|gif)$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
expires 0;
}
}
Конфигурационный файл /var/www/vhosts/postfixadmin.interlan.xyz/config.local.php
<?php
$CONF['database_type'] = 'pgsql';
$CONF['database_host'] = '127.0.0.1';
$CONF['database_user'] = 'postfixadmin_user';
$CONF['database_password'] = 'xxxDBUSERPASSWORDxxx';
$CONF['database_name'] = 'postfixadmin_db';
$CONF['encrypt'] = 'md5crypt';
$CONF['configured'] = true;
$CONF['setup_password'] = 'xxxINSTALLxxxHASHxxx';
$CONF['default_aliases'] = array (
'abuse' => 'chernousov@interlan.xyz',
'hostmaster' => 'chernousov@interlan.xyz',
'postmaster' => 'chernousov@interlan.xyz',
'webmaster' => 'chernousov@interlan.xyz'
);
?>
Переходим по URL https://postfixadmin.interlan.xyz/setup.php (это пример конечно) и выполняем настройку (наполнение схемы базы производится автоматически) и создаем аккаунт администратора.

- Поле Админ, это адрес электронной почты
- Дополнительно устанавливаем расширение IMAP для php
# aptitude install php8.1-imap - Симлинк на templates_c
# ln -s ./templates/ ./templates_c
Теперь у нас есть хранилище для учеток пользователей, доменов, алиасов и т.п. и можем переходить к следующему этапу и сделаем хранилище для почты и возможность подключаться почтовым клиентом.
Установка и настройка Dovecot
Изначально хотел использовать DBmail, но из за больших задержек между датацентрами это будет полная боль и страдание. Посему хранилка будет файловая на GlusterFS для возможности масштабирования.
Пакеты для Dovecot и плагинов устанавливаем на пакете с репликой базы данных.
# aptitude install dovecot-antispam dovecot-lmtpd dovecot-managesieved dovecot-pgsql dovecot-sieve
Настройка Dovecot.
За основу берем базовые конфигурационные файл и вносим ряд правок.
conf.d/10-auth.conf b/conf.d/10-auth.conf
Отключаем include auth-system.conf.ext (это механизмы системной авторизации) и добавляем:
!include auth-sql.conf.ext
/conf.d/10-mail.conf
Каталог где храним данные мэйлбоксов и UID/GID собственно Dovecot
mail_location = maildir:/opt/mail/%d/%u/
mail_uid = 118
mail_gid = 122
first_valid_uid = 118
last_valid_uid = 118
first_valid_gid = 122
last_valid_gid = 122
/conf.d/10-master.conf
Настраиваем сервисы LMTP и авторизации на работу как через сокет, так и по порту
inet_listener lmtp {
address = 127.0.0.1
port = 24
}
unix_listener /var/spool/postfix/private/auth {
mode = 0666
mode = 0666
user = postfix
group = postfix
}
/conf.d/10-ssl.conf
Настройка Dovecot на использование Let’s encrypt
ssl_cert = </etc/letsencrypt/live/mail.bds.su/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.bds.su/privkey.pem
conf.d/15-lda.conf
Настройка lmtp сервиса
postmaster_address = chernousov@interlan.xyz
hostname = mail.bds.su
lda_mailbox_autocreate = yes
protocol lda {
mail_plugins = $mail_plugins
}
conf.d/15-mailboxes.conf
Автосоздание каталогов в IMAP-папках
mailbox Drafts {
special_use = \Drafts
auto = subscribe
}
mailbox Junk {
special_use = \Junk
auto = subscribe
}
mailbox Trash {
special_use = \Trash
auto = subscribe
}
conf.d/20-lmtp.conf
LMTP-сервис переводим на работу по порту вместо сокета
protocol lmtp {
mail_plugins = $mail_plugins
}
service lmtp {
inet_listener lmtp {
address = 127.0.0.1
port = 24
}
dovecot-sql.conf.ext
Настройки авторизации на использование базы данных
driver = pgsql
connect = host=127.0.0.1 dbname=postfixadmin_db user=postfixadmin_user password=xxxPASSWORDxxx
default_pass_scheme = MD5-CRYPT
password_query = SELECT password FROM mailbox WHERE username = '%u'
user_query = select CONCAT('/opt/mail','/',LOWER(maildir)) as home, 118 AS uid, 122 AS gid FROM mailbox WHERE username = '%u';
dovecot.conf
listen = *
Разрешаем доступ к портам через UFW.
# ufw allow 143
# ufw allow 993
Создаем каталог для хранения почты и меняем владельца и группу.
# mkdir /mnt/gluster-storage/mail/
# chown dovecot:dovecot /mnt/gluster-storage/mail
Перезапускаем и активируем.
# systemctl restart dovecot
# systemctl enable dovecot
Отладка авторизации.
Проверяем, что пользователь существует:
# doveadm user chernousov@interlan.xyz
Проверяем авторизацию:
# doveadm auth login chernousov@interlan.xyz xxxPASSWORDxxx
passdb: chernousov@interlan.xyz auth succeeded
extra fields:
user=chernousov@interlan.xyz
userdb extra fields:
chernousov@interlan.xyz
home=/mnt/gluster-storage/mail/interlan.xyz/chernousov/
uid=118
gid=122
auth_mech=PLAIN
Если все ок, то пробуем подключиться почтовым клиентом.
Настройка Postfix для приема и отправки электронной почты
Postfix это такая-же классика как и Dovecot, поэтому будем использовать его. Как мне видится можно даже сделать небольшую заметку как вообще вся эта связка работает, но в каком-то минимальном формате исключительно для лэндинга.
Устанавливаем по старой схеме необходимые пакеты.
# apt-get install postfix postfix-pgsql postfix-gld postfix-policyd-spf-python
# aptitude install opendkim opendkim-tools
Подготовка базы почтовых алиасов:
# newaliases
# postfix reload
Устанавливаем и настраиваем два дополнительных сервиса:
DKIM настроим по инструкции для mail-relay
Postgregrey подойдет в дефолтной поставке:
# apt-get install postgrey
# systemctl enable postgrey
# systemctl start postgrey
Дополнительные DNS-записи для доменов:
- _dmarc TXT “v=DMARC1; p=reject; rua=mailto:chernousov@interlan.xyz”
- @ TXT “v=spf1 +a +mx -all”
Настройка Firewall:
# ufw allow 25
# ufw allow 465
# ufw allow 587
Пример конфигурации:
myorigin = mail.bds.su
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
delay_warning_time = 15m
readme_directory = no
compatibility_level = 3.6
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.bds.su/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.bds.su/privkey.pem
smtpd_tls_security_level=may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
smtpd_recipient_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_unauth_destination
check_policy_service inet:localhost:10023
myhostname = mail.bds.su
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = localhost
relayhost =
mynetworks = 127.0.0.0/8
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4
virtual_alias_maps = proxy:pgsql:/etc/postfix/virtual_alias_maps.cf
virtual_mailbox_domains = proxy:pgsql:/etc/postfix/virtual_domains_maps.cf
virtual_mailbox_maps = proxy:pgsql:/etc/postfix/virtual_mailbox_maps.cf
virtual_transport = lmtp:127.0.0.1:24
smtpd_sasl_auth_enable = yes
smtpd_sasl_exceptions_networks = $mynetworks
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtp_use_tls = yes
smtpd_use_tls = yes
smtpd_tls_auth_only = yes
smtpd_helo_required = yes
milter_default_action = accept
milter_protocol = 2
smtpd_milters = inet:localhost:10021
non_smtpd_milters = inet:localhost:10021
SQL-конфиги:
/etc/postfix/virtual_alias_maps.cf
user= postfixadmin_user
password= xxxPASSWORDxxx
hosts= localhost
dbname= postfixadmin_db
query= SELECT goto FROM alias WHERE address='%s' AND active='1'
/etc/postfix/virtual_domains_maps.cf
user= postfixadmin_user
password= xxxPASSWORDxxx
hosts= localhost
dbname= postfixadmin_db
query= SELECT domain FROM domain WHERE domain='%u'
/etc/postfix/virtual_mailbox_maps.cf
user= postfixadmin_user
password= xxxPASSWORDxxx
hosts= localhost
dbname= postfixadmin_db
query= SELECT goto FROM alias WHERE address='%s' AND active='1'
Отладка
Просмотр текущей очереди:
# mailq
Форсировать очередь:
# mailq -q
Подтверждение что мы не спамеры
Публичные сервисы типа gmail стали очень злые и нервные и теперь для использования своего self-hosted почтового сервера придется немного поприседать и побить в бубен.
Итак, давайте разбираться, что мы можем сделать.
У гугла как обычно все через одно место и в 90% случаев словим ошибку:
gmail-smtp-in.l.google.com[173.194.215.26] said: 550-5.7.1 [45.138.27.6]
The IP youre using to send mail is not authorized to 550-5.7.1 send email
directly to our servers. Please use the SMTP relay at your 550-5.7.1
service provider instead. Learn more at 5505.7.1
И вот тут все не просто.
- Идем добавлять домены https://postmaster.google.com/managedomains?pli=1
- Добавляем DNS-запись подтверждения домена (TXT)
- Они не быстрые и синхронизация даже после подтверждения займет много времени
Yandex
Здесь обычный GreyList и первое письмо будет уходить долго, а дальнейшие уходят на ура.
Mail.ru
Проверяет DKIM и SPF и все норм
Немного красоты
Веб-интерфейс для доступа к почте будем делать на базе RoundCube. Это обычное PHP-приложение, достаточно функциональное и удобное.
Скачиваем версию Complete на официальной странице загрузок проекта https://roundcube.net/download/
Виртуальный хост:
server {
listen 80;
server_name webmail.bds.su;
# Enforce HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name webmail.bds.su;
access_log /var/log/nginx/webmail.bds.su-access.log;
error_log /var/log/nginx/webmail.bds.su-error.log warn;
ssl_certificate /etc/letsencrypt/live/webmail.bds.su/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/webmail.bds.su/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
client_max_body_size 64M;
fastcgi_buffers 64 4K;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Download-Options "noopen" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "none" always;
add_header X-XSS-Protection "1; mode=block" always;
fastcgi_hide_header X-Powered-By;
root /var/www/vhosts/webmail.bds.su/;
location / {
index index.php;
try_files $uri $uri/ /index.php;
}
location ~* \.php$ {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {return 404;}
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
- Инициализируем схему
- Конфигурацию
- И плагинов по вкусу.