How To: Install an E-Mail Server on Fedora 43
Last updated: 2026-02-01
PostgreSQL will be used to store data about mailboxes and aliases.
This guide uses Postfix v3.10.3 and Dovecot v2.4.1.
Install NGINX if not already installed (assumed for Let's Encrypt certificate retrieval and Thunderbird Autoconfig):
sudo dnf install nginx && sudo systemctl enable --now nginx
OpenDKIM (DomainKeys Identified Mail)
To sign outgoing e-mail and verify incoming e-mail.
sudo dnf install opendkim opendkim-tools
In file /etc/opendkim.conf set the domain and the mode to sign and
verify:
Domain TTexample.org
Mode sv
Generate the signing key for the domain:
cd /etc/opendkim/keys
sudo opendkim-genkey -d TTexample.org
sudo chown opendkim:opendkim /etc/opendkim/keys/*
Start the OpenDKIM service and make it start with the system:
sudo systemctl enable --now opendkim
DNS
Create the following record, replacingGET_ME_FROM_DEFAULTTXT by what comes after p=
in
/etc/opendkim/keys/default.txt:
default._domainkey.TTexample.org. 1 IN TXT "v=DKIM1;t=s;h=sha256;k=rsa;p=GET_ME_FROM_DEFAULTTXT"
SpamAssassin
SpamAssassin gives a spam score to incoming e-mail and adds [SPAM] to the e-mail subject if it's above a certain threshold.
sudo dnf install spamassassin
sudo systemctl enable --now spamassassin
sudo useradd -d /var/spool/spamassassin -s /bin/false spamassassin
Postfix
DNS
Create those records in your zone:
smtp.TTexample.org. 1 IN CNAME server1.exampleTT.org.
TTexample.org. 1 IN MX 100 smtp.TTexample.org.
default._bimi.TTexample.org. 1 IN TXT "v=BIMI1;l=https://www.TTexample.org/images/logo.svg"
_dmarc.TTexample.org. 1 IN TXT "v=DMARC1; p=reject; rua=mailto:postmaster@TTexample.org;"
TTexample.org. 1 IN TXT "v=spf1 mx -all"
The BIMI record is used to display a brand logo in Gmail and other e-mail clients.
Thev=spf1 mx -all SPF record will make receiving SMTP servers reject e-mail for the domain
if it is not coming from the servers in your MX records or DKIM verification fails.
TLS Certificate
sudo certbot certonly --nginx -d smtp.TTexample.org
Configuration
In the following, "virtual mailbox" means that mailboxes do not have an associated Unix system account. Accounts are instead stored in PostgreSQL.
Install the Postfix packages:
sudo dnf install postfix postfix-pgsql
Create the user and the directories which will contain the actual email data:
sudo useradd --home-dir /var/mail --user-group --uid 5000 vmail
sudo rm /var/mail/vmail
sudo mkdir -p /var/mail/vmail/TTexample.org
sudo chown -R vmail:vmail /var/mail/vmail
If you don't yet have a PostgreSQL cluster, set it up:
sudo dnf install postgresql-server && sudo postgresql-setup --initdb && sudo systemctl enable --now postgresql
Set up the database (make sure to change the postmaster mailbox password):
sudo -u postgres psql
CREATE DATABASE vmail;
\c vmail
CREATE TABLE addresses (
email TEXT NOT NULL PRIMARY KEY,
active BOOLEAN NOT NULL DEFAULT true,
passwd TEXT NOT NULL
);
CREATE TABLE aliases (
source TEXT NOT NULL PRIMARY KEY,
target TEXT NOT NULL
);
CREATE USER vmail WITH ENCRYPTED PASSWORD 'TTpostgres-password';
GRANT SELECT ON public.addresses TO vmail;
GRANT SELECT ON public.aliases TO vmail;
Ensure configuration in /var/lib/pgsql/data/pg_hba.conf is such that Postfix and Dovecot can connect through the Unix domain
socket with the password authentication method.
/etc/postfix/pgsql_mailboxes.cf:
user = vmail
password = TTpostgres-password
hosts = /run/postgresql
dbname = vmail
query = SELECT 1 FROM addresses WHERE email = '%s'
Create file /etc/postfix/pgsql_aliases.cf`:
user = vmail
password = TTpostgres-password
hosts = /run/postgresql
dbname = vmail
query = SELECT 1 FROM aliases WHERE source = '%s'
Edit file /etc/postfix/main.cf and merge configuration:
myhostname = server1.exampleTT.org
mydomain = TTexample.org
mydestination = localhost.$mydomain, localhost
inet_interfaces = all
smtpd_tls_cert_file = /etc/letsencrypt/live/smtp.TTexample.org/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/smtp.TTexample.org/privkey.pem
virtual_mailbox_base = /var/mail/vmail
virtual_mailbox_domains = TTexample.org
virtual_mailbox_maps = pgsql:/etc/postfix/pgsql_mailboxes.cf
virtual_alias_maps = pgsql:/etc/postfix/pgsql_aliases.cf
virtual_gid_maps = static:5000
virtual_uid_maps = static:5000
virtual_minimum_uid = 5000
virtual_transport = lmtp:unix:private/dovecot-lmtp
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
milter_default_action = accept
milter_protocol = 2
smtpd_milters = unix:/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock
In file /etc/postfix/master.cf,
uncomment smtp, submission and submissions (the services without 127.0.0.1, to listen on all interfaces!)
and their arguments (beware not to uncomment actual comments about the configuration!).
Then, add as an argument to service smtp:
-o content_filter=spamassassin
Then, add a new service:
spamassassin unix - n n - - pipe user=spamassassin argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
Start the Postfix systemd service and make it start with the system:
systemctl enable --now postfix
Dovecot
DNS
imap.TTexample.org. 1 IN CNAME server1.exampleTT.org.
TLS Certificate
sudo certbot certonly --nginx -d imap.TTexample.org
Configuration
sudo dnf install dovecot dovecot-pgsql dovecot-pigeonhole
openssl dhparam -out /etc/dovecot/dh.pem 4096
Edit file /etc/dovecot/dovecot.conf:
ssl_server {
cert_file = /etc/letsencrypt/live/imap.TTexample.org/fullchain.pem
key_file = /etc/letsencrypt/live/imap.TTexample.org/privkey.pem
dh_file = /etc/dovecot/dh.pem
}
mail_location = maildir:/var/mail/vmail/%{user | domain}/%{user | username}
mail_uid = 5000
mail_gid = 5000
mail_privileged_group = vmail
pgsql /run/postgresql {
parameters {
host=/run/postgresql
user=vmail
password=TTpostgres-password
dbname=vmail
}
}
passdb sql {
default_password_scheme = ARGON2ID
query = \
SELECT email as username, password FROM addresses WHERE email = '%{user}'
}
userdb sql {
query = \
SELECT 5000 AS uid, 5000 as gid, '/var/mail/vmail/%{user | domain}/%{user | username}' AS home \
FROM addresses WHERE email = '%{user}'
iterate_query = SELECT email AS user FROM addresses
}
Create file /etc/dovecot/conf.d/20-lmtp.conf:
protocol lmtp {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = %{mail_plugins} sieve
postmaster_address = postmaster@TTexample.org
}
Edit file /etc/dovecot/conf.d/10-master.conf:
service imap-login {
inet_listener imap {
port = 143
}
inet_listener imaps {
port = 993
ssl = yes
}
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
service auth {
#unix_listener auth-userdb {
# #mode = 0666
# #user =
# #group =
#}
# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
mode = 0666
}
}
Start the dovecot systemd service and make it start with the system:
systemctl enable --now dovecot
Setting up Thunderbird Autoconfig
DNS
Create the following record in your DNS zone:
autoconfig.TTexample.org. 1 IN CNAME server1.exampleTT.org.
Issue a new Let's Encrypt TLS certificate:
sudo certbot certonly --nginx -d autoconfig.TTexample.org
Create file: /etc/nginx/conf.d/autoconfig.TTexample.org.conf with the
following contents:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name autoconfig.TTexample.org;
# SSL
ssl_certificate /etc/letsencrypt/live/autoconfig.TTexample.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/autoconfig.TTexample.org/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/autoconfig.TTexample.org/chain.pem;
# logging
access_log /var/log/nginx/access.log combined buffer=512k flush=1m;
error_log /var/log/nginx/error.log warn;
root /var/www/autoconfig.TTexample.org;
include sts;
}
# HTTP redirect
server {
listen 80;
listen [::]:80;
server_name autoconfig.TTexample.org;
location / {
return 301 https://autoconfig.TTexample.org/$request_uri;
}
}
Create file: /var/www/autoconfig.TTexample.org/mail/config-v1.1.xml:
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
<emailProvider id="TTexample.org">
<domain>TTexample.org</domain>
<displayName>TTexample.org mail service</displayName>
<displayShortName>TTexample.org mail service</displayShortName>
<incomingServer type="imap">
<hostname>imap.TTexample.org</hostname>
<port>993</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
<outgoingServer type="smtp">
<hostname>smtp.TTexample.org</hostname>
<port>465</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
</emailProvider>
</clientConfig>
Reload NGINX's configuration with:
sudo systemctl reload nginx
Creating mailboxes
Obtain a password hash using doveadm pw -s ARGON2ID then insert the new address in the database:
sudo -u postgres psql vmail
INSERT INTO public.addresses (email, active, passwd) VALUES ('postmaster@TTexample.org', true, 'YOURHASHHERE');