Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
email:configure-postfix [2025/03/03 09:32] – created willy | email:configure-postfix [2025/03/13 13:30] (current) – [Configuration: postfix] willy | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | USE flags: | + | ====== F) Configuration: postfix |
- | <code bash> | + | |
- | echo "*/* maildir dovecot sasl" >> / | + | |
- | echo " | + | |
- | echo " | + | |
- | echo " | + | |
- | </ | + | |
+ | Postfix is the Mail Transfer Agent, the tool that actually **moves** your email messages from a the sender to the destination (recipient). It speak the Simple Mail Transport Protocol (SMTP) and it has some serious responsibilities which are: | ||
+ | * Ensure that mail **for** you is properly delivered to you | ||
+ | * Ensure that mail **from** you is properly sent to whoever need to deliver it | ||
+ | * Filter out all undesired mail (spam) | ||
+ | * Prevent abuse from malicious actors who pretend to be you to send unauthorized mail in your name | ||
+ | * Prevent abuse from spammers to send mail pretending to be others (not act as an open relay) | ||
+ | * Ensure that all good practices are followed to prevent your mail server from being listed in blacklists and flagged down as a spammer. | ||
- | ===== Installation: | + | Due to these many tasks, an MTA is not a simple piece of software to properly setup. Postfix is no exception, and you need to understand a few basic concepts before proceeding. |
- | postfixadmin and roundcube will be installed manually and not via Gentoo portage, to avoid upgrade issues. | + | ===== Mail Delivery Pipeline ===== |
- | Download latest release | + | Traditionally, |
- | <code bash> | + | |
- | su # do this as root! You don't need to make postfixadmin writable by the web user | + | |
- | cd /home/web | + | |
- | mkdir postfixadmin | + | |
- | cd postfixadmin | + | |
- | wget https:// | + | |
- | tar xvf postfixadmin-3.3.15.tar.gz | + | |
- | mv postfixadmin-postfixadmin-3.3.15 postfixadmin | + | |
- | #The following folder must be writeable by web user: | + | |
- | mkdir -p postfixadmin/ | + | |
- | chown -R web postfixadmin/ | + | |
- | </ | + | |
- | Now, configure it by creating | + | Nowadays you have a complete separation between |
- | <file - config.local.php> | + | |
- | <?php | + | |
- | $CONF[' | + | |
- | $CONF[' | + | |
- | $CONF[' | + | |
- | $CONF[' | + | |
- | $CONF[' | + | |
- | $CONF[' | + | |
- | ' | + | |
- | ' | + | |
- | ' | + | |
- | ' | + | |
- | ); | + | |
- | $CONF[' | + | |
- | $CONF[' | + | |
- | /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: | + | |
- | </ | + | |
- | Now setup NGINX to point to it. You need of course to setup a certbot certificate, | + | The pipeline is: |
- | <file postfixadmin.conf> | + | * An SMTP client connect |
- | server { | + | |
- | | + | |
- | listen 443 ssl; | + | |
+ | | ||
+ | * Recipient is identified (RCPT TO:) | ||
+ | * Email data is transferred (DATA...) | ||
- | access_log / | + | Along all the steps of this pipeline, Postfix can be configured to perform checks to reject the entire transaction. |
- | error_log / | + | |
- | + | ||
- | index index.php; | + | |
- | + | ||
- | root / | + | |
- | + | ||
- | # Uncomment | + | |
- | # location ~ / | + | |
- | # deny all; | + | |
- | # alias / | + | |
- | # } | + | |
- | + | ||
- | location ~ /.*\.php$ { | + | |
- | try_files $uri =404; | + | |
- | fastcgi_split_path_info ^(.+\.php)(/ | + | |
- | include fastcgi_params; | + | |
- | fastcgi_param SCRIPT_FILENAME $request_filename; | + | |
- | fastcgi_pass 127.0.0.1:9000; | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
- | restart NGINX and go to the URL **https:// | + | === The local delivery === |
+ | Emails will be delivered locally | ||
- | Also don't forget | + | In our case, local delivery will be routed |
- | Go back, uncomment | + | === The virtual delivery === |
+ | Emails will be delivered to a list of domains and mailboxes, not users. It's called //virtual// because there the domains and the mailboxes are defined in a database and are not linked | ||
- | __note:__ when adding new domains, choose "virtual" as transport, and 0 as password expiry. | + | For our needs, i will show you how to link PostfixAdmin SQLite database to Postfix to perform |
- | At this point, you can already create all the mail domains and user accounts you want. | + | === The mail Relay === |
+ | So far, i always referred to emails to be delivered to the server (local) or to domains | ||
+ | The **relay** should be allowed only to authorized users to prevent spammers to abuse our server, which will cause the server itself to be blacklisted and unable to send any emails at all. | ||
- | ===== Configuration: | ||
- | Link to SQL. | + | ===== Mail Security ===== |
+ | The SMTP protocol by itself is unencrypted and unsecured. This was maybe ok at the beginning of Internet, but not today. Today you need to enable both authentication and encryption. | ||
- | File: **/ | + | Encryption is performed using TLS, but for legacy reasons you cannot |
- | <file - virtual_mailbox_domains.cf> | + | * Plain, no TLS (port 25) |
- | dbpath = / | + | * STARTTLS, connection starts unencrypted and can be __upgraded__ to a TLS connection if both parties support it (port 587) |
- | query = SELECT domain FROM domain WHERE domain = ' | + | |
- | </file> | + | |
- | File: **/ | + | Plain must always be provided, but you can at least prohibit authentication on non-encrypted connections. |
- | < | + | |
- | dbpath = / | + | |
- | query = SELECT maildir FROM mailbox WHERE local_part=' | + | |
- | </ | + | |
- | File: **/etc/postfix/sql/virtual_alias_maps.cf**: | + | The [[https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer~SASL]] (Simple Authentication Security Layer) is used for authentication. This implies the transmission on the connection of the username+password combination using a Base64 encoding. Please note, and this is **very important**, |
- | <file - virtual_alias_maps.cf> | + | |
- | dbpath = /home/vmail/db/vmail.sqlite3 | + | |
- | query = SELECT goto FROM alias WHERE address=' | + | |
- | </ | + | |
- | Now, link it all in **/ | + | Postfix does not provide a SASL authentication, this is demanded to Dovecot SASL implementation. The added value of this choice is to have the same background for both SMTP and IMAP servers. |
- | < | + | |
- | # A list of all virtual domains serviced by this instance of postfix. | + | |
- | virtual_mailbox_domains = sqlite:/ | + | |
- | # Look up the mailbox location based on the email address received. | + | |
- | virtual_mailbox_maps = sqlite:/ | + | |
- | # Any aliases that are supported by this system | + | |
- | virtual_alias_maps = sqlite:/ | + | |
- | </ | + | |
+ | ===== Postfix documentation resources ===== | ||
+ | Postfix website has tons of great documentation, | ||
- | < | + | On the internet there are lots of wikis and HOWTOs about postfix, including [[https:// |
- | compatibility_level = 3.6 | + | |
- | # Prevent hard-bounces | ||
- | soft_bounce = yes | ||
- | queue_directory | + | ===== Postfix Configuration ===== |
- | command_directory | + | |
- | daemon_directory | + | |
- | data_directory | + | |
- | mail_owner = postfix | + | Postfix configuration files are located under **/etc/postfix** and can be summarized as: |
+ | * main.cf: contains all the oeprative directives of Postfix | ||
+ | * master.cf: contains the services started by Postfix | ||
+ | * sql/*.cf: the specific SQLite link files. You //need// to create this folder. | ||
- | # Usa gethostname() per default | + | Please note that Postfix configuration is deeply connected to Dovecot, Spamassassin, |
- | #myhostname = gardiol.org | + | |
- | mydomain = gardiol.org | ||
- | # | + | ===== PostfixAdmin Link ===== |
- | # | + | Postfix support |
- | mydestination = localhost.localdomain | + | Create the sql folder: |
- | unknown_local_recipient_reject_code = 550 | + | <code bash> |
+ | mkdir / | ||
+ | </ | ||
- | mynetworks_style = host | + | Drop //all// the following files in the sql folder. |
- | in_flow_delay | + | File: **/ |
+ | <file - virtual_mailbox_domains.cf> | ||
+ | dbpath | ||
+ | query = SELECT domain FROM domain WHERE domain = ' | ||
+ | </ | ||
- | home_mailbox | + | File: **/ |
+ | <file - virtual_mailbox_maps.cf> | ||
+ | dbpath | ||
+ | query = SELECT | ||
+ | </file> | ||
- | header_checks = regexp:/ | + | File: **/ |
+ | <file - virtual_alias_maps.cf> | ||
+ | dbpath = / | ||
+ | query = SELECT goto FROM alias WHERE address=' | ||
+ | </ | ||
- | smtpd_banner = $myhostname ESMTP NO UCE | + | To ensure the above files are used by postfix, the following lines need to be added to the **main.cf** config file as show below. |
- | debug_peer_level = 2 | ||
- | # | ||
- | sendmail_path | + | ===== main.cf: configuration ===== |
- | newaliases_path | + | |
- | mailq_path | + | |
- | setgid_group = postdrop | + | For more details on all the possible options, see [[http://www.postfix.org/postconf.5.html|Postfix documentation]]. |
- | html_directory = no | + | |
- | manpage_directory = /usr/ | + | |
- | sample_directory = / | + | |
- | readme_directory = no | + | |
- | inet_protocols = ipv4 | + | |
- | meta_directory = / | + | |
- | shlib_directory = /usr/lib64/postfix/${mail_version} | + | |
+ | This is the overall file. **Do not copy it blindly**, double check everything, because there are comments that will cause postfix **not** to start if ignored: | ||
+ | <file - main.cf> | ||
+ | compatibility_level = 3.9 | ||
- | ############################################ | + | # Generic stuff |
- | ########################################### | + | mail_owner = postfix |
- | ########################################### | + | myhostname = mydomain.com |
- | disable_vrfy_command | + | mydomain |
- | message_size_limit | + | myorigin |
- | #20971520 | + | inet_interfaces = all |
biff = no | biff = no | ||
+ | message_size_limit = 0 <- this is unlimited, you might want to set a mail size limit here | ||
+ | disable_vrfy_command = yes | ||
+ | syslog_facility = mail | ||
+ | syslog_name = postfix | ||
+ | # Protection from lost mail | ||
+ | soft_bounce = yes <- switch to no in production | ||
+ | unknown_local_recipient_reject_code = 450 <- switch to 550 in production | ||
+ | |||
+ | # Important to keep as is to avoid delivery loops with the virtual domains | ||
+ | mydestination = localhost.localdomain | ||
+ | mynetworks_style = host | ||
+ | |||
+ | # Local delivery is actually managed by virtual delivery | ||
local_transport = virtual | local_transport = virtual | ||
- | local_recipient_maps = $alias_maps | + | local_recipient_maps = $virtual_mailbox_maps |
+ | # Manage postfix virtual delivery mechanism via dovecot lmtp service | ||
virtual_transport = lmtp: | virtual_transport = lmtp: | ||
+ | virtual_mailbox_domains = sqlite:/ | ||
+ | virtual_mailbox_maps = sqlite:/ | ||
+ | virtual_alias_maps = sqlite:/ | ||
- | virtual_uid_maps = static: | + | # SMTPD (server) SASL + TLS setup |
- | virtual_gid_maps = static: | + | |
- | + | ||
- | virtual_mailbox_domains = proxy: | + | |
- | virtual_alias_maps = proxy: | + | |
- | | + | |
- | | + | |
- | virtual_mailbox_maps = proxy: | + | |
- | | + | |
- | + | ||
- | # if you let postfix store your mails directly | + | |
- | virtual_mailbox_base = / | + | |
- | + | ||
- | # SASL | + | |
- | smtpd_sasl_type = dovecot | + | |
- | smtpd_sasl_path = private/ | + | |
smtpd_sasl_auth_enable = yes | smtpd_sasl_auth_enable = yes | ||
- | smtpd_sasl_security_options = noanonymous | ||
- | smtpd_sasl_local_domain = | ||
broken_sasl_auth_clients = no | broken_sasl_auth_clients = no | ||
- | smtpd_sasl_authenticated_header | + | smtpd_tls_auth_only |
- | # Setup TLS | + | smtpd_sasl_type = dovecot |
- | smtpd_tls_cert_file | + | smtpd_sasl_path |
- | smtpd_tls_key_file | + | smtpd_sasl_security_options |
- | # abilita il debug... | + | smtpd_sasl_tls_security_options |
- | smtpd_tls_loglevel | + | |
- | # metti a " | + | |
smtpd_tls_security_level = may | smtpd_tls_security_level = may | ||
- | # Metti a yes per impedire AUTH non cifrata | + | smtpd_tls_cert_file = / |
- | smtpd_tls_auth_only | + | smtpd_tls_key_file |
- | # Fai la cache delle sessioni | + | smtpd_tls_mandatory_protocols = > |
smtpd_tls_session_cache_database = btree:/ | smtpd_tls_session_cache_database = btree:/ | ||
- | # Some ANTISPAM | + | # Generic access rules |
- | smtpd_delay_reject | + | strict_rfc821_envelopes |
smtpd_helo_required = yes | smtpd_helo_required = yes | ||
- | smtpd_helo_restrictions | + | smtpd_reject_unlisted_sender |
- | smtpd_sender_restrictions = permit_sasl_authenticated, | + | |
- | smtpd_recipient_restrictions = reject_unauth_pipelining, | + | |
- | smtpd_client_restrictions = permit_mynetworks, | + | |
- | #, reject_rbl_client zen.spamhaus.org, | + | |
- | policy-spf_time_limit = 3600s | + | # Client connection (do not put " |
+ | smtpd_client_restrictions = permit_sasl_authenticated | ||
+ | # HELO / EHLO filtering | ||
+ | smtpd_helo_restrictions = permit_sasl_authenticated, | ||
+ | # MAIL FROM | ||
+ | smtpd_sender_restrictions = permit_sasl_authenticated, | ||
+ | # RCPT TO: (before recipient) | ||
+ | smtpd_relay_restrictions = permit_sasl_authenticated, | ||
+ | # DATA | ||
+ | smtpd_data_restrictions = reject_unauth_pipelining | ||
+ | # R * | ||
+ | | ||
+ | smtpd_recipient_restrictions = permit_sasl_authenticated check_policy_service unix: | ||
- | smtpd_timeout | + | # OpenDKIM & OpenDMARC setup |
- | default_process_limit | + | smtpd_milters |
+ | non_smtpd_milters | ||
+ | milter_default_action | ||
- | smtputf8_enable = no | + | </ |
- | smtp_data_done_timeout = 1800 | + | |
- | smtpd_milters = unix:/ | ||
- | non_smtpd_milters = unix:/ | ||
- | syslog_facility = mail | ||
- | syslog_name = postfix | ||
- | body_checks | + | ===== master.cf: configuration ===== |
+ | |||
+ | The master.cf contains a list of the services (and ports) managed by Postfix on startup: | ||
+ | <file - master.cf> | ||
+ | |||
+ | # Port 25 listener | ||
+ | smtp inet n | ||
+ | |||
+ | # Port 587 listener (STARTTLS) | ||
+ | smtps inet n | ||
+ | -o smtpd_tls_wrappermode=yes | ||
- | maximal_queue_lifetime = 60m | + | # Port 465 listener (pure TLS) |
- | bounce_queue_lifetime = 60m | + | submission inet n |
- | smtp_connect_timeout | + | |
- | smtp_helo_timeout = 60s | + | |
- | smtpd_relay_before_recipient_restrictions | + | # SPF inbound check filter |
+ | policy-spf | ||
+ | user=nobody argv=/ | ||
+ | |||
+ | pickup | ||
+ | cleanup | ||
+ | qmgr fifo n | ||
+ | tlsmgr | ||
+ | rewrite | ||
+ | bounce | ||
+ | defer | ||
+ | trace | ||
+ | verify | ||
+ | flush | ||
+ | proxymap | ||
+ | proxywrite unix - | ||
+ | smtp unix - | ||
+ | relay | ||
+ | showq | ||
+ | error | ||
+ | retry | ||
+ | discard | ||
+ | local | ||
+ | virtual | ||
+ | lmtp unix - | ||
+ | anvil | ||
+ | scache | ||
</ | </ | ||
+ | ===== Aliases ===== | ||
+ | Before starting postfix you need to generate the aliases database: | ||
+ | <code bash> | ||
+ | newaliases | ||
+ | </ | ||
+ | that's it. | ||
+ | ===== Testing ===== | ||
- | ===== Installation: | + | To test Postfix you need your Dovecot to be running, so set it up, ensure Dovecot SASL is working, then come back here. |
- | This step is **mandatory** and critical | + | To generate a valid Base64 encoding |
+ | To test your unencrypted SMTP service: | ||
+ | <code bash> | ||
+ | telnet 127.0.0.1 25 | ||
+ | Trying 127.0.0.1... | ||
+ | Connected to 127.0.0.1. | ||
+ | Escape character is ' | ||
+ | 220 gardiol.org ESMTP Postfix | ||
+ | EHLO mydomain.com | ||
+ | 250-gardiol.org | ||
+ | 250-PIPELINING | ||
+ | 250-SIZE | ||
+ | 250-ETRN | ||
+ | 250-STARTTLS | ||
+ | 250-ENHANCEDSTATUSCODES | ||
+ | 250-8BITMIME | ||
+ | 250-DSN | ||
+ | 250-SMTPUTF8 | ||
+ | 250 CHUNKING | ||
+ | quit | ||
+ | 221 2.0.0 Bye | ||
+ | Connection closed by foreign host. | ||
+ | </ | ||
- | ===== Installation: | + | ensure that AUTH is not offered! Make sure there is no **250-AUTH PLAIN LOGIN** in the output! This means you have successfully disabled SASL authentication on plain text connections. |
- | Install spamassassin & amavisd-new | + | To test SSL/TLS: |
+ | <code bash> | ||
+ | openssl s_client | ||
+ | Connecting to 127.0.0.1 | ||
+ | CONNECTED(00000003) | ||
+ | [ ... long TLS handshake omissis ... ] | ||
- | FILE / | + | EHLO mydomain.com |
- | <code> | + | 250-mydomain.com |
- | # Link the mailbox uid and gid to postfix. | + | 250-PIPELINING |
- | virtual_uid_maps = static:5000 | + | 250-SIZE |
- | virtual_gid_maps = static:5000 | + | 250-ETRN |
- | + | 250-AUTH PLAIN LOGIN | |
- | # Set the base address for all virtual mailboxes | + | 250-ENHANCEDSTATUSCODES |
- | virtual_mailbox_base = /var/vmail | + | 250-8BITMIME |
+ | 250-DSN | ||
+ | 250-SMTPUTF8 | ||
+ | 250 CHUNKING | ||
+ | AUTH PLAIN -- never post your Base64 strings on internet! -- | ||
+ | 235 2.7.0 Authentication successful | ||
+ | MAIL FROM:<user@mydomain.com> | ||
+ | 250 2.1.0 Ok | ||
+ | RCPT TO:< | ||
+ | 250 2.1.5 Ok | ||
+ | DATA | ||
+ | 354 End data with < | ||
+ | Subject: My Test Email | ||
+ | Test email body | ||
+ | . | ||
+ | 250 2.0.0 Ok: queued as 194869C1AB | ||
+ | quit | ||
+ | 221 2.0.0 Bye | ||
+ | QUIT | ||
</ | </ | ||
+ | Double check that you have that **250-AUTH PLAIN LOGIN**, this means that you have properly setup SASL authentication using TLS. | ||
+ | |||
+ | To tets TLS without STARTLS, repeat the same command above without //-starttls smtp//, but remember to use port 465 instead of 587! | ||
+ | |||
+ | You should now test that: | ||
+ | * You can send an email from yourself to yourself | ||
+ | * You can send an email from yourself to another mailbox of your domain | ||
+ | * You can **not** send emails to other domains if you do not authenticate with SASL on TLS | ||
+ | |||
+ | That should do. | ||
+ | |||
+ | After you are done, go back to your **main.cf** disable soft_bounce and set 550 as error code instead of 450. | ||
+ | |||
+ | |||
+ | |||
+ | |||