Configure stubby as DNS over HTTPS client and unbound as DNS cache for local network

Use the following configuration and set the DHCP server to hand out and fd00:2d3f:7fc8:3::53 as DNS server.

Note: local network will not resolve with this Unbound config.


  - 0::1@8053
root@doh:~# cat /etc/unbound/unbound.conf.d/*
    # Send minimum amount of information to upstream servers to enhance
    # privacy. Only sends minimum required labels of the QNAME and sets
    # QTYPE to NS when possible.

    # See RFC 7816 "DNS Query Name Minimisation to Improve Privacy" for
    # details.

    qname-minimisation: yes
    # The following line will configure unbound to perform cryptographic
    # DNSSEC validation using the root trust anchor.
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
	directory: "/etc/unbound"
	username: unbound
	# make sure unbound can access entropy from inside the chroot.
	# e.g. on linux the use these commands (on BSD, devfs(8) is used):
	#      mount --bind -n /dev/random /etc/unbound/dev/random
	# and  mount --bind -n /dev/log /etc/unbound/dev/log
	# logfile: "/etc/unbound/unbound.log"  #uncomment to use logfile.
	pidfile: "/etc/unbound/"
	verbosity: 1
	root-hints: root.hints
	do-not-query-localhost:  no
	# listen on all interfaces, answer queries from the local subnet.
	interface: ::0
	access-control: allow
	access-control: fd00:2d3f:7fc8::/48 allow
	interface-automatic: yes
  name: "."
    forward-addr: ::1@8053
curl --output /etc/unbound/root.hints
root@doh:~# cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback


source /etc/network/interfaces.d/*
auto eth0
iface eth0 inet static
iface eth0 inet6 static
        address fd00:2d3f:7fc8:3::53/64
        # use SLAAC to get global IPv6 address from the router
        # we may not enable ipv6 forwarding, otherwise SLAAC gets disabled
        autoconf 1
        accept_ra 2

Set up a simple Debian mail server with Postfix, Dovecot and opendkim

For my 19.99USD/yr VPS, the self-hosted email server solution provided by mailinabox is simply too bloated. This post will show you how to build a simple IMAP&SMTP-only mail server with Postfix and Dovecot with DKIM support. No DNS server, no fancy webmail, no CardDAV.


  • Supports DKIM
  • Simple configuration
  • Low memory usage (483MB total 72MB used)
  • pam based authentication
  • Mailboxes (sdbox format) stored in user homes
  • Suitable for small amount of users


DNS Records

@ 10800 IN MX 10 mail
@ 10800 IN TXT "v=spf1 mx -all"
_dmarc 10800 IN TXT "v=DMARC1; p=reject; sp=quarantine; pct=100;;; fo=1"
mail 10800 IN A
mail 10800 IN AAAA 2606:2800:220:1:248:1893:25c8:1946
dkimselector._domainkey 10800 IN TXT "v=DKIM1; h=sha256; k=rsa; "
smtp 10800 IN CNAME mail
imap 10800 IN CNAME mail

VPS configuration

Fresh-Clean-Brand-new Debian Buster installation REQUIRED

  1. Set up ssh access
mkdir /root/.ssh
tee -a /root/.ssh/authorized_keys << EOF
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQClNwNdN1uV/50kGEuQ7aoSIjIZAR+9zNEf/k9/rkGWFIwDxZ6jbwCp51zfZO+RuFfS3PeeChum2ukuzGgX3I0uMItxyVEdsurD7i5TmRNuT5TkigW9+LBOvIy9Qk8ueUhbkEv1P563TQVAKSMjikFYJOx/Z7dNUDcMmKap5Jauq36bE3XK++HvsyU55uN/y6D1LE8WxmkqhnmDatvY2Au6Sc6D7TxmnBbp3SZY0z9BRJ5Zf6IknC+nhqqykhU8vAdfxhpvhlCCoOf6atM+s0TqGvnNNT0L3XsQEhKcisik3mryV06IsMhTgVoWuH/cbNFGXPrsD6hNsEUL68jldj88okQuJTFCo5MROun8iRQsFJgvcEQ5MczW5DTy7lAmAWyFmmvLTW0h7yfVhgayF2JZv7+j4EaQOKaZ0iGXY0O8R2CMbz66vC2oY42fzj49jii1ozukSzyDf1Cd2XdClCOEB/bEPSk9FibovYjhxHFP11joM2zqCk6njQrXWxhfGcU= user@user1-elite-x2

cat <<EOF | tee /etc/ssh/sshd_config
PasswordAuthentication no
Port 51413

systemctl restart sshd
  1. update system
# update system
apt update && apt install -y screen && screen apt upgrade -y
# apply updates
systemctl reboot
  1. Install packages
apt install -y --no-install-recommends certbot dovecot-imapd dovecot-lmtpd postfix opendkim curl bsd-mailx opendkim-tools postfix-pcre rsync cron
  1. add mail users
groupadd -g 5000 mailusers
useradd -m -s /bin/false -g mailusers user1
useradd -m -s /bin/false -g mailusers user2
passwd user1
passwd user2
  1. generate certs
certbot --non-interactive --agree-tos -m certonly --standalone -d -d -d
  1. add cert renew cron job and deploy hook
# cert renew
cat <<EOF | tee /etc/cron.daily/cert-renew
exec certbot renew >/dev/null 2>&1
chmod +x /etc/cron.daily/cert-renew
# cert deploy hook
mkdir -p /etc/letsencrypt/renewal-hooks/post
cat <<EOF | tee /etc/letsencrypt/renewal-hooks/post/restart-mail
systemctl restart postfix
systemctl restart dovecot
chmod +x /etc/letsencrypt/renewal-hooks/post/restart-mail
  1. generate dkimkey
# dkim key gen
cd /etc/dkimkeys
opendkim-genkey --directory=./ --selector=qi2020
  1. view dkimkey dns record and add it to the DNS zone
cat dkimselector.txt
  1. fix dkim-related permissions
# fix dkimkey permission
chown opendkim:opendkim */qi2020.private
chmod 0600 */qi2020.private
  1. opendkim service config
