This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| computing:mailserver-trixie [2026/07/31 15:40] – oemb1905 | computing:mailserver-trixie [2026/08/31 00:07] (current) – oemb1905 | ||
|---|---|---|---|
| Line 70: | Line 70: | ||
| sudo apt update && sudo apt upgrade -y | sudo apt update && sudo apt upgrade -y | ||
| - | sudo apt install mailutils postfix ufw fail2ban | + | sudo apt install mailutils postfix ufw fail2ban apache2 php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip dovecot-core dovecot-imapd dovecot-lmtpd |
| It's also important that that the host knows how to identify itself properly. Let's open it up ''/ | It's also important that that the host knows how to identify itself properly. Let's open it up ''/ | ||
| - | < | + | < |
| 127.0.0.1 localhost | 127.0.0.1 localhost | ||
| 127.0.1.1 mail.haacksnetworking.org haacksnetworking | 127.0.1.1 mail.haacksnetworking.org haacksnetworking | ||
| Line 131: | Line 131: | ||
| Make sure to create '' | Make sure to create '' | ||
| - | < | + | < |
| submission inet n - y - - smtpd | submission inet n - y - - smtpd | ||
| -o syslog_name=postfix/ | -o syslog_name=postfix/ | ||
| Line 155: | Line 155: | ||
| The changes above add service definitions and configurations for smtps (465) and submission (587), both of which handle mail submission, or the sending of email. Examples of these configurations can be found in ''/ | The changes above add service definitions and configurations for smtps (465) and submission (587), both of which handle mail submission, or the sending of email. Examples of these configurations can be found in ''/ | ||
| - | < | + | < |
| # A) Leave the following upper-block defaults | # A) Leave the following upper-block defaults | ||
| smtpd_banner = $myhostname ESMTP $mail_name (Debian/ | smtpd_banner = $myhostname ESMTP $mail_name (Debian/ | ||
| Line 202: | Line 202: | ||
| inet_protocols = all | inet_protocols = all | ||
| message_size_limit = 52428800 | message_size_limit = 52428800 | ||
| + | cyrus_sasl_config_path = / | ||
| </ | </ | ||
| Line 212: | Line 213: | ||
| In order for dovecot to work, we need to specify which protocols we allow, the mail directory location, ensure that dovecot is a member of the mail group, and enable lmtp the for delivery agent. Lmtp is a local delivery agent and hands off processed incoming emails to the storage directories without requiring external authentication. This ensures everything is delivered locally, or within the hardened host. Larger distributed email systems require smtp for network hand offs between servers, but that's not required or helpful for mail server self-hosters. Open up ''/ | In order for dovecot to work, we need to specify which protocols we allow, the mail directory location, ensure that dovecot is a member of the mail group, and enable lmtp the for delivery agent. Lmtp is a local delivery agent and hands off processed incoming emails to the storage directories without requiring external authentication. This ensures everything is delivered locally, or within the hardened host. Larger distributed email systems require smtp for network hand offs between servers, but that's not required or helpful for mail server self-hosters. Open up ''/ | ||
| - | < | + | < |
| protocols = imap lmtp | protocols = imap lmtp | ||
| dovecot_storage_version = 2.4.1 | dovecot_storage_version = 2.4.1 | ||
| Line 225: | Line 226: | ||
| - | < | + | < |
| mail_driver = maildir | mail_driver = maildir | ||
| mail_path = ~/Maildir | mail_path = ~/Maildir | ||
| Line 239: | Line 240: | ||
| It is required that lmtp be configured for use with dovecot. To do that, edit ''/ | It is required that lmtp be configured for use with dovecot. To do that, edit ''/ | ||
| - | < | + | < |
| service lmtp { | service lmtp { | ||
| unix_listener / | unix_listener / | ||
| Line 251: | Line 252: | ||
| Postfix also needs to know the delivery agent that's being used, otherwise it won't know what service to which it should send incoming email. In short, lmtp hands off emails from postfix' | Postfix also needs to know the delivery agent that's being used, otherwise it won't know what service to which it should send incoming email. In short, lmtp hands off emails from postfix' | ||
| - | < | + | < |
| mailbox_transport = lmtp: | mailbox_transport = lmtp: | ||
| smtputf8_enable = no | smtputf8_enable = no | ||
| Line 265: | Line 266: | ||
| Now, let's enforce ssl so that the plain and login mechanisms above are wrapped - end to end - in TLS handshakes. It is also important to explicitly define the least secure and/or oldest TLS handshake your server will permit. Additionally, | Now, let's enforce ssl so that the plain and login mechanisms above are wrapped - end to end - in TLS handshakes. It is also important to explicitly define the least secure and/or oldest TLS handshake your server will permit. Additionally, | ||
| - | < | + | < |
| ssl = required | ssl = required | ||
| ssl_server_cert_file = / | ssl_server_cert_file = / | ||
| Line 274: | Line 275: | ||
| Add SASL listener in ''/ | Add SASL listener in ''/ | ||
| - | < | + | < |
| service auth { | service auth { | ||
| unix_listener / | unix_listener / | ||
| Line 303: | Line 304: | ||
| </ | </ | ||
| - | Lastly, before testing, make sure that you only authorize your mynetworks and properly authenticated users. Failing to do this will mean your server could potentially be used for public relay. This block rejects any unauthenticated senders (besides localhost) and requires senders to be authenticated (or to be localhost) while only permitting incoming email directed to '' | + | Lastly, before testing, make sure that you only authorize your mynetworks and properly authenticated users. Failing to do this will mean your server could potentially be used for public relay. This block rejects any unauthenticated senders (besides localhost) and requires senders to be authenticated (or to be localhost) while only permitting incoming email directed to '' |
| - | < | + | < |
| smtpd_sender_restrictions = permit_mynetworks, | smtpd_sender_restrictions = permit_mynetworks, | ||
| smtpd_recipient_restrictions = permit_mynetworks, | smtpd_recipient_restrictions = permit_mynetworks, | ||
| Line 312: | Line 313: | ||
| At this time, the bare minimum requirements are in place for sending and receiving email. You can either fire up Thunderbird and field test it all, or use swaks, ncat, telnet, or openssl to test the TLS handshakes. Here's the ones I use in addition to field testing with Thunderbird: | At this time, the bare minimum requirements are in place for sending and receiving email. You can either fire up Thunderbird and field test it all, or use swaks, ncat, telnet, or openssl to test the TLS handshakes. Here's the ones I use in addition to field testing with Thunderbird: | ||
| - | < | + | < |
| openssl s_client -connect mail.yourdomain.com: | openssl s_client -connect mail.yourdomain.com: | ||
| openssl s_client -connect mail.yourdomain.com: | openssl s_client -connect mail.yourdomain.com: | ||
| Line 342: | Line 343: | ||
| After that, open up ''/ | After that, open up ''/ | ||
| - | < | + | < |
| - | #spf incoming policy and recipient restrictions\ | + | #spf incoming policy and recipient restrictions |
| - | policyd-spf_time_limit = 3600\ | + | policyd-spf_time_limit = 3600 |
| smtpd_recipient_restrictions = | smtpd_recipient_restrictions = | ||
| permit_mynetworks, | permit_mynetworks, | ||
| Line 393: | Line 394: | ||
| Once that's done, it's time to open ''/ | Once that's done, it's time to open ''/ | ||
| - | < | + | < |
| Canonicalization relaxed/ | Canonicalization relaxed/ | ||
| Mode sv | Mode sv | ||
| Line 410: | Line 411: | ||
| sudo chown -R opendkim: | sudo chown -R opendkim: | ||
| sudo chmod 711 / | sudo chmod 711 / | ||
| - | sudo mkdir / | ||
| - | sudo opendkim-genkey -b 2048 -d yourdomain.com -D / | ||
| - | sudo chown opendkim: | ||
| - | sudo chmod 600 / | ||
| </ | </ | ||
| After creating the keys, edit the signing table so that outgoing emails can verify against the keypair. Open up ''/ | After creating the keys, edit the signing table so that outgoing emails can verify against the keypair. Open up ''/ | ||
| - | < | + | < |
| - | '*@yourdomain.com default._domainkey.yourdomain.com' | + | *@yourdomain.com default._domainkey.yourdomain.com |
| - | '*@*.yourdomain.com default._domainkey.yourdomain.com' | + | *@*.yourdomain.com default._domainkey.yourdomain.com |
| </ | </ | ||
| Line 427: | Line 424: | ||
| default._domainkey.yourdomain.com yourdomain.com: | default._domainkey.yourdomain.com yourdomain.com: | ||
| - | Lastly, instruct you server to trust only localhost and your domain. Open up / | + | Lastly, instruct you server to trust only localhost and your domain. Open up '' |
| - | < | + | < |
| 127.0.0.1\ | 127.0.0.1\ | ||
| localhost\ | localhost\ | ||
| Line 435: | Line 432: | ||
| </ | </ | ||
| - | Use the built-in tool to test your DKIM keypair: | + | Now, cut the keys: |
| - | sudo opendkim-testkey | + | |
| + | | ||
| + | sudo chown opendkim: | ||
| + | sudo chmod 600 / | ||
| It's now time to build your last DNS record. To do that, you need your public key you created above. To view that on the CLI, run the cat command below. You then take that output to your DNS host of choice, and create a TXT record containing this value. In the subdomain field, you enter your selector, i.e., default._domainkey and in the target, you enter the output of the command below. | It's now time to build your last DNS record. To do that, you need your public key you created above. To view that on the CLI, run the cat command below. You then take that output to your DNS host of choice, and create a TXT record containing this value. In the subdomain field, you enter your selector, i.e., default._domainkey and in the target, you enter the output of the command below. | ||
| sudo cat / | sudo cat / | ||
| + | |||
| + | Once your DNS record for DKIM is created, use the built-in tool to test the record: | ||
| + | |||
| + | sudo opendkim-testkey -d yourdomain.com -s default -vvv | ||
| Okay, now that your keypair for smtp has been created and DNS updated, it's a good time to create the server' | Okay, now that your keypair for smtp has been created and DNS updated, it's a good time to create the server' | ||
| Line 458: | Line 462: | ||
| Now that the server' | Now that the server' | ||
| - | < | + | < |
| milter_default_action = accept | milter_default_action = accept | ||
| milter_protocol = 6 | milter_protocol = 6 | ||
| Line 465: | Line 469: | ||
| </ | </ | ||
| - | The most important line is the uppermost line, which specifies that email should not be rejected as a result of leveraging the policy. Again, this ensures that spam assassin has what it needs to help users sort and organize email, without the possibility of phantom rejections, prohibiting email from having ever arrived in your inbox. After your DKIM keypair and DKIM policy are setup, you can setup a DMARC policy as well. Install the policy with sudo apt install opendmarc. After it installs, open up ''/ | + | The most important line is the uppermost line, which specifies that email should not be rejected as a result of leveraging the policy. Again, this ensures that spam assassin has what it needs to help users sort and organize email, without the possibility of phantom rejections, prohibiting email from having ever arrived in your inbox. After your DKIM keypair and DKIM policy are setup, you can setup a DMARC policy as well. Install the policy with '' |
| - | < | + | < |
| AuthservID OpenDMARC | AuthservID OpenDMARC | ||
| TrustedAuthservIDs mail.yourdomain.com | TrustedAuthservIDs mail.yourdomain.com | ||
| Line 479: | Line 483: | ||
| Similarly to the DKIM policy, we need to give postfix permissions to the DMARC tooling and socket. It's also required to add opendmarc to the postfix group: | Similarly to the DKIM policy, we need to give postfix permissions to the DMARC tooling and socket. It's also required to add opendmarc to the postfix group: | ||
| - | < | + | < |
| sudo mkdir -p / | sudo mkdir -p / | ||
| sudo chown opendmarc: | sudo chown opendmarc: | ||
| Line 489: | Line 493: | ||
| Similarly to SPF and DKIM, it's essential to define the policy in the main postfix configuration file. Open up ''/ | Similarly to SPF and DKIM, it's essential to define the policy in the main postfix configuration file. Open up ''/ | ||
| - | < | + | < |
| #dmarc policy | #dmarc policy | ||
| milter_default_action = accept | milter_default_action = accept | ||
| Line 507: | Line 511: | ||
| First, let's add sieve to the local delivery agent over in / | First, let's add sieve to the local delivery agent over in / | ||
| - | < | + | < |
| protocol lda { | protocol lda { | ||
| mail_plugins = $mail_plugins sieve | mail_plugins = $mail_plugins sieve | ||
| Line 515: | Line 519: | ||
| It is also imperative that LMTP be configured for using sieve as well. Head over to ''/ | It is also imperative that LMTP be configured for using sieve as well. Head over to ''/ | ||
| - | < | + | < |
| protocol lmtp { | protocol lmtp { | ||
| mail_plugins = quota sieve | mail_plugins = quota sieve | ||
| Line 523: | Line 527: | ||
| As with any service, it's essential to configure postfix to use it. So, head to ''/ | As with any service, it's essential to configure postfix to use it. So, head to ''/ | ||
| - | < | + | < |
| milter_default_action = accept | milter_default_action = accept | ||
| milter_protocol = 6 | milter_protocol = 6 | ||
| Line 543: | Line 547: | ||
| Sieve is already enabled in dovecot and postfix. That was done up above. Now, the tutorial is discussing how to leverage the sieve syntax or language to get desired user results for incoming email. The most basic way to do this is by setting up a global, or server-wide rule, that filters emails before dovecot, via lmtp, delivers the emails to their final destination. This is done by spam assassin adding custom fields and scoring to incoming email headers. The sieve plugin assesses these headers and then makes the correct determination for the final destination, | Sieve is already enabled in dovecot and postfix. That was done up above. Now, the tutorial is discussing how to leverage the sieve syntax or language to get desired user results for incoming email. The most basic way to do this is by setting up a global, or server-wide rule, that filters emails before dovecot, via lmtp, delivers the emails to their final destination. This is done by spam assassin adding custom fields and scoring to incoming email headers. The sieve plugin assesses these headers and then makes the correct determination for the final destination, | ||
| - | | + | < |
| + | #sieve_before = / | ||
| - | Now, create the file that you just referenced above in '' | + | #modern trixie way |
| + | sieve_script SpamToJunk { | ||
| + | type = before | ||
| + | path = / | ||
| + | } | ||
| + | </ | ||
| - | < | + | Now, create the file that you just referenced above in ''/ |
| + | |||
| + | < | ||
| require " | require " | ||
| if header :contains " | if header :contains " | ||
| Line 557: | Line 569: | ||
| Load the rule by issuing the '' | Load the rule by issuing the '' | ||
| - | < | + | < |
| report_contact webmaster@yourdomain.com | report_contact webmaster@yourdomain.com | ||
| required_score 5.0 | required_score 5.0 | ||
| report_safe 0 | report_safe 0 | ||
| - | ifplugin Mail:: | + | ifplugin Mail:: |
| + | endif Mail:: | ||
| # uncomment the line below once unbound is working (optional) | # uncomment the line below once unbound is working (optional) | ||
| # dns_server 127.0.0.1score MISSING_FROM 5.0 | # dns_server 127.0.0.1score MISSING_FROM 5.0 | ||
| Line 589: | Line 602: | ||
| This basic configuration shows you where to place the rules/ | This basic configuration shows you where to place the rules/ | ||
| - | < | + | < |
| cd /var/www | cd /var/www | ||
| wget https:// | wget https:// | ||
| Line 602: | Line 615: | ||
| Obviously, go check the git repo and make sure to download the latest stable version. Roundcube requires a database, so let's set that up: | Obviously, go check the git repo and make sure to download the latest stable version. Roundcube requires a database, so let's set that up: | ||
| - | < | + | < |
| sudo mysql -u root | sudo mysql -u root | ||
| CREATE DATABASE roundcube DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; | CREATE DATABASE roundcube DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; | ||
| Line 617: | Line 630: | ||
| Earlier in the tutorial, you were instructed to setup a vhost for '' | Earlier in the tutorial, you were instructed to setup a vhost for '' | ||
| - | < | + | < |
| < | < | ||
| ServerName mail.domain.com | ServerName mail.domain.com | ||
| ServerAdmin email@email.com | ServerAdmin email@email.com | ||
| - | DocumentRoot / | + | DocumentRoot / |
| ErrorLog ${APACHE_LOG_DIR}/ | ErrorLog ${APACHE_LOG_DIR}/ | ||
| CustomLog ${APACHE_LOG_DIR}/ | CustomLog ${APACHE_LOG_DIR}/ | ||
| Line 645: | Line 658: | ||
| After that's done, edit the vhost-mail-le.conf file with something like the following: | After that's done, edit the vhost-mail-le.conf file with something like the following: | ||
| - | < | + | < |
| < | < | ||
| < | < | ||
| ServerName mail.domain.com | ServerName mail.domain.com | ||
| ServerAdmin email@email.com | ServerAdmin email@email.com | ||
| - | DocumentRoot / | + | DocumentRoot / |
| ErrorLog ${APACHE_LOG_DIR}/ | ErrorLog ${APACHE_LOG_DIR}/ | ||
| CustomLog ${APACHE_LOG_DIR}/ | CustomLog ${APACHE_LOG_DIR}/ | ||
| Line 674: | Line 687: | ||
| Since the certificate was already created, these configs can just be dropped in to the http vhost and https vhost, respectively. These two vhosts were created earlier by you (http) and subsequently by Let's Encrypt (https). It's now time to connect Roundcube to the database that was created earlier: | Since the certificate was already created, these configs can just be dropped in to the http vhost and https vhost, respectively. These two vhosts were created earlier by you (http) and subsequently by Let's Encrypt (https). It's now time to connect Roundcube to the database that was created earlier: | ||
| - | < | + | < |
| cd / | cd / | ||
| sudo cp config.inc.php.sample config.inc.php | sudo cp config.inc.php.sample config.inc.php | ||
| Line 688: | Line 701: | ||
| In addition to configuring the database and renaming the sample config, it's imperative to edit the plugins block at the end of the primary configuration as well. Add and/or replace the bottom plugin block as follows: | In addition to configuring the database and renaming the sample config, it's imperative to edit the plugins block at the end of the primary configuration as well. Add and/or replace the bottom plugin block as follows: | ||
| - | < | + | < |
| $config[' | $config[' | ||
| ' | ' | ||
| Line 698: | Line 711: | ||
| ' | ' | ||
| ' | ' | ||
| - | ' | + | |
| ' | ' | ||
| ' | ' | ||
| Line 727: | Line 740: | ||
| At this point, you should be able to log in to mail.haacksnetworking.org / mail.domain.com using a common web browser and your credentials. Your user name is the UNIX user name, i.e., only the handle without the domain. So, my user name is jonathan, for example ... and without haacksnetworking.org at the end. Your password is whatever you adduser yourusername specified when creating your simple UNIX user. At this point, you want to open your web browser and login. Head to the Filters section under Settings. Click the cog at the top and select Edit Filter Set. In the block to the right, replace the contents with the following: | At this point, you should be able to log in to mail.haacksnetworking.org / mail.domain.com using a common web browser and your credentials. Your user name is the UNIX user name, i.e., only the handle without the domain. So, my user name is jonathan, for example ... and without haacksnetworking.org at the end. Your password is whatever you adduser yourusername specified when creating your simple UNIX user. At this point, you want to open your web browser and login. Head to the Filters section under Settings. Click the cog at the top and select Edit Filter Set. In the block to the right, replace the contents with the following: | ||
| - | < | + | < |
| require [" | require [" | ||
| # rule: | # rule: | ||
| Line 755: | Line 768: | ||
| As you can see in the asset above, you now have access to refined sieve rules for each user. Once I decided that I needed or wanted user-level spam controls, it no longer made sense for me to manage this exclusively on the CLI. For this reason, I added Roundcube and began managing spam for email accounts individually. When Trixie came out, Dovecot' | As you can see in the asset above, you now have access to refined sieve rules for each user. Once I decided that I needed or wanted user-level spam controls, it no longer made sense for me to manage this exclusively on the CLI. For this reason, I added Roundcube and began managing spam for email accounts individually. When Trixie came out, Dovecot' | ||
| - | < | + | < |
| # Comment out the old block: | # Comment out the old block: | ||
| #plugin { | #plugin { | ||
| Line 791: | Line 804: | ||
| ====== Part V - Setting up pflogsumm ====== | ====== Part V - Setting up pflogsumm ====== | ||
| - | It's important to be able to monitor how your setup is performing and what is or is not working correctly. No better way to do that than to get some analytics emailed to you each day. To do that, let's install pflogsumm and use rsyslog for logging. Install the packages sudo apt install pflogsumm rsyslog and then create the log rotation rule over in ''/ | + | It's important to be able to monitor how your setup is performing and what is or is not working correctly. No better way to do that than to get some analytics emailed to you each day. To do that, let's install pflogsumm and use rsyslog for logging. Install the packages |
| - | < | + | < |
| / | / | ||
| missingok | missingok | ||
| Line 806: | Line 819: | ||
| You need to make sure to comment out or remove #mail.log from ''/ | You need to make sure to comment out or remove #mail.log from ''/ | ||
| - | < | + | < |
| #!/bin/sh | #!/bin/sh | ||
| #/ | #/ | ||
| Line 831: | Line 844: | ||
| Some of the spam assassin tooling that uses RBL will not work unless you use your own recursive DNS instead of, for example, '' | Some of the spam assassin tooling that uses RBL will not work unless you use your own recursive DNS instead of, for example, '' | ||
| - | < | + | < |
| server: | server: | ||
| # Bind to localhost only | # Bind to localhost only | ||
| Line 910: | Line 923: | ||
| Next, open up ''/ | Next, open up ''/ | ||
| - | < | + | < |
| / | / | ||
| daily | daily | ||
| Line 924: | Line 937: | ||
| Next, disable systemd' | Next, disable systemd' | ||
| - | < | + | < |
| systemctl disable --now unbound-resolvconf.service | systemctl disable --now unbound-resolvconf.service | ||
| sed -Ei ' | sed -Ei ' | ||
| Line 974: | Line 987: | ||
| In the .xml file, put the following, adjusting for '' | In the .xml file, put the following, adjusting for '' | ||
| - | < | + | < |
| <?xml version=" | <?xml version=" | ||
| < | < | ||
| Line 1033: | Line 1046: | ||
| The options below are results of small things that came up while using my own server over the last 5 years or so. First, I noticed that clients would not set up the standard directories and it turns out you need to tell dovevot to do that over in ''/ | The options below are results of small things that came up while using my own server over the last 5 years or so. First, I noticed that clients would not set up the standard directories and it turns out you need to tell dovevot to do that over in ''/ | ||
| - | < | + | < |
| mailbox Drafts { | mailbox Drafts { | ||
| auto = create | auto = create | ||
| Line 1050: | Line 1063: | ||
| Once you edited the file, load the changes with sudo postmap / | Once you edited the file, load the changes with sudo postmap / | ||
| - | Another thing I researched when reviewing Linux Babe's tutorial, but ultimately rejected doing was body and header inspection. To do that, install postfix' | + | Another thing I researched when reviewing Linux Babe's tutorial, but ultimately rejected doing was body and header inspection. To do that, install postfix' |
| header_checks = pcre:/ | header_checks = pcre:/ | ||
| Line 1130: | Line 1143: | ||
| I rewrote the mail server tutorial for the presentation [[https:// | I rewrote the mail server tutorial for the presentation [[https:// | ||
| - | --- // | + | --- // |