Install Postfix + Dovecot e-mail server

Install Postfix + Dovecot e-mail server KCTang

Go to End

Note

  • 12/7/2026: Dovecot 2.4 and its conf files freshly installed because of many changes to the command names used. 99-mail-stack-delivery.conf updated to suit Dovecot 2.4 and is to override all other default conf files. Minor updating to Postfix conf files.
  • 25/6/2026: Increase imap-login process limit.
  • 29/1/2026: Change "nano" to "micro".
  • 14/4/2025: Re-arrange sections with some updates.
  • 12/4/2025: Update ClamAV and amavis.
  • 8/2/2025: Change maximal_queue_lifetime and bounce_queue_lifetime from 0 to 60s to give more time for the receiving server to process
  • 7/1/2025: Revise ip address. Delete "mail.kctang.com.hk".
  • 7/10/2024: Add also "mail.kctang.com.hk" as email domain name.
  • 19/9/2024: Delete separate configuration instructions for spamassassin. Change micro to nano as non-graphical editor.
  • 13/2/2023: Use Letsencrypt ssl. Change gedit to micro as non-graphical editor.
  • 11/2/2023: Add full config files. Correct opendkim socket.
  • 1/2/2023: Correct typos.
  • 10/10/2022: Add DKIM setting.
  • 18/6/2021: Adjust TLS setting.
  • 5/1/2021: Increase imap-login process limit. List Dovecot full custom settings.
  • 9/12/2020: Define cron job to delete filtered mails.
  • 20/9/2020: Stop using mail-stack-delivery.
  • 8/5/2020: Correct typo errors.
  • 30/5/2019: Add anti-virus and spam mail filtering.
  • 17/5/2019: Add copying emails to external accounts.
  • 7/5/2019: Change "gksudo gedit" to "sudo gedit" as Ubuntu 18.04 dropped "gksudo". Add header_checks. Add auto creation of Trash folders. Add webmaster.
  • 29/9/2018: Add "body_checks" for spam control.
  • 20/9/2018: Increase message_size_limit to 20 times the default.
  • 5/4/2018: Increase message_size_limit to 10 times the default.
  • 25/12/2018: Publish on web.
  • 12/4/2014: Specify maximal_queue_lifetime to notify unsuccessful delivery immediately.
  • 2/4/2014: Specify fully qualified domain name.

Intro

Postfix is a mail transfer agent (MTA) responsible for sending out and receiving emails between servers.

Dovecot is a mail delivery agent (MDA) responsible for sending out and receiving emails between a server and its users.

Mail-stack-delivery was a combined package containing both Postfix and Dovecot. It is now no longer supported.

Re-direct to server ports​

Set the internet router to re-direct the following connections to server ports:

  • SMTP = port 25 (for receiving or sending emails)
  • secure SMTP = port 465 (for receiving or sending emails securely)
  • IMAP = port 143 (for retrieving emails)
  • secure IMAP = port 993 (for retrieving emails securely)
  • POP3 = port 110 (for retrieving emails)

Install both Postfix and Dovecot​

Ubuntu 20.04 does not support the combined package "mail-stack-delivery" anymore.

Install Dovecot and Postfix individually:

$ sudo apt install dovecot-imapd dovecot-pop3d
$ sudo apt install postfix

However, the previous config file "/etc/dovecot/conf.d/99-mail-stack-delivery.conf" is still retained for use, because "99" represents the last and overriding config file. This eliminates the need to change the individual config files.

(revised to install individually, 20/9/2020)

Reconfigure Postfix

Reconfig:

$ sudo dpkg-reconfigure postfix

Use Tab key to change selection.

Select "Internet Site".

Enter the following information:

System mail name: <fully qualified domain name, such as "kctang.com.hk">
Root and postmaster mail recipient: <such as "kctang">
Other destinations to accept mail: <fully qualified domain name, such as "mail.kctang.com.hk" and "kctang.com.hk">, <server name such as "server">, localhost.localdomain, localhost
Force synchronous updates on mail queue: No
Local networks: <leave it blank to accept the default>
Use procmail for local delivery: No
Mailbox size limit (bytes): 0
Local address extension character: +
Internet protocols to use: all

("mail.kctang.com.hk" also added, 7/10/2024)

Edit "main.cf" settings:

$ sudo micro /etc/postfix/main.cf

Specify in full:

# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
#append_dot_mydomain = no
# last line commented as obsolete, KCTang 12/7/2026

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

#readme_directory = no
# last line commented as obsolete, KCTang 12/7/2026

# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2

# the following automatically set by dpkg-reconfigure postfix
myhostname = kctang.com.hk
# fully qualified domain name instead of server name used,
# otherwise some servers would not accept e-mails sent without fully qualified domain name, 2/4/2014

alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = mail.kctang.com.hk,kctang.com.hk,server3,localhost.localdomain,localhost
relayhost = 
mynetworks = 127.0.0.1/32 192.168.0.19/32 10.8.0.1/32 [::1]/128 [fe80::d732:96d2:c380:fd6d]/128 [fe80::2d5f:fcae:5284:48cf]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all

# ALL the following added to default
inet_protocols = all
# home_mailbox = Maildir/ # commented, KCTang 11/7/2026
mailbox_command = /usr/lib/dovecot/deliver -c /etc/dovecot/dovecot.conf -m "${EXTENSION}"

message_size_limit = 204800000
# last line added to increase the default to 10 times, KCTang 5/4/2014
# increased to 20 times, KCTang 20/9/2018

maximal_queue_lifetime = 10s
# last line added to report unsuccessful delivery immediately instead of after the default of 5 days, KCTang 12/4/2014
# changed from 0 to 10s, KCTang 8/2/2025

bounce_queue_lifetime = 10s
# last line added, this should not be bigger than maximal_queue_lifetime, KCTang 20/5/2019
# changed from 0 to 10s, KCTang 8/2/2025

# smtpd setting
#smtpd_proxy_timeout = 240s
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/dovecot-auth
smtpd_sasl_local_domain = $myhostname

smtpd_sasl_security_options = noanonymous,noplaintext
# noplaintext in last line added to prevent unencrypted credentials, KCTang 13/2/2023

smtpd_sasl_tls_security_options = noanonymous
# last line added, KCTang 13/2/2023

#broken_sasl_auth_clients = yes
# last line commented as obsolete, KCTang 12/7/2026

smtpd_sasl_auth_enable = yes
smtpd_sasl_authenticated_header = yes
smtpd_recipient_restrictions = 
   reject_unknown_sender_domain
   reject_unknown_recipient_domain 
   reject_unauth_pipelining
   permit_mynetworks
   permit_sasl_authenticated 
   reject_unauth_destination
   check_policy_service unix:private/policyd-spf
# last line added to enable spf, KCTang 19/2/2022
smtpd_relay_restrictions = 
   permit_mynetworks
   permit_sasl_authenticated
   defer_unauth_destination
smtpd_sender_restrictions = reject_unknown_sender_domain

# TLS parameters
#smtp_use_tls = yes
# last line commented as obsolete, KCTang 12/7/2026
smtp_tls_security_level=may
smtpd_tls_security_level=may
smtp_tls_note_starttls_offer=yes
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes

# use the following two as provided as default
#smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
#smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key

# use the following two if self define key and certificate
#smtpd_tls_key_file=/etc/ssl/private/server.key
#smtpd_tls_cert_file=/etc/ssl/certs/server.crt

# use the following two if using letsencrypt, KCTang 13/2/2023
# using those set up when setting up Apache2 web server, actually they are soft-linked to the following files, KCTang 19/9/2024
smtpd_tls_cert_file=/etc/letsencrypt/live/kctang.com.hk/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/kctang.com.hk/privkey.pem

# use the following if using self certification authority
#smtp_tls_CApath=/etc/ssl/certs/

smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_auth_only = yes
smtpd_tls_mandatory_ciphers = medium
# smtpd_tls_mandatory_protocols = SSLv3, TLSv1 # commented, replaced below, KCTang 18/6/2021
# next 2 lines added to enhance security, KCTang 1/7/2021
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

#tls_random_source = dev:/dev/urandom
# last line commented as obsolete, KCTang 12/7/2026

#header_checks = regexp:/etc/postfix/header_checks
# last line added to check headers, KCTang 30/9/2018, disabled after using amavis 1/6/2019

#body_checks = regexp:/etc/postfix/body_checks
# last line added to refer to another file to check contents of email bodies, KCTang 29/9/2018, disabled after using amavis 1/6/2019

# virtual_alias_domains = kctang.com.hk
# last line added on 17/5/2019, but disabled since same domain name already defined as destination above, KCTang 20/5/2019

virtual_alias_maps = hash:/etc/postfix/virtual
# last line added to forward emails to another server, KCTang 17/5/2019

content_filter = smtp-amavis:[127.0.0.1]:10024
# last line added for amavis, KCTang 1/6/2019

# the following line added to enable spf, KCTang 19/2/2022
policyd-spf_time_limit = 3600

# Milter configuration, added but disabled, KCTang 2/6/2019, enabled again, KCTang 19/2/2022
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters

(full file given, 11/2/2023)

(updated, 13/2/2023)

(note regarding letsencrypt added, 19/9/2024)

(updated, 12/7/2026)

Edit "master.cf" settings:

$ sudo micro /etc/postfix/master.cf

Specify​ in full:

# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp        inet  n       -       y       -       -       smtpd
#smtp       inet  n       -       y       -       1       postscreen
#smtpd      pass  -       -       y       -       -       smtpd
#dnsblog    unix  -       -       y       -       0       dnsblog
#tlsproxy   unix  -       -       y       -       0       tlsproxy
#submission inet  n       -       y       -       -       smtpd
#  -o syslog_name=postfix/submission
#  -o smtpd_tls_security_level=encrypt
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_tls_auth_only=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
smtps       inet  n       -       y       -       -       smtpd
# last line uncommented, KCTang 2/4/2014
#  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
# last line added to force use of TLS, KCTang 2/4/2014
  -o smtpd_sasl_auth_enable=yes
# last line uncommented to enable STARTTLS authentication, KCTang 2/4/2014
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
# last line uncommented to reject if not authenticated, KCTang 2/4/2014
  -o milter_macro_daemon_name=ORIGINATING
# last line uncommented, KCTang 2/4/2014
#628      inet  n       -       y       -       -       qmqpd
pickup    unix  n       -       y       60      1       pickup
  -o content_filter=
  -o receive_override_options=no_header_body_checks
# last two lines added for anti-spam, KCTang 29/5/2019
cleanup   unix  n       -       y       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
#qmgr     unix  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       y       1000?   1       tlsmgr
rewrite   unix  -       -       y       -       -       trivial-rewrite
bounce    unix  -       -       y       -       0       bounce
defer     unix  -       -       y       -       0       bounce
trace     unix  -       -       y       -       0       bounce
verify    unix  -       -       y       -       1       verify
flush     unix  n       -       y       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       y       -       -       smtp
relay     unix  -       -       y       -       -       smtp
  -o syslog_name=postfix/$service_name
#  -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
  -o smtp_connect_timeout=60s
# last line added, KCTang 10/3/2021
showq     unix  n       -       y       -       -       showq
error     unix  -       -       y       -       -       error
retry     unix  -       -       y       -       -       error
discard   unix  -       -       y       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       y       -       -       lmtp
anvil     unix  -       -       y       -       1       anvil
scache    unix  -       -       y       -       1       scache
postlog   unix-dgram n  -       n       -       1       postlogd
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
#   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
#  mailbox_transport = lmtp:inet:localhost
#  virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus     unix  -       n       n       -       -       pipe
#  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix  -       n       n       -       -       pipe
#  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix    -    n    n    -    2    pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman   unix  -       n       n       -       -       pipe
  flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
  ${nexthop} ${user}

# the following added, KCTang 29/5/2019
smtp-amavis     unix    -       -       -       -       2       smtp
  -o smtp_data_done_timeout=1200
  -o smtp_send_xforward_command=yes
  #-o disable_dns_lookups=yes
  # last line obsolete, replaced with next line, KCTang 12/7/2026
  -o smtp_dns_support_level=disabled
  -o max_use=20
127.0.0.1:10025 inet    n       -       -       -       -       smtpd
  -o content_filter=
  -o local_recipient_maps=
  -o relay_recipient_maps=
  -o smtpd_restriction_classes=
  -o smtpd_delay_reject=no
  -o smtpd_client_restrictions=permit_mynetworks,reject
  -o smtpd_helo_restrictions=
  -o smtpd_sender_restrictions=
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o smtpd_data_restrictions=reject_unauth_pipelining
  -o smtpd_end_of_data_restrictions=
  -o mynetworks=127.0.0.0/8
  -o smtpd_error_sleep_time=0
  -o smtpd_soft_error_limit=1001
  -o smtpd_hard_error_limit=1000
  -o smtpd_client_connection_count_limit=0
  -o smtpd_client_connection_rate_limit=0
  -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
    
# the following added for spf, KCTang 1/6/2019
policyd-spf   unix  -       n       n       -       0       spawn
  user=policyd-spf argv=/usr/bin/policyd-spf

(full file given, 11/2/2023)

(updated, 12/7/2026)

Create "body_checks" file, if required:

$ sudo micro /etc/postfix/body_checks

Specify​ one or more lines of texts within //:

/unique text contained in email you do not want to receive/ DISCARD

"DISCARD" means delete from the server.

Create "header_checks" file, if required:

$ sudo micro /etc/postfix/header_checks

Specify similarly.

(header checks added, 7/5/2019)

Change Dovecot settings

Edit config file:

$ sudo micro /etc/dovecot/conf.d/99-mail-stack-delivery.conf

Note that a number of default config files are contained in sub-directory "conf.d". To override them, create a config file beginning with "99" so that it is read the latest to override the others. The name is based on the previous "mail-stack-delivery" config file, but can be any.

Specify in full:

# This conf file shows custom settings to override those in the conf files provided by Dovecot
# Re-arranged to follow .conf file sequence, KCTang 12/7/2026

# use "telnet <domain name> <143, 110, 993 or 995>" to test connections.
# press "Ctrl-]" to escape, and enter "quit" to exit.
# use "doveconf -n | grep <text>" to debug individual settings
# use "doveconf -n" see the full settings
# use "openssl s_client -connect <domain name>:993 -quiet" and
# use "openssl s_client -connect <domain name>:995 -quiet" to verify TLS handshakes.
# press Ctrl-C to exit.

# protocols = imap pop3 sieve
protocols = imap pop3

# IMAP configuration
protocol imap {
        mail_max_userip_connections = 500
        # 10 in last line increased to 1000, KCTang 25/5/2019
        # 1000 reduced to 500, KCTang 26/6/2019
        imap_client_workarounds = delay-newmail
}

# POP3 configuration
protocol pop3 {
        mail_max_userip_connections = 50
        # 10 in last line increased to 50, KCTang 25/5/2019 
        pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
}

# Authentication configuration
auth_mechanisms = plain login
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@

# 10-auth.conf
auth_allow_cleartext = no # force TLS, no plaintext auth

# 10-mail.conf
#mail_location = maildir:~/Maildir:LAYOUT=fs
# LAYOUT=fs added to last line, to use "/" instead of "." to denote sub-folders, KCTang 25/5/2019
# The above 2 lines replaced by the following for Dovecot 2.4, KCTang 12/7/2026
mail_driver = maildir
mail_path   = /home/%{user}/Maildir
# mail_inbox_path = /home/%{user}/Maildir/INBOX
# last line forces to have "INBOX"; next line does not, and is used to maintain compatibility with the existing email folders
mail_inbox_path = /home/%{user}/Maildir
mailbox_list_layout = fs
namespace inbox {
  inbox = yes
  separator = /
  prefix =
  subscriptions = yes
  list = yes
  # The following are to auto create Trash folders.
  # Trash folders would not be backed up with back-in-time,
  # and therefore would need to be re-created after email recovery is done from back-in-time.
  mailbox Trash {
    auto = subscribe
  }
}

# 10-master.conf
service imap-login {
  # inet_listener imap inserted, KCTang 12/7/2026
#  inet_listener imap {
#    port = 143
#  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }
  # process_limit increased to 200, KCTang 4/1/2021
  # process_limit increased to 500, KCTang 25/6/2026
  process_limit = 500
}
service pop3-login {
#  inet_listener pop3 {
#    port = 110
#  }
  inet_listener pop3s {
    port = 995
    ssl = yes
  }
  process_limit = 100
}
service auth {
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/dovecot-auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}
# The following added to resolve stats-writer failure, KCTang 3/6/2021
# Changed from 0660 to 0666 to give permissions, KCTang 22/7/2024
service stats {
  unix_listener stats-reader {
    user = root
    group = root
    mode = 0666
  }
  unix_listener stats-writer {
    user = root
    group = dovecot
    mode = 0666
  }
}
#service managesieve-login {
#  inet_listener sieve {
#    port = 4190
#  }
#}
#service managesieve {
#}

# 10-ssl.conf
ssl = yes
# original codes as 10-ssl.conf for Dovecot 2.4
# ssl_server_cert_file = /etc/dovecot/private/dovecot.pem
# ssl_server_key_file = /etc/dovecot/private/dovecot.key
# use letsencrypt ssl, KCTang 13/2/2023 
ssl_server_cert_file = /etc/letsencrypt/live/kctang.com.hk/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/kctang.com.hk/privkey.pem

#ssl_client_ca_dir =

ssl_min_protocol = TLSv1.2
ssl_cipher_list = HIGH:!aNULL:!MD5

