"리눅스에서 메일을 보내고 싶다" 는 요구는 실제로 두 가지로 갈린다. 필요한 것이 어느 쪽인지 먼저 정해야 한다.
| 목적 | 필요한 것 |
|---|---|
| 서버가 관리자에게 알림 메일을 보낸다 | 외부 SMTP 로 중계하는 릴레이 전용 구성 |
| 도메인의 메일을 받고 사용자가 읽는다 | Postfix + Dovecot + DNS + 인증서까지 갖춘 메일 서버 |
알림 발송만 필요한데 메일 서버를 세우는 것은 과하고 위험하다. 자체 메일 서버에서 외부로 보낸 메일은 대부분의 수신 측에서 스팸으로 처리되거나 거부된다. SPF·DKIM·DMARC·PTR 레코드가 모두 갖춰져야 하고, 클라우드나 사내 회선의 25번 포트가 막혀 있는 경우도 많다. 대부분의 상황에서는 릴레이 구성이 정답이다.
기존 사내 메일 서버나 Gmail, SES 같은 외부 SMTP 로 넘겨 보낸다.
# RHEL 계열
sudo dnf install -y postfix cyrus-sasl-plain mailx
# Ubuntu
sudo apt install -y postfix libsasl2-modules mailutils
/etc/postfix/main.cf 에 다음을 둔다.
relayhost = [smtp.gmail.com]:587
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
inet_interfaces = loopback-only
mydestination = $myhostname, localhost.$mydomain, localhost
대괄호는 그 호스트의 MX 레코드를 찾지 말고 그 이름으로 바로 접속하라 는 뜻이다. 빠뜨리면 릴레이가 엉뚱한 곳으로 간다.
옛 문서에 함께 나오는 smtp_use_tls = yes 는 Postfix 2.x 시절 설정이다. 3.x 에서는 smtp_tls_security_level 이 그 역할을 대신하며 둘을 같이 적으면 security_level 쪽이 이긴다. 새로 쓸 때는 smtp_tls_security_level 만 둔다. encrypt 는 TLS 를 반드시 쓰되 인증서 이름까지는 검증하지 않고, verify · secure 는 검증까지 요구한다.
인증 정보를 등록한다.
sudo tee /etc/postfix/sasl_passwd > /dev/null <<'TXT'
[smtp.gmail.com]:587 sender@example.com:${APP_PASSWORD}
TXT
sudo postmap /etc/postfix/sasl_passwd
sudo chown root:root /etc/postfix/sasl_passwd*
sudo chmod 600 /etc/postfix/sasl_passwd*
Gmail 은 계정 비밀번호를 받지 않는다. Gmail App-password 생성 을 참고해 앱 비밀번호를 만든다.
발신 주소를 고정하려면 주소 재작성을 붙인다. 사내 릴레이가 발신자 도메인을 검사하는 경우에 필요하다.
sender_canonical_maps = static:noreply@example.com
smtp_generic_maps = hash:/etc/postfix/generic
적용하고 시험한다.
sudo postfix check
sudo systemctl enable --now postfix
echo "test body" | mail -s "test subject" admin@example.com
inet_interfaces = loopback-only 로 두면 외부에서 이 서버로 메일을 보낼 수 없다. 알림 발송 전용 서버는 이렇게 닫아 둔다. 이것을 열어 두고 릴레이 제한을 걸지 않으면 스팸 중계기로 악용된다.
도메인 메일을 실제로 받아야 할 때다. Postfix 가 SMTP 를, Dovecot 이 IMAP·POP3 와 인증을 맡는다.
sudo dnf install -y postfix dovecot
# /etc/postfix/main.cf
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
mynetworks = 127.0.0.0/8, 192.168.0.0/24
home_mailbox = Maildir/
smtpd_banner = $myhostname ESMTP
# /etc/dovecot/dovecot.conf
protocols = imap lmtp
# /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:~/Maildir
# /etc/dovecot/conf.d/10-auth.conf
disable_plaintext_auth = yes
auth_mechanisms = plain login
# /etc/dovecot/conf.d/10-ssl.conf
ssl = required
ssl_cert = </etc/pki/dovecot/certs/mail.example.com.crt
ssl_key = </etc/pki/dovecot/private/mail.example.com.key
sudo systemctl enable --now postfix dovecot
sudo firewall-cmd --permanent --add-service={smtp,submission,imaps}
sudo firewall-cmd --reload
mynetworks 는 인증 없이 릴레이를 허용할 대역이다. 여기에 넓은 대역을 넣지 않는다. 외부 사용자는 587(submission) 포트에서 SASL 인증을 거치게 한다. 평문 IMAP(143)과 POP3(110)는 열지 말고 993 만 쓴다.
Maildir 형식을 쓰면 새 계정의 홈에 디렉터리가 자동으로 만들어지도록 /etc/skel/Maildir 을 준비해 둔다.
sudo mkdir -p /etc/skel/Maildir/{cur,new,tmp}
sudo chmod -R 700 /etc/skel/Maildir
# 큐 확인
mailq
postqueue -p
# 큐에 걸린 메일 강제 재시도
sudo postqueue -f
# 특정 메일 내용 확인
sudo postcat -vq <QUEUE_ID>
# 로그
sudo tail -f /var/log/maillog # RHEL 계열
sudo tail -f /var/log/mail.log # Debian 계열
sudo journalctl -u postfix -f
SMTP 대화를 직접 해 보면 어느 단계에서 거부되는지 바로 보인다.
openssl s_client -starttls smtp -connect smtp.example.com:587 -crlf
| 응답 | 뜻 |
|---|---|
454 4.7.1 Relay access denied |
릴레이 권한 없음. mynetworks 나 SASL 인증 확인 |
550 5.7.1 ... Client host rejected |
수신 측이 발신 IP 를 거부. PTR·평판·블랙리스트 |
535 5.7.8 Authentication failed |
SASL 자격 증명. 앱 비밀번호 여부 확인 |
Connection timed out to port 25 |
회선이나 클라우드에서 25번 차단. 587 릴레이로 전환 |
550 5.7.1 계열의 해석은 SMTP 554 5.7.1 Client host rejected 해석 에 정리돼 있다.