# opendkim service config
tee -a /etc/opendkim.conf << EOF
# Automatically re-start on failures

AutoRestart yes
# limits the restarts to 10 in one hour

AutoRestartRate 10/1h
SyslogSuccess yes
Socket                  inet:8892@localhost
KeyTable file:/etc/dkimkeys/KeyTable
SigningTable refile:/etc/dkimkeys/SigningTable
tee -a /etc/dkimkeys/KeyTable << EOF
# %:qi2020:/etc/dkimkeys/%/qi2020.private %:qi2020:/etc/dkimkeys/%/qi2020.private

tee -a /etc/dkimkeys/SigningTable << EOF
  1. dhparams
curl > /usr/share/dovecot/dh.pem
  1. configure dovecot
# backup original dovecot.conf
mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.or

cat <<EOF | tee /etc/dovecot/dovecot.conf

# Pigeonhole version 0.5.4 ()

# OS: Linux 4.19.0-9-amd64 x86_64 Debian 10.4 

# Hostname:

mail_location = sdbox:~/sdbox
mail_privileged_group = mail
namespace inbox {
  inbox = yes
  location = 
  mailbox Archive {
    auto = subscribe
    special_use = \Archive
  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  mailbox Junk {
    auto = subscribe
    special_use = \Junk
  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  mailbox "Sent Messages" {
    special_use = \Sent
  mailbox Spam {
    special_use = \Junk
  mailbox Trash {
    auto = subscribe
    special_use = \Trash
  prefix = 
passdb {
  driver = pam
protocols = " imap lmtp"
service auth {
  unix_listener /var/spool/postfix/private/auth {
    group = postfix
    mode = 0600
    user = postfix
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    group = postfix
    mode = 0600
    user = postfix

ssl = required
ssl_cert = </etc/letsencrypt/live/
ssl_client_ca_dir = /etc/ssl/certs
ssl_dh = </usr/share/dovecot/dh.pem
ssl_key = </etc/letsencrypt/live/
ssl_min_protocol = TLSv1.2
userdb {
  driver = passwd-file
  args = username_format=%n /etc/passwd
protocol lmtp {
  recipient_delimiter = +.
  1. configure postfix
cat <<EOF | tee /etc/postfix/
# See /usr/share/postfix/ for a commented, more complete version

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# See -- default to 2 on
# fresh installs.
compatibility_level = 2

# TLS parameters
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname =
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination =,,,, localhost
relayhost = 
mynetworks = [::ffff:]/104 [::1]/128
mailbox_size_limit = 0
#recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
mailbox_transport = lmtp:unix:private/dovecot-lmtp
recipient_delimiter = .+

# generated 2020-07-14, Mozilla Guideline v5.4, Postfix 3.4.8, OpenSSL 1.1.1d, intermediate configuration


smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_cert_file = /etc/letsencrypt/live/
smtpd_tls_key_file = /etc/letsencrypt/live/
smtpd_tls_mandatory_ciphers = medium

# curl > /path/to/dhparam

# not actually 1024 bits, this applies to all DHE >= 1024 bits

smtpd_tls_dh1024_param_file = /usr/share/dovecot/dh.pem

tls_preempt_cipherlist = no

virtual_alias_maps = hash:/etc/postfix/virtual


smtpd_milters = inet:localhost:8892
non_smtpd_milters = inet:localhost:8892

## handle other domains
# virtual_alias_domains =,

# block spams 
strict_rfc821_envelopes = yes
disable_vrfy_command = yes

smtpd_helo_required = yes
smtpd_recipient_restrictions =

tee -a /etc/postfix/ << EOF
authclean unix  n       -       y       -       0       cleanup
 -o header_checks=pcre:/etc/postfix/outgoing_mail_header_filters
 -o nested_header_checks=

submission inet n       -       -       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=reject_unknown_recipient_domain,reject_non_fqdn_recipient,permit_sasl_authenticated,reject
  -o cleanup_service_name=authclean
# for enabling 465 port
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=reject_unknown_recipient_domain,reject_non_fqdn_recipient,permit_sasl_authenticated,reject
  -o cleanup_service_name=authclean


tee -a /etc/postfix/outgoing_mail_header_filters << EOF
# Remove the first line of the Received: header. Note that we cannot fully remove the Received: header
# /^Received: .*/ IGNORE
# because OpenDKIM requires that a header be present when signing outbound mail. The first line is
# where the user's home IP address would be.
/^\s*Received:[^\n]*(.*)/         REPLACE Received: from authenticated-user $1

# Remove other typically private information.
/^\s*User-Agent:/        IGNORE
/^\s*X-Enigmail:/        IGNORE
/^\s*X-Mailer:/          IGNORE
/^\s*X-Originating-IP:/  IGNORE
/^\s*X-Pgp-Agent:/       IGNORE

# The Mime-Version header can leak the user agent too, e.g. in Mime-Version: 1.0 (Mac OS X Mail 8.1 \(2010.6\)).
/^\s*(Mime-Version:\s*[0-9\.]+)\s.+/  REPLACE $1
  1. add postfix aliases
# virtual aliases
tee -a /etc/postfix/virtual << EOF   user1   user1   user1   user1
# user aliases
tee -a /etc/aliases << EOF
nobody: root
root: user1
# generate postmap db
postmap /etc/postfix/virtual
  1. configure unattended-upgrades
apt install -y unattended-upgrades
dpkg-reconfigure unattended-upgrades
tee -a /etc/apt/apt.conf.d/50unattended-upgrades << EOF
Unattended-Upgrade::Mail "root";
Unattended-Upgrade::Automatic-Reboot "true";
  1. apply everything
systemctl restart opendkim
systemctl restart dovecot
systemctl restart postfix
  1. Backup
# backup
# also install rsync on the target
apt install -y rsync
ssh-keygen -t ed25519
cat .ssh/
echo 'Host
     Port 54321
     User mailbackup' >> ~/.ssh/config
echo '#!/bin/bash
rsync -a --delete --quiet -e ssh /home' >> /etc/cron.hourly/mailbackup
chmod +x /etc/cron.hourly/mailbackup 

Email client configuration


  • Server: or or
  • Security: SSL/TLS
  • Port: 993
  • Username: “user1”
  • Password: passw0rd
  • authentication method: normal password (plaintext)


  • Server: or or
  • Security: STARTTLS or SSL/TLS
  • Port: 587 or 465
  • Username: “user1”
  • Password: passw0rd
  • authentication method: normal password (plaintext)

Markdown Syntax Guide

This article offers a sample of basic Markdown syntax that can be used in Hugo content files, also it shows whether basic HTML elements are decorated with CSS in a Hugo theme.


The following HTML <h1><h6> elements represent six levels of section headings. <h1> is the highest section level while <h6> is the lowest.







Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.

Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.


The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a footer or cite element, and optionally with in-line changes such as annotations and abbreviations.

Blockquote without attribution

Tiam, ad mint andaepu dandae nostion secatur sequo quae. Note that you can use Markdown syntax within a blockquote.

Blockquote with attribution

Don’t communicate by sharing memory, share memory by communicating.Rob Pike1


Tables aren’t part of the core Markdown spec, but Hugo supports supports them out-of-the-box.

Name Age
Bob 27
Alice 23

Inline Markdown within tables

Inline    Markdown    In    Table
italics bold strikethrough    code

Code Blocks

Code block with backticks

<!DOCTYPE html>
<html lang="en">
  <meta charset="UTF-8">
  <title>Example HTML5 Document</title>

Code block indented with four spaces

<!DOCTYPE html>
<html lang="en">
  <meta charset="UTF-8">
  <title>Example HTML5 Document</title>

Code block with Hugo’s internal highlight shortcode

<!DOCTYPE html>
<html lang="en">
  <meta charset="UTF-8">
  <title>Example HTML5 Document</title>

List Types

Ordered List

  1. First item
  2. Second item
  3. Third item

Unordered List

  • List item
  • Another item
  • And another item

Nested list

  • Item
  1. First Sub-item
  2. Second Sub-item

Other Elements — abbr, sub, sup, kbd, mark

GIF is a bitmap image format.


Xn + Yn = Zn

Press CTRL+ALT+Delete to end the session.

Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures.

  1. The above quote is excerpted from Rob Pike’s talk during Gopherfest, November 18, 2015. ↩︎

Pirates arrrr

Piracy is typically an act of robbery or criminal violence at sea. The term can include acts committed on land, in the air, or in other major bodies of water or on a shore. It does not normally include crimes committed against persons traveling on the same vessel as the perpetrator (e.g. one passenger stealing from others on the same vessel). The term has been used throughout history to refer to raids across land borders by non-state agents.