# 15-lda.conf
protocol lda {
  postmaster_address = admin@%d
#  mail_plugins = sieve
  quota_full_tempfail = yes
  deliver_log_format = msgid=%m: %$
  rejection_reason = Your message to <%t> was automatically rejected:%n%r
}

# 90-sieve conf
#sieve = file:/var/lib/dovecot/sieve/default.sieve
#sieve_dir = /var/lib/dovecot/sieve/

(full file given, 11/2/2023)

(updated, 13/2/2023)
(updated, 25/6/2026)
(updated for Dovecot 2.4, 12/7/2026)

Activate the changes:

$ sudo systemctl reload postfix
or
$ sudo service postfix reload
and
$ sudo systemctl restart dovecot
or
$ sudo service dovecot reload

Verify success

See whether the Postfix server is running:

$ telnet localhost 25

should display:

220 kctang.com.hk ESMTP Postfix (Ubuntu)

ehlo localhost

should display the following:

250-kctang.com.hk

250-PIPELINING

250-SIZE 204800000

250-VRFY

250-ETRN

250-STARTTLS

250-ENHANCEDSTATUSCODES

250-8BITMIME

250-DSN

250-SMTPUTF8

250 CHUNKING

Ctrl-]

to exit to "telnet >" prompt.

quit

to exit telnet.

Try also:

$ telnet localhost 993

("993", not "995", 1/2/2023)

similarly:

$ telnet localhost 465

should display either one:

Connected to localhost

Connected to kctang.com.hk

"Ctrl-]"

to exit to "telnet >" prompt.

quit

to exit telnet.

Specify internal email forwarding

Edit "aliases" file:

$ sudo micro /etc/aliases

Specify​:

postmaster: kctang
webmaster: kctang
kctcl: kctcl, kctclpop

meaning:

  • forwarding e-mails sent to postmaster@localhost and webmaster@localhost to kctang@localhost, no email will be left at postmaster or webmaster

(webmaster added, 7/5/2019)

  • forwarding e-mails sent to kctcl@localhost to kctcl@localhost (itself) and to kctclpop@localhst, i.e. making a copy

"localhost" means "kctang.com.hk" in our case. For the email user name before "@", there is no need to create a file system user account for it if all emails addressed to it are forwarded elsewhere. The name serves as an alias only of the email account forwarded to.

(added, 17/5/2019)

Activate setting everytime the "aliases" file has been changed:

$ sudo newaliases

Copy to external email accounts

(section added, 17/5/2019)

Execute:

$ sudo micro /etc/postfix/main.cf

Specify​ at the end of the file (already shown above):

virtual_alias_domains = kctang.com.hk
virtual_alias_maps = hash:/etc/postfix/virtual

Execute:

$ sudo micro /etc/postfix/virtual

Specify​ to make a copy to itself and a copy to the external email account:

# from               to one or more addresses, separated by a space 
kctang@kctang.com.hk kctang@kctang.com.hk name1@external.account.name
kctcl@kctang.com.hk  kctcl@kctang.com.hk name2@external.account.name

Omit making a copy to itself if only email forwarding is required.

Save and exit.

Execute after the "virtual" file is created or changed:

$ sudo postmap /etc/postfix/virtual

Execute:

$ sudo systemctl restart postfix

Test by sending emails.

Install anti-virus ClamAV

(section added, 30/5/2019)
(heading changed, not just daemon, 13/4/2025)

Install ClamAV:

$ sudo apt update
$ sudo apt install clamav clamav-daemon   (clamav-freshclam and clamavscan also automatically installed)
$ clamscan --version                      (check if clamav is running)
$ sudo dpkg-reconfigure clamav-daemon     (must be done, generally accept all defaults, set yes to scan emails)
$ sudo systemctl restart clamav-freshclam (should have been installed, restart to refresh)
$ sudo systemctl restart clamav-daemon    (ditto)
$ sudo systemctl status clamav-freshclam  (check if running)
$ sudo systemctl status clamav-daemon     (check if running, OK if reported "/bin/mkdir /run/clamav (code=exited, status=1/FAILURE" because directory already created)
$ sudo tail -f /var/log/clamav/clamav.log (see running progress, Ctrl-Z to exit)

(typo corrected, 8/5/2020)

(use sudo, 11/2/2023)

(install clamav also, 13/4/2025)

Filter spam mails

(section added, 30/5/2019)

Execute to install various software:

$ sudo apt update
$ sudo apt install amavisd-new spamassassin
$ sudo apt install pyzor razor            (optional extras)
$ sudo apt install arj cabextract cpio lhasa nomarch pax rar unrar unzip zip  ("lhasa", not "lha")
$ sudo razor-admin -create                (added 19/9/2024)
$ sudo razor-admin -register              (added 19/9/2024)

Configure to integrate with ClamAV:

$ sudo adduser clamav amavis
$ sudo adduser amavis clamav

Configure SpamAssassin:

$ sudo micro /etc/default/spamassassin

Change "ENABLED=0" to:

(no need to configure because amavisd-new handles it, 19/9/2024)

ENABLED=1

(no longer set here, 8/5/2020)

Start the service:

$ sudo systemctl start spamassassin.service

(no need to configure because amavisd-new handles it, 19/9/2024)

Configure Amavisd-new:

dded, 14/4/2025)

Specify to enable both antivirus and spam checking modes:

$ sudo micro /etc/amavis/conf.d/15-content_filter_mode

Specify to enable both antivirus and spam checking modes:

use strict;
# Uncomment the two lines below to enable antivirus checking mode
@bypass_virus_checks_maps = (\%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);
# Uncomment the two lines below to enable SPAM checking mode
@bypass_spam_checks_maps = (\%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
1;  # insure a defined return

Execute:

$ sudo micro /etc/amavis/conf.d/20-debian_defaults

Adjust the following values only if desired to flag more messages as spam:

$sa_tag_level_deflt = 2.0; # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 6.31; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 6.31; # triggers spam evasive actions
$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent

Execute:

$ sudo micro /etc/amavis/conf.d/50-user

Specify all other user settings under this file:

$myhostname = 'kctang.com.hk';
$mydomain = 'kctang.com.hk';
$unrar = ['rar', 'unrar'];
$final_spam_destiny = D_DISCARD;    (deleted, 8/5/2020, reinstated, 26/7/2021)
$enable_dkim_verification = 0;      (added, 14/4/2025, amavis would not check dkim but leave it to OpenDKIM)

(combine all settings here, 14/4/2025)

Edit the following file to specify domains to be whitelisted if necessary:

$ sudo micro /etc/amavis/conf.d/40-policy_banks

Quote the names as the examples given there.

Re-start the service (note not "amavis-new"):

$ sudo systemctl restart amavis
$ sudo systemctl status amavis

 If, when executing "sudo apt remove spamassassin" for any reason, it is reported that sa-compile cannot also be removed, execute:

$ sudo apt install spamd
$ sudo apt remove sa-compile
$ sudo apt remove spamd

(added, 14/4/2025)

 

Configure Postfix for amavis content filter

(split from the last section, 13/4/2025)

Edit "master.cf" settings:

$ sudo micro /etc/postfix/master.cf

Add the following immediately below the "pickup" transport service (already done above):

     -o content_filter=
     -o receive_override_options=no_header_body_checks

Add the following to the end of the file (already done above):

smtp-amavis     unix    -       -       -       -       2       smtp
    -o smtp_data_done_timeout=1200
    -o smtp_send_xforward_command=yes
    -o disable_dns_lookups=yes
    -o max_use=20
127.0.0.1:10025 inet    n       -       -       -       -       smtpd
    -o content_filter=
    -o local_recipient_maps=
    -o relay_recipient_maps=
    -o smtpd_restriction_classes=
    -o smtpd_delay_reject=no
    -o smtpd_client_restrictions=permit_mynetworks,reject
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o smtpd_data_restrictions=reject_unauth_pipelining
    -o smtpd_end_of_data_restrictions=
    -o mynetworks=127.0.0.0/8
    -o smtpd_error_sleep_time=0
    -o smtpd_soft_error_limit=1001
    -o smtpd_hard_error_limit=1000
    -o smtpd_client_connection_count_limit=0
    -o smtpd_client_connection_rate_limit=0
    -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters

Edit "main.cf" settings:

$ sudo micro /etc/postfix/main.cf

Add the following to the end of the file (already done above):

content_filter = smtp-amavis:[127.0.0.1]:10024

Comment it with "#" at the start of the line if desired to stop using amavis.

Restart service:

$ sudo systemctl restart postfix

Test that the Amavisd-new SMTP is listening:

telnet localhost 10024
Trying 127.0.0.1...
Connected to kctang.com.hk.
Escape character is '^]'.
220 [127.0.0.1] ESMTP amavisd-new service ready

Press Ctrl-] and enter "quit" to exit.

Check the hidden header of email received after the above to see the presence of one or more of the following:

X-Spam-Level: 
X-Virus-Scanned: Debian amavisd-new at kctang.com.hk
X-Spam-Status:
X-Spam-Level: 

If present, the spam filter is working.

See https://help.ubuntu.com/lts/serverguide/mail-filtering.html.en for a full explanation of the above.

(outdated, 19/9/2024)

Delete filtered mails

(section added, 9/12/2020)

Filtered files are stored in /var/lib/amavis/virusmails and should be deleted regularly.

Edit to define a scheduled job to delete filtered files:

$ sudo crontab -e -u amavis

where:

-e = edit

-u amavis = user amavis

Define as follows:

0 0 * * * find /var/lib/amavis/virusmails/ -type f -mtime +15 -delete

which means at

0 = 0 minute

0 = 0 hour

* = every day of month

* = every month

* = every day of week

find /var/lib/amavis/virusmails = find in that directory

-type f = regular files

-mtime +15 = file modified more than 15 days ago

-delete = delete the file

Set up DKIM

(implemented, 19/2/2022

section added, 10/10/2022)

Set up DomainKeys Identified Mail (DKIM) to prevent email phishing by others by putting a digital key in the outgoing email headers so that the receiving email server can verify the digital key by looking up the public key in the DNS record of the sender domain.

Install OpenDKIM:

$ sudo apt install opendkim opendkim-tools

Add postfix user to opendkim group:

$ sudo gpasswd -a postfix opendkim

Configure:

$ sudo micro /etc/opendkim.conf

Specify:

# keep to log in /var/log/mail.log
Syslog              yes
SyslogSuccess       yes
# change to "yes" if desired to log more details
Logwhy              no
# keep
Canonicalization    relaxed/simple
# uncomment
Mode                sv
SubDomains          no
# keep
OversignHeaders     from
# add
AutoRestart         yes
AutoRestartRate     10/1M
Background          yes
DNSTimeout          5
SignatureAlgorithm  rsa-sha256
# keep
UserID              opendkim
UMask               007
# comment
#Socket             local:/run/opendkim/opendkim.sock
# add
Socket              local:/var/spool/postfix/opendkim/opendkim.sock
# keep
PidFile             /run/opendkim/opendkim.pid
TrustAnchorFile     /usr/share/dns/root.key
# keep
KeyTable            refile:/etc/opendkim/key.table
SigningTable        refile:/etc/opendkim/signing.table
ExternalIgnoreList  /etc/opendkim/trusted.hosts
InternalHosts       /etc/opendkim/trusted.hosts

Create a directory structure:

$ sudo mkdir /etc/opendkim
$ sudo mkdir /etc/opendkim/keys
$ sudo chown -R opendkim:opendkim /etc/opendkim
$ sudo chmod go-rw /etc/opendkim/keys

Create the signing table:

$ sudo micro /etc/opendkim/signing.table

Specify:

*@kctang.com.hk        default._domainkey.kctang.com.hk
# if sub-domain used
*@*.kctang.com.hk      default._domainkey.kctang.com.hk    

Create the key table:

$ sudo micro /etc/opendkim/key.table

Specify:

default._domainkey.kctang.com.hk     kctang.com.hk:default:/etc/opendkim/keys/kctang.com.hk/default.private

Create the trusted hosts file:

$ sudo micro /etc/opendkim/trusted.hosts

Specify to sign but not to verify emails from localhost or the following domain:

127.0.0.1
localhost
*.kctang.com.hk

("*" added, 1/2/2023)

Create separate folder for the domain and generate Private/Public Keypair:

$ sudo mkdir /etc/opendkim/keys/kctang.com.hk
$ sudo opendkim-genkey -b 1024 -d kctang.com.hk -D /etc/opendkim/keys/kctang.com.hk -s default -v

Use -b 2048 instead of -b 1024 for bits of key if the domain name server permits.
-d for domain name

-D for directory to store the keys

-s for selector

The private key will be written to default.private file and the public key will be written to default.txt file.

Change owner and permission:

$ sudo chown opendkim:opendkim /etc/opendkim/keys/kctang.com.hk/default.private
$ sudo chmod 600 /etc/opendkim/keys/kctang.com.hk/default.private

Display the public key:

$ sudo cat /etc/opendkim/keys/kctang.com.hk/default.txt

The string after the p parameter is the public key:

default._domainkey      IN      TXT     ( "v=DKIM1; h=sha256; k=rsa; "
          "p=..." )  ; ----- DKIM key default for kctang.com.hk

Define in the Domain Name Record on the domain name host:

