OpenSMTPD with sender dependent smart hosts


aptitude install opensmtpd


The main config resides in /etc/smtpd.conf. In this case it enables authenticated users to relay to several smart hosts depending on the sender of the email. Thus, users can send from with their non-local adresses and OpenSMTPD knows where to forward it:

# This is the smtpd server system-wide configuration file.
# See smtpd.conf(5) for more information.

# Certificates for SSL and TLS
pki domain.example certificate "/etc/letsencrypt/live/domain.example/cert.pem"
pki domain.example key "/etc/letsencrypt/live/domain.example/privkey.pem"

# Accept local mail
listen on localhost
listen on

# Accept external mail when authenticated over a secure channel, authenticated user are considered local!
listen on smtps pki domain.example auth

listen on smtps pki domain.example auth

#listen on port 465 tls pki domain.example auth

# If you edit the file, you have to run "smtpctl update table aliases"
table aliases file:/etc/aliases

# Forward mail for local accounts to their inbox
accept for local alias  deliver to mda "/usr/lib/dovecot/dovecot-lda -k"
accept from source for local alias  deliver to mda "/usr/lib/dovecot/dovecot-lda -k"

# Table for smtp-auth credentials, has to be created with "makemap /etc/smtpd_auth"
table smtp_auth_db db:/etc/smtpd_auth.db

# Accept mail from known mail accounts and forward them to their respective smarthosts using smtp-auth
accept from local sender some_user@mailprovider.example for any relay via tls+auth://some_user_mailprovider@mailprovider.example auth

accept for any relay

Authorization data is located in /etc/smtpd_auth:

some_user_mailprovider username:password

After changing it the database has to be updated via makemap /etc/smtpd_auth. Authorization databases need to be readable by the user opensmtpd and should be protected from other users:

chmod 600 /etc/smtpd.conf /etc/smtpd_auth*
chown root.root /etc/smtpd.conf /etc/smtpd_auth
chown opensmtpd.opensmtpd /etc/smtpd_auth.db

/etc/aliases had to be updated to redirect root mails to an actual user.

Dovecot & Getmail


aptitude install dovecot-imapd dovecot-sieve getmail4


Overwriting predefined values via /etc/dovecot/local.conf:

# SSL configuration
ssl = required
ssl_cert = </etc/letsencrypt/live/domain.example/cert.pem
ssl_key = </etc/letsencrypt/live/domain.example/privkey.pem

# Where to put received mail

# Enable zlib plugin globally for reading/writing
mail_plugins = $mail_plugins zlib

# Enable compression while saving
plugin {
  zlib_save_level = 9
  zlib_save = xz

# Enable sieve filtering for local delivery agent
protocol lda {
  mail_plugins = $mail_plugins sieve

# Restrict plain imap protocol
service imap-login {
  inet_listener imap {
    address = #Listen only on loopback
    #port = 0 #This disables it completely

# Fix stats-writer issue for Debian 10 (see
service stats {
  unix_listener stats-writer {
    mode = 0666

User defined filtering can be achieved via ~/.dovecot.sieve files.

Getmail configurations need the following destination section to forward mails to dovecot:

type = MDA_external
path = /usr/lib/dovecot/deliver

Collecting mails regularly can be done via cron:

@hourly getmails -q

Let’s Encrypt SSL Certificates

Installing certbot

aptitude install certbot

Running certbot

Certbot can use its own simple webserver when no webserver is installed so that certificates for mail servers etc. can be obtained:

certbot certonly --rsa-key-size 4096 --standalone \
  --pre-hook  "systemctl stop lighttpd opensmtpd dovecot" \
  --post-hook "cat /etc/letsencrypt/live/domain1.example/cert.pem /etc/letsencrypt/live/domain1.example/privkey.pem > /etc/letsencrypt/live/domain1.example/cert+privkey.pem && chmod 600 /etc/letsencrypt/archive/*/*.pem && systemctl start dovecot opensmtpd lighttpd" \
  -d domain1.example -d domain2...


The certbot packages provides its own entries for cron and systemd thus no own mechanism is needed. Hook mechanism can be later edited also in /etc/letsencrypt/renewal/domain1.example.conf

OpenSSH Daemon

Copy the current active ssh-agent key to the new host before disabling password authentication: ssh-copy-id user@hostname

The following values are changed from their default values in /etc/ssh/sshd_config:

  • Disable root login under any circumstances: PermitRootLogin no
  • Disable password authentification: PasswordAuthentication no
  • Check for inactive connections after 5 minutes: ClientAliveInterval 300
  • Drop inactive connections after 3 tries without response:ClientAliveCountMax 3
  • Allow only known users: AllowUsers user_a user_b user_c@host_x
  • Disable X11-Forwarding (why would you do that on a server?): X11Forwarding no

Install fail2ban to blacklist malicious accesses:

aptitude install fail2ban

Check in /etc/fail2ban/jail.d/defaults-debian.conf that SSH checking is enabled:

enabled = true

Server Setup with Debian 9 (Stretch)


During system setup tasksel will ask what to install. Choose only “Basic system tools” and “SSH server”.


Network card names have changed from eth* to more specific ones based on chipset and bus location. Beware: During installation one of the cards was named enp5s0 which had changed to enp6s0 after the first reboot. Thus networking was not working as expected.

NAT (IPv4)

General IP forwarding has to be enabled in /etc/sysctl.conf by uncommenting net.ipv4.ip_forward=1. Masquerading will be done via iptables and a hook in /etc/network/interfaces. Script for iptables with comments saved as /etc/network/00-nat:




# delete all existing rules.
iptables -F
iptables -t nat -F
iptables -X

# Always accept loopback traffic
iptables -A INPUT -i lo -j ACCEPT

# Masquerade.
iptables -t nat -A POSTROUTING -o $WAN -j MASQUERADE

# Allow outgoing connections from the LAN side.
iptables -A FORWARD -i $LAN -o $WAN -j ACCEPT

# Allow established connections, and those not coming from the outside
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i $WAN -o $LAN -m state --state ESTABLISHED,RELATED -j ACCEPT

# Don't forward left overs from the outside to the inside.
iptables -A FORWARD -i $WAN -o $LAN -j REJECT

This script will be called when the interface enp6s0 comes up in /etc/network/interfaces:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto enp6s0
iface enp6s0 inet static
        nameserver # if a local dns resolver is used
        up /etc/network/00-nat

auto enp3s0
iface enp3s0 inet static

At this point networking should be working after rebooting the machine. For machines which should be accessible from the outside a dynamic DNS service such as FreeDNS is handy. Account updates can be handled with a single crontab entry:

@hourly wget -O -<id> >/dev/null 2>&1

Other services

The machine is now providing internet access and can be remotely accessed via SSH. Other services are described in separate articles in preferred setup order:


Birthday reminder

Using birthday and cron can provides us with mails for upcoming events. Edit ~/.birthdays accordingly and add @daily birthday to the crontab.

RSS feeds via email

Using rss2email RSS feeds can be received without using an explicit RSS reader. For details see man r2e. Automation can be done via cron: @hourly r2e run Note for backups: Configuration files are located in ~/.local/share/rss2email.json and ~/.config/rss2email.cfg.

Remotely unlocking LUKS encrypted headless system

Define a fixed IP address for initramfs in /etc/initramfs-tools/initramfs.conf otherwise the kernel will try to get an address via DHCP (eth0 has to be adapted to the actual network card for Debian Stretch, such as enp3s0):


Install dropbear which automatically gets installed into the initramfs and implicitly updates the initramfs image:

apt-get install dropbear
  • For Debian Stretch/Buster this ends with the following warning: dropbear: WARNING: Invalid authorized_keys file, remote unlocking of cryptroot via SSH won't work! The reason is that dropbear no longer generates a default key anymore. Thus, copy your own public key into /etc/dropbear-initramfs/authorized_keys and run update-initramfs -u which should not show the warning above again. Afterwards copy your private key to a client machine. To finally  resolve the key-mismatch warnings, let dropbear listen on a different port by changing DROPBEAR_OPTIONS to "-p 2222 -s -j -k -I 30" in /etc/dropbear-initramfs/config. This prevents port forwarding, password logins, and sets an inactivity timeout. Additionally, set IFDOWN=none to keep any network settings. This later lets opensmtpd start cleanly on boot (otherwise it complains not finding its network device to listen on). Run update-initramfs -u to activate these settings for the next reboot.
  • For Debian Jessie copy the newly generated /etc/initramfs-tools/root/.ssh/id_rsa to a client machine.

Add the following on a clients .ssh/config file to avoid key-mismatch errors stemming from entries in known_hosts:

Host debian_unlock
  User root
  Port 22 # or whatever is used for dropbear
  IdentityFile path/to/previously/copied/private_key

To unlock the drives on boot-up ssh debian8_unlock into the system. ps will print the needed cryptsetup command:

# ps
124 root /sbin/dropbear
133 root {cryptroot} /bin/sh /scripts/local-top/cryptroot
138 root /lib/cryptsetup/askpass Please unlock disk sda5_crypt:
139 root /sbin/cryptsetup -T 1 open --type luks /dev/disk/by-uuid/715ac060-2efc-491a-9c96-6898e18c01bd sda5_crypt --key-file=-

Copy the cryptsetup command which will ask for your passphrase and unlocks your drive.

Debian Jessie: The askpass process still prevents booting up (as it is asking on the boot console which nobody sees) so kill -9 that one to continue the boot process.

Debian Buster: Run cryptroot-unlock only, this will ask for the password and continues booting afterwards.