new page on certbot. re-ordered pages
This commit is contained in:
parent
a29d2627d6
commit
13b2d7bedd
5 changed files with 379 additions and 228 deletions
|
|
@ -3,7 +3,7 @@ title: Installing Debian
|
||||||
lastUpdated: 2025-08-14
|
lastUpdated: 2025-08-14
|
||||||
slug: ispmail-trixie/install-debian
|
slug: ispmail-trixie/install-debian
|
||||||
sidebar:
|
sidebar:
|
||||||
order: 130
|
order: 120
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Aside } from "@astrojs/starlight/components";
|
import { Aside } from "@astrojs/starlight/components";
|
||||||
|
|
@ -3,7 +3,7 @@ title: Install software packages
|
||||||
lastUpdated: 2025-08-14
|
lastUpdated: 2025-08-14
|
||||||
slug: ispmail-trixie/install-the-software-packages
|
slug: ispmail-trixie/install-the-software-packages
|
||||||
sidebar:
|
sidebar:
|
||||||
order: 140
|
order: 130
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Aside } from "@astrojs/starlight/components";
|
import { Aside } from "@astrojs/starlight/components";
|
||||||
|
|
@ -11,14 +11,14 @@ import { Aside } from "@astrojs/starlight/components";
|
||||||
Let's install the necessary Debian packages. Run this command as root on your server as _root_:
|
Let's install the necessary Debian packages. Run this command as root on your server as _root_:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
apt update
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
apt -y install postfix-mysql dovecot-mysql \
|
apt -y install postfix-mysql dovecot-mysql \
|
||||||
dovecot-imapd dovecot-lmtpd dovecot-managesieved \
|
dovecot-imapd dovecot-lmtpd dovecot-managesieved \
|
||||||
apache python3-certbot-apache libapache2-mod-php \
|
apache2 python3-certbot-apache libapache2-mod-php \
|
||||||
php-intl php-mbstring php-xml unzip \
|
php-intl php-mbstring php-xml unzip certbot \
|
||||||
roundcube-sqlite3 roundcube swaks ufw mutt \
|
roundcube-sqlite3 roundcube swaks ufw mutt \
|
||||||
crowdsec crowdsec-firewall-bouncer unattended-upgrades \
|
unattended-upgrades mariadb-server
|
||||||
mariadb-server
|
|
||||||
|
|
||||||
postfix chroot off
|
postfix chroot off
|
||||||
```
|
```
|
||||||
|
|
@ -48,16 +48,16 @@ While the server is downloading and installing the packages, let me give you a q
|
||||||
to Postfix to send emails.
|
to Postfix to send emails.
|
||||||
- **swaks** \
|
- **swaks** \
|
||||||
The SWiss Army Knife of Smtp. Helpful command-line tool to send test emails to a mail server.
|
The SWiss Army Knife of Smtp. Helpful command-line tool to send test emails to a mail server.
|
||||||
|
- **certbot** \
|
||||||
|
Gets a free TLS certificate from [_Let's Encrypt_](https://letsencrypt.org/) for encrypting network communication.
|
||||||
- **ufw** \
|
- **ufw** \
|
||||||
Universal FireWall. A simple tool to manage firewall rules to limit access to your server.
|
Universal FireWall. A simple tool to manage firewall rules to limit access to your server. TODO: omit?
|
||||||
- **rspamd** \
|
- **rspamd** \
|
||||||
It reliably detects spam. Also handles adding DKIM signature to outgoing email to prevent spoofing your domains.
|
It reliably detects spam. Also handles adding DKIM signature to outgoing email to prevent spoofing your domains. TODO:
|
||||||
|
later/optional
|
||||||
- **mutt** \
|
- **mutt** \
|
||||||
A console-based program that can speak IMAP and also read Maildirs directly. Very helpful for testing the
|
A console-based program that can speak IMAP and also read Maildirs directly. Very helpful for testing the
|
||||||
functionality of your mail server. Think of it as a text-based Thunderbird.
|
functionality of your mail server. Think of it as a text-based Thunderbird.
|
||||||
- **crowdsec** \
|
|
||||||
Detects attacks to your mail server and blocks the IP address of the attacker. This is a more sophisticated version of
|
|
||||||
fail2ban.
|
|
||||||
- **unattended-upgrades** \
|
- **unattended-upgrades** \
|
||||||
Installs security updates automatically.
|
Installs security updates automatically.
|
||||||
|
|
||||||
|
|
@ -65,8 +65,6 @@ While the server is downloading and installing the packages, let me give you a q
|
||||||
|
|
||||||
By default, Debian enables Postfix to run in chroot mode. While this may sound like a security improvement, even
|
By default, Debian enables Postfix to run in chroot mode. While this may sound like a security improvement, even
|
||||||
Postfix’s author advises against it, arguing that it mostly creates a false sense of security. For this reason, we are
|
Postfix’s author advises against it, arguing that it mostly creates a false sense of security. For this reason, we are
|
||||||
disabling chroot mode—which also simplifies some configuration details for us later on.
|
disabling chroot mode—which also simplifies some configuration later on.
|
||||||
|
|
||||||
</Aside>
|
</Aside>
|
||||||
|
|
||||||
TODO: move crowdsec into optional chapter
|
|
||||||
114
src/content/docs/ispmail-trixie/140-certbot.mdx
Normal file
114
src/content/docs/ispmail-trixie/140-certbot.mdx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
---
|
||||||
|
title: TLS certificate
|
||||||
|
lastUpdated: 2025-08-24
|
||||||
|
slug: ispmail-trixie/tls-certificate
|
||||||
|
sidebar:
|
||||||
|
order: 140
|
||||||
|
---
|
||||||
|
|
||||||
|
Your mail server can't work properly without a valid TLS certificate. It will be used in three places:
|
||||||
|
|
||||||
|
1. the **webmail** interface (driven by the Apache web server)
|
||||||
|
2. **Postfix** (to encrypt SMTP communication with other mail servers on the internet where possible)
|
||||||
|
3. **Dovecot** (to encrypt IMAP/POP3 communication)
|
||||||
|
|
||||||
|
Your mail server will be able to host email addresses for many domains. But a TLS certificate is usually issued for a
|
||||||
|
single [FQDN](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) like “mail.example.org”. That _common name_ is
|
||||||
|
an important part of the certificate. It must match the name you use to talk to the server.
|
||||||
|
|
||||||
|
**Correct**: Your mail server is addressed as `mail.example.org`. The TLS certificate was also issued to
|
||||||
|
`mail.example.org`.
|
||||||
|
|
||||||
|
**Wrong**: Your mail server is addressed as `smtp01.example.com`. But the TLS certificate was issued to
|
||||||
|
`mail.example.org`.
|
||||||
|
|
||||||
|
So choose an FQDN for your mail server. For example I use `webmail.workaround.org` for my personal server.
|
||||||
|
|
||||||
|
## Certbot
|
||||||
|
|
||||||
|
You already have the `certbot` software and the `apache2` web server installed. Requesting a certificate from Let's
|
||||||
|
Encrypt is as simple as running the next command. Please replace `mail.example.org` by the FQDN you chose.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
certbot --apache --register-unsafely-without-email --agree-tos -d mail.example.org
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click here to learn what the options mean…</summary>
|
||||||
|
|
||||||
|
- **--apache**: Use the [HTTP challenge](https://letsencrypt.org/docs/challenge-types/) to verify that you are actually
|
||||||
|
in control of the FQDN. The certbot plugin will add some configuration to intercept HTTP requests to
|
||||||
|
`/.well-known/acme-challenge/…` on your web server. This is a fully automatic process that does not even have to stop
|
||||||
|
your web server.
|
||||||
|
- **--register-unsafely-without-email**: Do not ask for an email address to send to Let's Encrypt. Before June 2025
|
||||||
|
Let's Encrypt used that address to inform you if your certificate is about to expire and the automatic renewal failed.
|
||||||
|
Nowadays they do not need an email any more. But for historical reasons they still ask for it by default.
|
||||||
|
- **--agree-tos**: Confirm that you have read Let's Encrypt's terms and conditions. Let's be honest – nobody reads that.
|
||||||
|
- **-d**: The domain that the certificate will be issued to. This has to be the exact FQDN of your server.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
The output should look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Saving debug log to /var/log/letsencrypt/letsencrypt.log
|
||||||
|
Requesting a certificate for mail.example.org
|
||||||
|
|
||||||
|
Successfully received certificate.
|
||||||
|
Certificate is saved at: /etc/letsencrypt/live/mail.example.org/fullchain.pem
|
||||||
|
Key is saved at: /etc/letsencrypt/live/mail.example.org/privkey.pem
|
||||||
|
This certificate expires on 2025-11-22.
|
||||||
|
These files will be updated when the certificate renews.
|
||||||
|
Certbot has set up a scheduled task to automatically renew this certificate in the background.
|
||||||
|
|
||||||
|
Deploying certificate
|
||||||
|
Successfully deployed certificate for mail.example.org to /etc/apache2/sites-available/000-default-le-ssl.conf
|
||||||
|
Congratulations! You have successfully enabled HTTPS on https://mail.example.org
|
||||||
|
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
If you like Certbot, please consider supporting our work by:
|
||||||
|
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
|
||||||
|
* Donating to EFF: https://eff.org/donate-le
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click here to learn which files have been created…</summary>
|
||||||
|
|
||||||
|
In /etc/letsencrypt/live/**mail.example.org** you will find a couple of files now:
|
||||||
|
|
||||||
|
- cert.pem: the certificate file
|
||||||
|
- chain.pem: the _chaining_ or _intermediate_ certificate. This certificate provides information how the LetsEncrypt
|
||||||
|
certificates are linked to other known certificate authorities. It is generally a good idea to always send this
|
||||||
|
certificate along with your own for clients who may not know LetsEncrypt properly yet.
|
||||||
|
- fullchain.pem: this file contains a concatenation of the _cert.pem_ and the _chain.pem_. This is the preferred file to
|
||||||
|
use when a piece of software asks where to find the _certificate_.
|
||||||
|
- privkey.pem: the private key file. Keep it secret.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Renewal
|
||||||
|
|
||||||
|
Your new certificate is only valid for 3 months. Fortunately the renewal is happening automatically. A _systemd timer_
|
||||||
|
checks your certificate frequently and triggers a renewal one month before it expires.
|
||||||
|
|
||||||
|
These commands will show you the definition and status of the timer and the renewal service:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl status certbot.timer
|
||||||
|
systemctl cat certbot.timer
|
||||||
|
systemctl status certbot.service
|
||||||
|
systemctl cat certbot.service
|
||||||
|
```
|
||||||
|
|
||||||
|
_Systemd timers_ are similar to cron jobs. They require more work to set up but have some nice features and make you
|
||||||
|
look cooler.
|
||||||
|
|
||||||
|
It is important that services that use the certificate load the new certificate after an automatic renewal. This shell
|
||||||
|
command will create a file `/etc/letsencrypt/cli.ini` that handles it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo > /etc/letsencrypt/cli.ini "post-hook = systemctl reload postfix dovecot apache2"
|
||||||
|
```
|
||||||
|
|
||||||
|
Perfect. You won't have to worry about the certificate again.
|
||||||
|
|
@ -15,17 +15,7 @@ import { Steps } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
import Box from "../../../components/Box.astro";
|
import Box from "../../../components/Box.astro";
|
||||||
|
|
||||||
import StepList from "../../../components/StepList.astro";
|
import StepListReceive from "../../../components/StepListReceive.astro";
|
||||||
|
|
||||||
<StepList
|
|
||||||
steps={[
|
|
||||||
{ title: "Install Astro", description: "Set up your project." },
|
|
||||||
{ title: "Add Starlight", description: "Install the theme." },
|
|
||||||
{ title: "Customize", description: "Adjust settings to your needs." },
|
|
||||||
{ title: "Deploy", description: "Publish to production." },
|
|
||||||
]}
|
|
||||||
currentStep={2}
|
|
||||||
/>
|
|
||||||
|
|
||||||
I have created a slideshow to help you understand the process of receiving an email from a remote mail server. Use the
|
I have created a slideshow to help you understand the process of receiving an email from a remote mail server. Use the
|
||||||
arrows to navigate between slides:
|
arrows to navigate between slides:
|
||||||
|
|
@ -38,29 +28,17 @@ arrows to navigate between slides:
|
||||||
))}
|
))}
|
||||||
</Splide>
|
</Splide>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Let's tackle the receiving of emails step by step:
|
||||||
|
|
||||||
## DNS
|
## DNS
|
||||||
|
|
||||||
<ul class="steps steps-vertical lg:steps-horizontal">
|
<StepListReceive currentStep={1} />
|
||||||
<li class="step step-primary">Register</li>
|
|
||||||
<li class="step step-primary">Choose plan</li>
|
|
||||||
<li class="step">Purchase</li>
|
|
||||||
<li class="step">Receive Product</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<Box icon='check-list'>
|
As shown in the slideshow, you need to have proper DNS records set up. Say that you want to receive emails for the
|
||||||
<Steps>
|
`example.com` domain. If some other mail server on the internet wants to send an email to your server, it looks for two
|
||||||
|
records:
|
||||||
1. **DNS is used to find your mail server. First the MX record. Then the A and/or AAAA record.**
|
|
||||||
1. Postfix receives emails using SMTP (the Simple Mail Transport Protocol)
|
|
||||||
1. MariaDB stores information about your domains and mail users
|
|
||||||
1. rspamd checks if it is spam (optional)
|
|
||||||
1. Dovecot saves it to disk
|
|
||||||
|
|
||||||
</Steps>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
Let's begin with setting up the DNS records. Say that you want to receive emails for the `example.com` domain. If some
|
|
||||||
other mail server on the internet wants to send an email to your server, it looks for two records:
|
|
||||||
|
|
||||||
- **MX record (`example.com`)**
|
- **MX record (`example.com`)**
|
||||||
The MX (Mail Exchange) record tells other mail servers _where to deliver emails_ for the domain `example.com`.
|
The MX (Mail Exchange) record tells other mail servers _where to deliver emails_ for the domain `example.com`.
|
||||||
|
|
@ -101,20 +79,46 @@ If possible, also add a PTR record that makes the IP addresses point back to the
|
||||||
servers may be more likely so flag your sent emails as spam if you do not have a PTR record. Preferable the forward
|
servers may be more likely so flag your sent emails as spam if you do not have a PTR record. Preferable the forward
|
||||||
(A/AAAA) and reverse (PTR) records match.
|
(A/AAAA) and reverse (PTR) records match.
|
||||||
|
|
||||||
<div class="scroll-trigger" data-slide="5">
|
<Aside icon="approve-check-circle" title="Test it">
|
||||||
dingdong
|
|
||||||
</div>
|
Use the `host` command to check your domain's MX record.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$> host -t mx example.com
|
||||||
|
example.com mail is handled by 10 smtp.example.com.
|
||||||
|
```
|
||||||
|
|
||||||
|
Then check if that result resolves to an IP address:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$> host smtp.example.com
|
||||||
|
smtp.example.com has address 49.13.89.249
|
||||||
|
smtp.example.com has IPv6 address 2a01:4f8:c012:62d1::1
|
||||||
|
```
|
||||||
|
|
||||||
|
Preferably the IP addresses should resolve back to the mail server's name:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$> host 49.13.89.249
|
||||||
|
249.89.13.49.in-addr.arpa domain name pointer smtp.example.com.
|
||||||
|
$> host 2a01:4f8:c012:62d1::1
|
||||||
|
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.d.2.6.2.1.0.c.8.f.4.0.1.0.a.2.ip6.arpa domain name pointer smtp.example.com.
|
||||||
|
```
|
||||||
|
|
||||||
|
</Aside>
|
||||||
|
|
||||||
## Postfix
|
## Postfix
|
||||||
|
|
||||||
|
<StepListReceive currentStep={2} />
|
||||||
|
|
||||||
Now that other mail servers can locate your mail server, the next step is to make sure it can receive and process
|
Now that other mail servers can locate your mail server, the next step is to make sure it can receive and process
|
||||||
incoming emails. This task is handled by Postfix, which communicates using the SMTP protocol.
|
incoming emails. This task is handled by Postfix, which communicates using the SMTP protocol.
|
||||||
|
|
||||||
Postfix knows if an email address is valid by checking for…
|
Postfix knows if an email address is valid by checking for…
|
||||||
|
|
||||||
- Virtual Domains (am I responsible for this domain?)
|
- **Virtual Domains** (am I responsible for this domain?)
|
||||||
- Virtual Aliases (do I have to redirect this address to another address?)
|
- **Virtual Aliases** (do I have to redirect this address to another address?)
|
||||||
- Virtual Mailboxes (do I have a valid mailbox to store the email to?)
|
- **Virtual Mailboxes** (do I have a valid mailbox to store the email to?)
|
||||||
|
|
||||||
### Virtual Domains
|
### Virtual Domains
|
||||||
|
|
||||||
|
|
@ -128,9 +132,9 @@ _mapping_. Consider it as a _question_ and an _answer_.
|
||||||
If the information is **not** available you just get no answer:
|
If the information is **not** available you just get no answer:
|
||||||
|
|
||||||
- Question: is `example.net` one of our virtual domains?
|
- Question: is `example.net` one of our virtual domains?
|
||||||
- Answer: (no answer)
|
- Answer: _(no answer)_
|
||||||
|
|
||||||
So Postfix hast to ask MariaDB that question:
|
In SQL language Postfix has to ask MariaDB the question:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT "yes" FROM virtual_domains WHERE name='example.net'
|
SELECT "yes" FROM virtual_domains WHERE name='example.net'
|
||||||
|
|
@ -166,9 +170,9 @@ and _answer_ game goes like this:
|
||||||
Postfix would then check if there are further aliases:
|
Postfix would then check if there are further aliases:
|
||||||
|
|
||||||
- Question: is there an alias for `john@example.org`?
|
- Question: is there an alias for `john@example.org`?
|
||||||
- Answer: (no answer)
|
- Answer: _(no answer)_
|
||||||
|
|
||||||
Apparently, there aren't any. So the email will go to `john@example.org`.
|
Apparently, there aren't any more. So the email will go to `john@example.org`.
|
||||||
|
|
||||||
Run this code to create the appropriate mapping file:
|
Run this code to create the appropriate mapping file:
|
||||||
|
|
||||||
|
|
@ -202,8 +206,9 @@ Just the same story as with virtual domains. The database returns `1` if there i
|
||||||
that specific email address.
|
that specific email address.
|
||||||
|
|
||||||
Actually the _answer_ is usually not `1` but the path to the mailbox on disk. But that applies only if Postfix would be
|
Actually the _answer_ is usually not `1` but the path to the mailbox on disk. But that applies only if Postfix would be
|
||||||
saving the email. In our setup the email will be passed on to Dovecot in a later step. Dovecot will then handle the
|
saving the email to disk on its own. In our setup the email will be passed on to Dovecot in a later step. Dovecot will
|
||||||
files on disk. Postfix just has to know is whether a mailbox exists. And that's why any _answer_ is sufficient here.
|
then handle the files on disk. Postfix just has to know is whether a mailbox exists. And that's why **any** _answer_ is
|
||||||
|
sufficient here.
|
||||||
|
|
||||||
### Tell Postfix
|
### Tell Postfix
|
||||||
|
|
||||||
|
|
@ -224,7 +229,7 @@ to restart Postfix.
|
||||||
The last two lines set the owner of the config files (_user=root, group=postfix_) and make sure that _others_ have no
|
The last two lines set the owner of the config files (_user=root, group=postfix_) and make sure that _others_ have no
|
||||||
access. After all a database password is found in these files.
|
access. After all a database password is found in these files.
|
||||||
|
|
||||||
### Test it
|
<Aside icon="approve-check-circle" title="Test it">
|
||||||
|
|
||||||
Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_):
|
Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_):
|
||||||
|
|
||||||
|
|
@ -243,3 +248,5 @@ john@example.org
|
||||||
```
|
```
|
||||||
|
|
||||||
If you get anything else, please check those three config files. Perhaps the database password is wrong?
|
If you get anything else, please check those three config files. Perhaps the database password is wrong?
|
||||||
|
|
||||||
|
</Aside>
|
||||||
|
|
|
||||||
|
|
@ -7,197 +7,229 @@ sidebar:
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Aside } from "@astrojs/starlight/components";
|
import { Aside } from "@astrojs/starlight/components";
|
||||||
|
import StepListReceive from "../../../components/StepListReceive.astro";
|
||||||
|
|
||||||
In the previous chapter you have prepared Postfix to receive emails. As shown in the slideshow, Postfix hands incoming
|
## About LMTP
|
||||||
emails over to Dovecot which in turn saves it to a mailbox on disk.
|
|
||||||
|
|
||||||
So:
|
<StepListReceive currentStep={3} />
|
||||||
|
|
||||||
- DNS is used to find your mail server. First the MX record. Then the A and/or AAAA record.
|
As explained in the previous section, Postfix speaks SMTP and receives the email from the internet. Postfix could even
|
||||||
- Postfix receives emails using SMTP (the Simple Mail Transport Protocol)
|
save the email to a mailbox on disk. But instead we will use Dovecot for the final delivery. Actually Dovecot's main
|
||||||
- MariaDB stores information about your domains and mail users
|
purpose is to let users fetch their email using the IMAP protocol. But it provides additional features we can use as
|
||||||
- rspamd checks if it is spam
|
well.
|
||||||
- Dovecot saves it to disk
|
|
||||||
|
|
||||||
## DNS
|
So we need tell Postfix to hand over the incoming email to Dovecot. The communication between Postfix and Dovecot will
|
||||||
|
happen using LMTP – the [local mail transfer protocol](https://en.wikipedia.org/wiki/Local_Mail_Transfer_Protocol).
|
||||||
|
LMTP is a variant of SMTP with fewer features. It is meant for email communication between internal services that trust
|
||||||
|
each other.
|
||||||
|
|
||||||
Let's begin with setting up the DNS records. Say that you want to receive emails for the `example.com` domain. If some
|
In this chapter we will mainly configure Dovecot so that it knows how to deliver incoming emails.
|
||||||
other mail server on the internet wants to send an email to your server, it looks for two records:
|
|
||||||
|
|
||||||
- **MX record (`example.com`)**
|
## Setting up the vmail directory
|
||||||
The MX (Mail Exchange) record tells other mail servers _where to deliver emails_ for the domain `example.com`.
|
|
||||||
Instead of pointing directly to an IP address, it points to a **hostname** (for example, `smtp.example.com`).
|
|
||||||
MX records also include a **priority value**. If multiple mail servers are listed, the one with the lowest priority
|
|
||||||
number is tried first. Others are used as fallback if the first is unavailable.
|
|
||||||
|
|
||||||
- **A / AAAA record (`smtp.example.com`)**
|
On your disk all the mailboxes will live in the directory `/var/vmail`. To separate the mailboxes from the rest of your
|
||||||
The hostname specified in the MX record (`smtp.example.com`) must resolve to an IP address so that other mail servers
|
system, let's create a new user and group that will own the mailboxes:
|
||||||
know how to reach it.
|
|
||||||
- An **A record** maps the hostname to an IPv4 address.
|
|
||||||
- An **AAAA record** maps the hostname to an IPv6 address.
|
|
||||||
|
|
||||||
Once resolved, the sending mail server connects to that IP address on **TCP port 25**, which is the standard port for
|
```sh
|
||||||
email delivery (SMTP).
|
groupadd --system vmail
|
||||||
|
useradd --system --gid vmail vmail
|
||||||
|
mkdir -p /var/vmail
|
||||||
|
chown -R vmail:vmail /var/vmail
|
||||||
|
```
|
||||||
|
|
||||||
<Aside>
|
## Dovecot configuration
|
||||||
|
|
||||||
If no MX record is found, the remote mail server will instead directly look for an A or AAAA record of `example.com`.
|
The configuration files for Dovecot are found in `/etc/dovecot/conf.d`. All these files are loaded by Dovecot. This is
|
||||||
|
done by this magical line at the end of the `/etc/dovecot/dovecot.conf` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
!include conf.d/*.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
It loads all files in `/etc/dovecot/conf.d/` that end in “.conf” in alphanumerical order. Let's edit a couple of files
|
||||||
|
for our purpose.
|
||||||
|
|
||||||
|
### 10-auth.conf
|
||||||
|
|
||||||
|
The `/etc/dovecot/conf.d/10-auth.conf` file is dealing with authentication. At the end of this file you will find a list
|
||||||
|
of authentication backends that Dovecot ships with. By default it will use system users (those from /etc/passwd). But we
|
||||||
|
want to use the MariaDB database backend so go ahead and change this block to:
|
||||||
|
|
||||||
|
```
|
||||||
|
#!include auth-system.conf.ext
|
||||||
|
!include auth-sql.conf.ext
|
||||||
|
#!include auth-ldap.conf.ext
|
||||||
|
#!include auth-passwdfile.conf.ext
|
||||||
|
#!include auth-checkpassword.conf.ext
|
||||||
|
#!include auth-static.conf.ext
|
||||||
|
```
|
||||||
|
|
||||||
|
Every line starting with `#` is disabled. Only lines starting with `!` are considered.
|
||||||
|
|
||||||
|
### 10-mail.conf
|
||||||
|
|
||||||
|
Edit this file at `/etc/dovecot/conf.d/10-mail.conf`. It contains many settings regarding mailboxes, like where they are
|
||||||
|
located, who owns them and what is their layout? Set this:
|
||||||
|
|
||||||
|
```
|
||||||
|
mail_driver = maildir
|
||||||
|
mail_home = /var/vmail/%{user | domain}/%{user | username}
|
||||||
|
mail_path = ~/Maildir
|
||||||
|
```
|
||||||
|
|
||||||
|
This looks more sophisticated than with versions of Dovecot before 2.4. What it means:
|
||||||
|
|
||||||
|
- **mail_driver**: Mailboxes are stored in
|
||||||
|
[maildir](https://doc.dovecot.org/2.4.1/core/config/mailbox_formats/maildir.html#maildir-mailbox-format) format.
|
||||||
|
- **mail_home**: Every mail user gets their own _home_ directory in `/var/vmail/DOMAIN/USER`. The `user` variable
|
||||||
|
contains the email address like `john@example.org`. The expression `user | domain` splits off the _domain_ part from
|
||||||
|
the email address. Similarly `user | username` takes the part before the `@` sign.
|
||||||
|
- **mail_path**: Within the _home_ directory the actual mailbox will live in a subdirectory called `Maildir`. The reason
|
||||||
|
is that we will store other data in the user's _home_ directory as well that should not conflict with the mailbox
|
||||||
|
directory.
|
||||||
|
|
||||||
|
### 10-master.conf
|
||||||
|
|
||||||
|
This configuration file at `/etc/dovecot/conf.d/10-master.conf` deals with Dovecot's services.
|
||||||
|
|
||||||
|
So most settings are sane here and do not have to be changed. However one change is required in the “service auth”
|
||||||
|
section because we want Postfix to allow Dovecot as an authentication service. Make it look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Postfix smtp-auth
|
||||||
|
unix_listener /var/spool/postfix/private/auth {
|
||||||
|
mode = 0660
|
||||||
|
user = postfix
|
||||||
|
group = postfix
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That way Dovecot will put a communication socket into `/var/spool/postfix/private/auth`. It will allow Postfix to
|
||||||
|
authenticate your users which is relevant later when we set up _relaying_.
|
||||||
|
|
||||||
|
### 10-ssl.conf
|
||||||
|
|
||||||
|
TODO: verify
|
||||||
|
|
||||||
|
Earlier in this guide you created both a key and a certificate file to encrypt the communication with POP3, IMAPs and
|
||||||
|
HTTPS between the users and your mail server. You need to tell Dovecot where to find these files:
|
||||||
|
|
||||||
|
```
|
||||||
|
ssl\_cert = \</etc/letsencrypt/live/webmail.example.org/fullchain.pem
|
||||||
|
ssl\_key = \</etc/letsencrypt/live/webmail.example.org/privkey.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
And enforce TLS encryption by setting:
|
||||||
|
|
||||||
|
```
|
||||||
|
ssl = required
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [Dovecot documentation on SSL encryption](https://doc.dovecot.org/admin_manual/ssl/) for more information.
|
||||||
|
|
||||||
|
Next let’s take a look at how Dovecot knows about users and their passwords:
|
||||||
|
|
||||||
|
### auth-sql.conf.ext
|
||||||
|
|
||||||
|
Dovecot reads the `auth-sql.conf.ext` which defines how to find user information in your database. Open the file. There
|
||||||
|
are two sections:
|
||||||
|
|
||||||
|
- userdb: where to find a user’s mailbox in the file system
|
||||||
|
- passdb: where to find the user’s hashed password
|
||||||
|
|
||||||
|
By default Dovecot will run two queries at your database. One for the _userdb_ that gets information like the user ID,
|
||||||
|
group ID, home directory and quota. And another for the _passdb_ that gets the hashed password.
|
||||||
|
|
||||||
|
The “userdb” section already reads:
|
||||||
|
|
||||||
|
```
|
||||||
|
userdb {
|
||||||
|
driver = sql
|
||||||
|
args = /etc/dovecot/dovecot-sql.conf.ext
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see Dovecot uses an SQL database lookup to get that information. And it refers to the dovecot-sql.conf.ext
|
||||||
|
file for more information. Let’s see…
|
||||||
|
|
||||||
|
## /etc/dovecot/dovecot-sql.conf.ext
|
||||||
|
|
||||||
|
(This configuration file is one level up and not in “conf.d”.)
|
||||||
|
|
||||||
|
You will find this file well documented although all configuration directives are commented out. Add these lines at the
|
||||||
|
bottom of the file:
|
||||||
|
|
||||||
|
```
|
||||||
|
driver = mysql
|
||||||
|
|
||||||
|
connect = \
|
||||||
|
host=127.0.0.1 \
|
||||||
|
dbname=mailserver \
|
||||||
|
user=mailserver \
|
||||||
|
password=x893dNj4stkHy1MKQq0USWBaX4ZZdq
|
||||||
|
|
||||||
|
user_query = SELECT email as user, \
|
||||||
|
concat('*:bytes=', quota) AS quota_rule, \
|
||||||
|
'/var/vmail/%d/%n' AS home, \
|
||||||
|
5000 AS uid, 5000 AS gid \
|
||||||
|
FROM virtual_users WHERE email='%u'
|
||||||
|
|
||||||
|
password_query = SELECT password FROM virtual_users WHERE email='%u'
|
||||||
|
|
||||||
|
iterate_query = SELECT email AS user FROM virtual_users
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip" title="Backslashes">
|
||||||
|
Ending a line with a backslash (\) means that it is continued on the next line. It keeps the configuration more
|
||||||
|
readable when it is split over multiple lines.
|
||||||
</Aside>
|
</Aside>
|
||||||
|
|
||||||
<Aside type="caution">
|
What these lines mean:
|
||||||
|
|
||||||
The MX record must point to a hostname. It **must not** point to an IP address directly.
|
- driver: the kind of database. MariaDB is the same kind as MySQL.
|
||||||
|
- connect: where to find the MySQL database and how to access it (username, password)
|
||||||
|
- user_query: an SQL query that returns the user name (=the email address), the quota, the home directory, user ID and
|
||||||
|
group ID.
|
||||||
|
- password_query: this SQL query just gets the password hash from the database
|
||||||
|
- iterate_query: ‘doveadm’ uses this query to get a list of all users. That allows you to use the “doveadm user ‘\*'”
|
||||||
|
command later.
|
||||||
|
|
||||||
</Aside>
|
The _user_query_ gets several pieces of information from the database. Let’s look at it one by one:
|
||||||
|
|
||||||
In zone syntax you would create something like:
|
- email AS user
|
||||||
|
It gets the the _email_ field from the database which corresponds to the user name. Dovecot expects it in the _user_
|
||||||
|
field so we set an alias to _“user”._
|
||||||
|
- userdb_quota_rule
|
||||||
|
This is the user’s quota in bytes. Think of it as the maximum possible space on disk that the user can occupy. As
|
||||||
|
[documented](https://doc.dovecot.org/configuration_manual/quota/#per-user-quota) Dovecot expects the quota in a
|
||||||
|
special format like “\*:bytes=10000” if the user should not be able to store more than 10,000 bytes. That’s why we
|
||||||
|
begin the string with ‘\*:bytes=’.
|
||||||
|
- userdb_home
|
||||||
|
This leads to the directory where all emails and various control files for this user are located. The placeholder
|
||||||
|
‘%d’ replaces the domain and ‘%n’ the user part. So for John that makes it “/var/vmail/example.org/john”.
|
||||||
|
- userdb*uid and userdb_gid
|
||||||
|
Those are the user ID and group ID of \_vmail* user – 5000 for both. Dovecot uses it to set the permissions of files
|
||||||
|
it creates. As all users share the same system user “vmail” this is just a static number.s
|
||||||
|
|
||||||
|
## Fix permissions
|
||||||
|
|
||||||
|
Make sure that only root can access the SQL configuration file so nobody else is reading your database access passwords:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ IN MX 10 smtp
|
chown root:root /etc/dovecot/dovecot-sql.conf.ext
|
||||||
smtp IN A 100.64.17.3
|
chmod go= /etc/dovecot/dovecot-sql.conf.ext
|
||||||
smtp IN A fd7a:115c:a1e0::17
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If possible, also add a PTR record that makes the IP addresses point back to the name `smtp.example.com`. Other mail
|
Restart Dovecot from the shell:
|
||||||
servers may be more likely so flag your sent emails as spam if you do not have a PTR record. Preferable the forward
|
|
||||||
(A/AAAA) and reverse (PTR) records match.
|
|
||||||
|
|
||||||
## Postfix
|
|
||||||
|
|
||||||
Now that other mail servers can locate your mail server, the next step is to make sure it can receive and process
|
|
||||||
incoming emails. This task is handled by Postfix, which communicates using the SMTP protocol.
|
|
||||||
|
|
||||||
Postfix knows if an email address is valid by checking for…
|
|
||||||
|
|
||||||
- Virtual Domains (am I responsible for this domain?)
|
|
||||||
- Virtual Aliases (do I have to redirect this address to another address?)
|
|
||||||
- Virtual Mailboxes (do I have a valid mailbox to store the email to?)
|
|
||||||
|
|
||||||
### Virtual Domains
|
|
||||||
|
|
||||||
There are different ways for Postfix to get that information. From text files, LDAP, MongoDB or PostgreSQL. Or via
|
|
||||||
MariaDB – which is what we will use. But whatever kind of data source you configure, Postfix needs the information as a
|
|
||||||
_mapping_. Consider it as a _question_ and an _answer_.
|
|
||||||
|
|
||||||
- Question: is `example.org` one of our virtual domains?
|
|
||||||
- Answer: yes
|
|
||||||
|
|
||||||
If the information is **not** available you just get no answer:
|
|
||||||
|
|
||||||
- Question: is `example.net` one of our virtual domains?
|
|
||||||
- Answer: (no answer)
|
|
||||||
|
|
||||||
So Postfix hast to ask MariaDB that question:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT "yes" FROM virtual_domains WHERE name='example.net'
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the following code in your shell to create a configuration file creating that mapping:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cat > /etc/postfix/virtual-mailbox-domains.cf << EOF
|
|
||||||
user = mailserver
|
|
||||||
password = SECOND-PASSWORD-HERE
|
|
||||||
hosts = 127.0.0.1
|
|
||||||
dbname = mailserver
|
|
||||||
query = SELECT 1 FROM virtual_domains WHERE name='%s'
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
This has created a configuration file called `/etc/postfix/virtual-mailbox-domains.cf`. The `user`, `password`, `hosts`
|
|
||||||
and `dbname` definitions tell Postfix how to connect to the database. And the `query` asks the _question_ if a certain
|
|
||||||
domain is present in the database table `virtual_domains`. Using `SELECT 1` we return just the number 1 if such an entry
|
|
||||||
was found. In fact it does not matter what we return. Any _answer_ is fine.
|
|
||||||
|
|
||||||
Don't forget to replace the `SECOND-PASSWORD-HERE` by your own password for the `mailserver` user.
|
|
||||||
|
|
||||||
### Virtual Aliases
|
|
||||||
|
|
||||||
Now let's deal with aliases. As shown earlier an alias redirects an email to another email address. So the _question_
|
|
||||||
and _answer_ game goes like this:
|
|
||||||
|
|
||||||
- Question: is there an alias for `jack@example.org`?
|
|
||||||
- Answer: `john@example.org`
|
|
||||||
|
|
||||||
Postfix would then check if there are further aliases:
|
|
||||||
|
|
||||||
- Question: is there an alias for `john@example.org`?
|
|
||||||
- Answer: (no answer)
|
|
||||||
|
|
||||||
Apparently, there aren't any. So the email will go to `john@example.org`.
|
|
||||||
|
|
||||||
Run this code to create the appropriate mapping file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cat > /etc/postfix/virtual-alias-maps.cf << EOF
|
|
||||||
user = mailserver
|
|
||||||
password = SECOND-PASSWORD-HERE
|
|
||||||
hosts = 127.0.0.1
|
|
||||||
dbname = mailserver
|
|
||||||
query = SELECT destination FROM virtual_aliases WHERE source='%s'
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
The query now gets all the `destination` email addresses from the database for a certain `source` email address.
|
|
||||||
|
|
||||||
### Virtual Mailboxes
|
|
||||||
|
|
||||||
The last mapping we need is a query to find valid mailboxes. Without further ado:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cat > /etc/postfix/virtual-mailbox-maps.cf << EOF
|
|
||||||
user = mailserver
|
|
||||||
password = SECOND-PASSWORD-HERE
|
|
||||||
hosts = 127.0.0.1
|
|
||||||
dbname = mailserver
|
|
||||||
query = SELECT 1 FROM virtual_mailboxes WHERE email='%s'
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
Just the same story as with virtual domains. The database returns `1` if there is a `virtual_mailboxes` record matching
|
|
||||||
that specific email address.
|
|
||||||
|
|
||||||
Actually the _answer_ is usually not `1` but the path to the mailbox on disk. But that applies only if Postfix would be
|
|
||||||
saving the email. In our setup the email will be passed on to Dovecot in a later step. Dovecot will then handle the
|
|
||||||
files on disk. Postfix just has to know is whether a mailbox exists. And that's why any _answer_ is sufficient here.
|
|
||||||
|
|
||||||
### Tell Postfix
|
|
||||||
|
|
||||||
You have created the config files for the three mappings. Now you just need to tell Postfix to use them:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
|
||||||
postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
|
||||||
postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
|
||||||
|
|
||||||
chown root:postfix /etc/postfix/*.cf
|
|
||||||
chmod o= /etc/postfix/*.cf
|
|
||||||
```
|
|
||||||
|
|
||||||
`postconf` is a command that changes configuration in `/etc/postfix/main.cf` and applies them instantly. You don't have
|
|
||||||
to restart Postfix.
|
|
||||||
|
|
||||||
The last two lines set the owner of the config files (_user=root, group=postfix_) and make sure that _others_ have no
|
|
||||||
access. After all a database password is found in these files.
|
|
||||||
|
|
||||||
### Test it
|
|
||||||
|
|
||||||
Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
postmap -q example.org mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
|
||||||
postmap -q jack@example.org mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
|
||||||
postmap -q john@example.org mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
|
||||||
```
|
|
||||||
|
|
||||||
These three commands use the three mapping files (`*.cf`) to query your three database tables. The result should show:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
1
|
systemctl restart dovecot
|
||||||
john@example.org
|
|
||||||
1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you get anything else, please check those three config files. Perhaps the database password is wrong?
|
Look at your /var/log/mail.log logfile. You should see:
|
||||||
|
|
||||||
|
```
|
||||||
|
... Dovecot v2.3.13 (f79e8e7e4) starting up for imap, lmtp, sieve, pop3 (core dumps disabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
If you get any error messages please double-check your configuration files.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue