re-ordering pages. adding dovecot page.

This commit is contained in:
Christoph Haas 2025-08-24 18:54:58 +02:00
parent 3a5508cdb5
commit 8a218dbe5a
5 changed files with 186 additions and 193 deletions

View file

@ -248,3 +248,5 @@ you should run before taking your mail server into production:
```sql
DELETE FROM mailserver.virtual_domains WHERE name='example.org';
```
TODO: move to final step

View file

@ -0,0 +1,108 @@
---
title: DNS records
lastUpdated: 2025-08-24
slug: ispmail-trixie/dns-records
sidebar:
order: 157
---
import { Aside } from "@astrojs/starlight/components";
import "@splidejs/splide/css";
import { Splide, SplideSlide } from "astro-splide";
import { Steps } from "@astrojs/starlight/components";
import Box from "../../../components/Box.astro";
import StepListReceive from "../../../components/StepListReceive.astro";
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:
<Splide>
{Array.from({ length: 24 }, (_, i) => i + 1).map((i) => (
<SplideSlide key={i}>
<img src={`/big-picture-receive/${String(i).padStart(2, "0")}.svg`} alt={`Slide ${i}`} />
</SplideSlide>
))}
</Splide>
---
Let's tackle the receiving of emails step by step:
## DNS
<StepListReceive currentStep={1} />
As shown in the slideshow, you need to have proper DNS records set up. 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`)**
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`)**
The hostname specified in the MX record (`smtp.example.com`) must resolve to an IP address so that other mail servers
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
email delivery (SMTP).
<Aside>
If no MX record is found, the remote mail server will instead directly look for an A or AAAA record of `example.com`.
</Aside>
<Aside type="caution">
The MX record must point to a hostname. It **must not** point to an IP address directly.
</Aside>
In zone syntax you would create something like:
```
@ IN MX 10 smtp
smtp IN A 100.64.17.3
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
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.
<Aside icon="approve-check-circle" title="Test it">
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>

View file

@ -3,9 +3,13 @@ title: TLS certificate
lastUpdated: 2025-08-24
slug: ispmail-trixie/tls-certificate
sidebar:
order: 140
order: 158
---
import StepListReceive from "../../../components/StepListReceive.astro";
<StepListReceive currentStep={2} />
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)
@ -13,7 +17,7 @@ Your mail server can't work properly without a valid TLS certificate. It will be
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
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

View file

@ -8,108 +8,13 @@ sidebar:
import { Aside } from "@astrojs/starlight/components";
import "@splidejs/splide/css";
import { Splide, SplideSlide } from "astro-splide";
import { Steps } from "@astrojs/starlight/components";
import Box from "../../../components/Box.astro";
import StepListReceive from "../../../components/StepListReceive.astro";
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:
<Splide>
{Array.from({ length: 24 }, (_, i) => i + 1).map((i) => (
<SplideSlide key={i}>
<img src={`/big-picture-receive/${String(i).padStart(2, "0")}.svg`} alt={`Slide ${i}`} />
</SplideSlide>
))}
</Splide>
---
Let's tackle the receiving of emails step by step:
## DNS
<StepListReceive currentStep={1} />
As shown in the slideshow, you need to have proper DNS records set up. 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`)**
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`)**
The hostname specified in the MX record (`smtp.example.com`) must resolve to an IP address so that other mail servers
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
email delivery (SMTP).
<Aside>
If no MX record is found, the remote mail server will instead directly look for an A or AAAA record of `example.com`.
</Aside>
<Aside type="caution">
The MX record must point to a hostname. It **must not** point to an IP address directly.
</Aside>
In zone syntax you would create something like:
```
@ IN MX 10 smtp
smtp IN A 100.64.17.3
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
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.
<Aside icon="approve-check-circle" title="Test it">
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
<StepListReceive currentStep={2} />
<StepListReceive currentStep={3} />
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.

View file

@ -109,127 +109,101 @@ 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:
Earlier in this guide you created both a key and a certificate file to encrypt the communication with IMAPS and HTTPS
between the users and your mail server. You need to tell Dovecot where to find these files. Also set
[ssl=required](https://doc.dovecot.org/2.4.1/core/config/ssl.html#how-to-specify-when-ssl-tls-is-required) to prevent
that someone sends their password without encryption.
```
ssl = required
ssl_server_cert_file = /etc/letsencrypt/live/mail.example.org/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/mail.example.org/privkey.pem
```
See the [Dovecot documentation on SSL encryption](https://doc.dovecot.org/admin_manual/ssl/) for more information.
Next lets 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 users mailbox in the file system
- passdb: where to find the users 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:
Let's tell Dovecot where to find information on an valid users (_userdb_) and their passwords (_passdb_). You can safely
replace the content of the `/etc/dovecot/conf.d/auth-sql.conf.ext` file by:
```
userdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
sql_driver = mysql
mysql /var/run/mysqld/mysqld.sock {
user = mailserver
password = 'SECOND-PASSWORD-HERE'
dbname = mailserver
}
userdb sql {
query = SELECT email as user FROM virtual_users WHERE email='%{user}'
iterate_query = SELECT email as user FROM virtual_users
}
passdb sql {
query = SELECT password FROM virtual_usrs where email='%{user}'
}
```
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. Lets 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>
What these lines mean:
- 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.
The _user_query_ gets several pieces of information from the database. Lets look at it one by one:
- 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 users 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. Thats 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
Dovecot can also read the path to a user's home directory and the user-ID and group-ID from the database. Our setup has
a fixed schema for the home directory (`/var/vmail/DOMAIN/USER`) and the user-ID and group-ID are always `vmail` and
`vmail`.
Make sure that only root can access the SQL configuration file so nobody else is reading your database access passwords:
```sh
chown root:root /etc/dovecot/conf.d/auth-sql.conf.ext
chmod go= /etc/dovecot/conf.d/auth-sql.conf.ext
```
chown root:root /etc/dovecot/dovecot-sql.conf.ext
chmod go= /etc/dovecot/dovecot-sql.conf.ext
```
## Restart Dovecot
Restart Dovecot from the shell:
```
```sh
systemctl restart dovecot
```
Look at your /var/log/mail.log logfile. You should see:
Check your logs:
```sh
journalctl -fu dovecot
```
You should see:
```
... Dovecot v2.3.13 (f79e8e7e4) starting up for imap, lmtp, sieve, pop3 (core dumps disabled)
dovecot[13309]: master: Dovecot v2.4.1-4 (7d8c0e5759) starting up for imap, lmtp, sieve (core dumps disabled)
systemd[1]: Started dovecot.service - Dovecot IMAP/POP3 email server.
```
If you get any error messages please double-check your configuration files.
<Aside type="tip" title="Why there is no /var/log/mail.log?">
In previous guides we used to check the log output from Postfix and Dovecot in a file called `/var/log/mail.log`.
However in the age of _systemd_ the output of the processes is written to systemd's _journal_ instead.
To read the complete logs of Dovecot, run (u=unit):
```sh
journalctl -u dovecot
```
To `tail -f` the logs (f=follow):
```sh
journalctl -fu dovecot
```
However on a busy server it will show the entire journal and then follow any changes. You may want to limit the output
to the last 10 lines:
```sh
journalctl -n 10 -fu dovecot
```
If you prefer the old way of having log files, just install the `rsyslog` package.
</Aside>