Name = default._domainkey

Address or value = the text in ( ) above, but deleting double quotes and spaces

Type = SPF (txt)

Test DKIM Key:

$ sudo opendkim-testkey -d kctang.com.hk -s default -vvv

Successful if an opendkim-testkey output is key OK.

No problem if an opendkim-testkey output is key not secure because DNSSEC may not have been enabled on the domain name.

The DKIM record may take up to 24 hours to propagate to the Internet.

Can also go to https://www.dmarcanalyzer.com/dkim/dkim-check/, enter "kctang.com.hk" as the domain name and "default" as the selector to check DKIM record propagation.

In case of the query timed out error, comment out the following line in /etc/opendkim.conf file and restart opendkim.service.

TrustAnchorFile       /usr/share/dns/root.key

Create a directory to hold the OpenDKIM socket file, and allow only opendkim user and postfix group to access it:

$ sudo mkdir /var/spool/postfix/opendkim
$ sudo chown opendkim:postfix /var/spool/postfix/opendkim

Edit:

$ sudo micro /etc/default/opendkim

Comment out:

# SOCKET=local:$RUNDIR/opendkim.sock

Add to suit Ubuntu:

SOCKET=local:/var/spool/postfix/opendkim/opendkim.sock

Create the file

$ sudo mkdir /var/spool/postfix/opendkim 
$ sudo chown -R opendkim:opendkim /var/spool/postfix/opendkim

(file creation added, 11/2/2023)

Edit Postfix main.cf to configure Milter as shown above.

Restart opendkim and postfix service and check status to see any errors:

$ sudo systemctl restart opendkim postfix
$ sudo systemctl status opendkim postfix

(status check added, 11/2/2023)

Set up SPF record

(implemented, 19/2/2022

section added, 14/4/2025)

A Sender Policy Framework (SPF) record is to enable verification that an email comes from its authorized email server bearing the proper domain name.

Execute to install spf app:

$ sudo apt install postfix-policyd-spf-python

Edit "master.cf" settings:

$ sudo micro /etc/postfix/master.cf

Specify​ as follows (already done above):

# the following added for spf, KCTang 1/6/2019
policyd-spf   unix  -       n       n       -       0       spawn
  user=policyd-spf argv=/usr/bin/policyd-spf

Edit "main.cf" settings:

$ sudo micro /etc/postfix/main.cf

Specify​ as follows (already done above):

smtpd_recipient_restrictions = 
   reject_unknown_sender_domain
   reject_unknown_recipient_domain 
   reject_unauth_pipelining
   permit_mynetworks
   permit_sasl_authenticated 
   reject_unauth_destination
   check_policy_service unix:private/policyd-spf
# last line added to enable spf, KCTang 19/2/2022

# the following line added to enable spf, KCTang 19/2/2022 
policyd-spf_time_limit = 3600

Set up the SPF record with the domain hosting company as shown in the next section.

Set up Domain Name record

Set up the Domain Name record at the domain hosting company as follows:

v=DMARC1; p=quarantine; pct=10
v=spf1 ip4::61.238.249.58 include:hku.hk include:_spf.google.com include:spf.protection.outlook.com ~all

The first one is a DMARC record.

The second one is a spf record.

The ip4 is our company's IP address. This is not a secret.

(added, 21/2/2023)

(ip address changed, 7/1/2025)

 

Increase Dovecot connection limit

(section added, 25/6/2026)

(updated, 17/7/2026)

Edit config file:

$ sudo micro /etc/dovecot/conf.d/99-mail-stack-delivery.conf

Specify the following which controls the maximum number of all connections to Dovecot:

service imap-login {
  process_limit = 500
}

Save and exit.
Activate the change:

$ sudo systemctl reload dovecot

Check limit of number of files:

$ systemctl show dovecot | grep LimitNOFILE

It should show:

LimitNOFILE=65535
LimitNOFILESoft=65535

No need to adjust limit of number of files which is big enough based on Dovecot 2.4.

Monitor live usage:
Active sessions:

$ sudo doveadm who

Memory per process:

$ ps -o pid,rss,comm -u dovecot

File descriptors in use:

$ lsof -u dovecot | wc -l

System load:

$ sudo apt install htop
(if not already installed)
 
$ htop

Security hardening:
Enable **fail2ban** or similar to block brute‑force login attempts.  
Check kernel parameters (optional but good practice): 

$ sysctl fs.file-max

This should give a very big number like: fs.file-max = 9223372036854775807, which represents the maximum number of file descriptors.

End of Page