Monday, January 01, 2007

Mail Server - The perfect setup using postfix MTA from A to Z.

Debian 4 (etch) based email server, including configuration, security and performance considerations. Using Linux, BIND, Postfix, SpamAssassin, Mutt, Apache, SquirrelMail. (incomplete list- more will we added with time).


I've got me a bare minimum debian VPS from
First thing to do is obviously - getting that postfix running.

Here how I did it, to get the ultimate pleasure of having my own mail server.

Install the relevant packages:

aptitude install mutt # Mail reader (That is important, at least for first time install).
aptitude install postfix
aptitude install chrony # So that we will always have correct time set on the machine.
* take offline out off /etc/chrony/chrony.conf, chrony by itself is great but it comes customized for non constant Internet connection. I really prefer to use it over all the other obscure packages (ntpdate and co.)

Update your DNS record, mine looks like this
@ 500 IN MX 1 500 IN A
Which basicly says that my main (and only) MX server for the domain is "", the second line is a simple A record resolving the name to my server IP address.

Time for some postfix conf

Edit /etc/mailname to contain ""
Edit /etc/postfix/, mydestination should be equal to something like ", xen018013, localhost.localdomain, localhost, $myorigin". while myorigin = /etc/mailname

Now, one specific email alias I would like to forward to my main email account, so I'll tell postfix to never locally store it. I'm using hq4ever at the general account for system messages and co.
cat '' >> /home/hq4ever/.forward

My aliases file looks like this:
$ cat /etc/aliases
# /etc/aliases
mailer-daemon: postmaster
nobody: root
hostmaster: root
usenet: root
news: root
webmaster: root
www: root
ftp: root
abuse: root
noc: root
security: root
root: hq4ever
postmaster: hq4ever
remember to run newaliases after each edit made to this file, otherwise postfix won't catch up with changes. Reason for this being: postfix uses hash based lookup for the aliases file, when you run newaliases it actually created aliases.db and thats the file postfix reads.

Now comes the fun part, let the spam begin.

First I set an SPF record for domain , it's done in the DNS file (the guys at gandi are using bind, which is great and easy to operate.) My add record looks like this: 28800 IN TXT "v=spf1 mx -all"
which says, for domain "", use spf version 1, trust every mx defined in the dns, and also trust every mx defined in the dns for the domain, everything else is bad so block it. The part is very nice, because I mostly do my emailing from gmail, being able to change my FROM address could fail if I hadn't added this "include" record.

A good source for dns troubleshooting is, from there I've learned that my Postfix greeting was wrong. Lets fix that:
  1. Edit /etc/postfix/
  2. myhostname = (old value = xen018013)
  3. mydestination = ", xen018013, localhost.localdomain, localhost, $myorigin, $myhostname" (old value = ", xen018013, localhost.localdomain, localhost, $myorigin")
DNS caching only server

One might ask why do we even need a local DNS server?
The answer is simple: SMTP protocol is heavily depended on DNS data (MX records). Now, because Postfix uses DNS queries for email delivery decisions (SPF, DomainKeys, RBL's and MX) having a (remote / slow / stupid) 3rd party DNS resolver is a major performance burden.

The idea is to install BIND in a caching only mode[1]. Shell we begin? (yes! we bash)
aptitude install module-init-tools
aptitude install bind9
aptitude install dnsutils
Lets bluntly ignore that Stanford warnings about "bind on localhost" and configure our bind9 to listen ONLY on localhost. The logic is - We are setting up a local caching DNS server, I don't want others to waste my bandwidth using me as their DNS resolver (I wouldn't mind doing it for you temporarily if you ask me to, but not globally for the whole worm infesteded Internet). So we follow the instructions here[2], [3]
  1. Edit /etc/bind/named.conf.options
  2. The options section should look like this:
    options {
    directory "/var/cache/bind";

    // If there is a firewall between you and nameservers you want
    // to talk to, you might need to uncomment the query-source
    // directive below. Previous versions of BIND always asked
    // questions using port 53, but BIND 8.1 and later use an unprivileged
    // port by default.

    // query-source address * port 53;

    // If your ISP provided one or more IP addresses for stable
    // nameservers, you probably want to use them as forwarders.
    // Uncomment the following block, and insert the addresses replacing
    // the all-0's placeholder.

    // forwarders {
    // };

    auth-nxdomain no; # conform to RFC1035
    listen-on-v6 { 0:0:0:0:0:0:0:1; };
    listen-on {; };
  3. /etc/init.d/bind9 restart
    Stopping domain name service...: bind.
    Starting domain name service...: bind.
  4. Lets test our BIND:
    root@srv-il:/# dig @

    ; <<>> DiG 9.3.2-P1 <<>> @
    ; (1 server found)
    ;; global options: printcmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14378 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 2 ;; QUESTION SECTION: ; IN A ;; ANSWER SECTION: 172800 IN A

    ;; AUTHORITY SECTION: 21600 IN NS 21600 IN NS

    ;; ADDITIONAL SECTION: 172799 IN A 172799 IN A

    ;; Query time: 1390 msec
    ;; SERVER:
    ;; WHEN: Sat Jan 13 17:08:50 2007
    ;; MSG SIZE rcvd: 125

    root@srv-il:/# dig @

    ; <<>> DiG 9.3.2-P1 <<>> @
    ; (1 server found)
    ;; global options: printcmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40396 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 2 ;; QUESTION SECTION: ; IN A ;; ANSWER SECTION: 172795 IN A

    ;; AUTHORITY SECTION: 21595 IN NS 21595 IN NS

    ;; ADDITIONAL SECTION: 172794 IN A 172794 IN A

    ;; Query time: 0 msec
    ;; SERVER:
    ;; WHEN: Sat Jan 13 17:08:55 2007
    ;; MSG SIZE rcvd: 125
  5. If everything goes well, we can continue configuration the system-wide DNS server changes. Note: Make sure your BIND server DOES WORK!! Do some more dig queries.

    Edit your /etc/resolve.conf, we need our local bind server at the highest priority; Mine looks like this:
    * Note: if "nameserver" has "magicly" appeared in your resolve.conf after you installed bind the means you use resolvconf(8), bind9 has a hook in it's init script for resolveconf so this step can be safely skipped.