User Tools

Site Tools


Mail server setups are not one sized fits all. Depending on the setting, the amount of users, and mail volume you will need to tweak things to fit your needs. This guide is meant to be a good starting point. As you setup your mail server /var/log/maillog will be your friend. Check it frequently to make sure things are working correctly or to diagnose problems. When you setup a mail server you're taking responsibility for it. This means continuous monitoring and tweaking. Don't just set it and forget it.

This guide uses MySQL, I like PostgreSQL but you have to enable System V IPC to use Postgres in a jail which has adverse security implications. If you want to use Postgres you should think about installing it on the main host system and have everything connect to it there.

Setup your jail(s) however you wish, but I recommend ezjail ( ).

This guide is not meant to be a replacement for the official documentation for each program. When in doubt RTFM!

Mail Server Testing

Security Considerations

Although you can throw everything in a single jail this probably isn't an ideal solution if this server is going to be used for other things. At the very least you shouldn't put other web applications in with everything else. It's very easy to provide some isolation of certain services. Email provides the keys to the castle so to speak due to it's ability to gain passwords to other services via password recovery.

Below is an example setup. You should think about your specific needs and setup and adapt as needed. If you're just starting out it's recommended to simply go with a single jail. Once you're familiar with everything it's very easy to split things out into other jails. But this should get you thinking about security and how you might tackle things once you're ready.

  1. Jail
    1. Postfix
    2. Dovecot
    3. ClamAV, SpamAssassin, etc…
  2. Jail (with nginx and php-fpm) - If you know exactly who will be accessing these you can use your firewall to further protect this jail by only allowing certain IP's.
    1. Postfixadmin
    2. phpMyAdmin (the same people were accessing both of these so I felt comfortable putting both in the same jail, you may want to put this in the general PHP Jail)
  3. Jail
    1. MySQL
  4. Jail (with nginx and php-fpm)
    1. Webmail
    2. Other PHP Programs


The first thing we need to do inside the jail is install the software we will be using. I recommend using Portmaster ( ) to manage your ports, as such this guides examples will use that.

cd /usr/ports/ports-mgmt/portmaster/ && make install clean

Now let's install Postfix (which will also install Dovecot and MySQL client).

portmaster --packages-build --delete-build-only --force-config mail/postfix/
Select the following compile options for PostFix: PCRE, DOVECOT, TLS, MYSQL, & VDA
Select the following compile options for Dovecot: KQUEUE, SSL, MANAGESIEVE, MYSQL''

After that finishes install the MySQL server.

portmaster --packages-build --delete-build-only --force-config databases/mysql51-server/

Finally install OpenSSL.

portmaster --packages-build --delete-build-only --force-config security/openssl/

Program Startup

Edit the following files to disable Sendmail and start the other programs.

round box 500px center

  • box creates a box around the container and uses the colours from the template's style.ini as default colours (__background_alt__ and __text__)
  • any of the classes info, tip, important, alert, help, download, todo will add a special note container with a corresponding icon
  • the classes danger, warning, caution, notice, safety use safety colours (and no icons)
  • round can be added to anything with a background colour or a border and will only work in modern browsers (no IE8 and under)



<box 100% round blue|/etc/periodic.conf>



<box 100% round blue|/etc/mail/mailer.conf>

sendmail        /usr/local/sbin/sendmail
send-mail       /usr/local/sbin/sendmail
mailq           /usr/local/sbin/sendmail
newaliases      /usr/local/sbin/sendmail


To stop Sendmail and start Postfix without rebooting (or restarting your jail) simply enter the following at the shell prompt

/etc/rc.d/sendmail stop
# Adapt the following to also start any other programs, just replace postfix with another programs name
/usr/local/etc/rc.d/postfix onestart

MySQL Setup

We need to setup the my.cnf file. This is the config file for MySQL ( ). There are 3 pre-made config files you can choose from that are for small, medium, and large server setups. Consult each file for memory usage info.

cp /usr/local/share/mysql/my-medium.cnf /usr/local/etc/my.cnf
/usr/local/etc/rc.d/mysql-server restart

Set the root password that MySQL will use. Make sure it's running ( /usr/local/etc/rc.d/mysql-server onestart ).

mysqladmin -u root password 'newpassword'

Now let's start setting up the users and database that will be used. You will be prompted for the password you just setup.

mysql -u root -p

Edit the following to include your own passwords.

CREATE USER postfix@localhost IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON postfix.* TO postfix;
GRANT SELECT ON postfix.*  to 'dovecot'@'localhost' IDENTIFIED by 'your_password';
flush privileges;

MySQL Access

With MySQL setup the config files for Postfix and Dovecot to access the database can be created.

<box 100% round blue|/usr/local/etc/postfix/>

user            = postfix
password        = your_password
hosts           = localhost
dbname          = postfix
query           = SELECT goto FROM alias WHERE address='%s' AND active = '1'


<box 100% round blue|/usr/local/etc/postfix/>

user            = postfix
password        = your_password
hosts           = localhost
dbname          = postfix
#query          = SELECT domain FROM domain WHERE domain='%s'
#optional query to use when relaying for backup MX
query           = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'


<box 100% round blue|/usr/local/etc/postfix/>

user            = postfix
password        = your_password
hosts           = localhost
dbname          = postfix
query           = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'


<box 100% round blue|/usr/local/etc/postfix/>

user            = postfix
password        = your_password
hosts           = localhost
dbname          = postfix
query           = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'


<box 100% round blue|/usr/local/etc/postfix/postfix/>

user            = postfix
password        = your_password
hosts           = localhost
dbname          = postfix
query           = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'


<box 100% round blue|/usr/local/etc/dovecot-sql.conf>

driver = mysql
connect = host=localhost dbname=postfix user=dovecot password=dovecot_password
user_query = SELECT concat('/var/vmail/', maildir) as home, concat('maildir:/var/vmail/', maildir) as mail, 26 AS uid, 6 AS gid, concat('maildir:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'

password_query = SELECT username as user, password, concat('/var/vmail/', maildir) as userdb_home, concat('maildir:/var/vmail/', maildir) as userdb_mail, 26 as userdb_uid, 6 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'


The config files now need to be secured.

chmod 640 /usr/local/etc/postfix/mysql_*
chgrp postfix /usr/local/etc/postfix/mysql_*
# Group needs to match what dovecot-auth runs as (set via 'user =' in dovecot.conf).
chmod 640 /usr/local/etc/dovecot-sql.conf
chgrp nobody /usr/local/etc/dovecot-sql.conf


To setup Dovecot ( ) first create the directory that will store e-mail.

mkdir -p /var/vmail
#Don't forget this next step for security reasons
chmod 770 /var/vmail
chown mailnull:mail /var/vmail

We'll be using a self signed certificate for SSL/TLS connections. You can use a non self signed certificate but that will cost money (some mail clients will show a warning with a self signed certificate).

mkdir -p /etc/ssl/dovecot
cd /etc/ssl/dovecot
openssl req -new -x509 -nodes -out cert.pem -keyout key.pem -days 365
chmod 400 key.pem
chmod 444 cert.pem

Rather than trying to edit the specific dovecot.conf file I found it easier to simply make a backup of the original /usr/local/etc/dovecot.conf and then use the following:

<box 100% round blue|/usr/local/etc/dovecot.conf>

#remove protocols you don't need
protocols = imap imaps pop3 pop3s

#Change this to no for plaintext passwords
disable_plaintext_auth = yes

ssl = yes
ssl_disable = no
ssl_cert_file = /etc/ssl/dovecot/cert.pem
ssl_key_file = /etc/ssl/dovecot/key.pem
mail_location = maildir:/var/vmail/%d/%u

mail_privileged_group = mail
dotlock_use_excl = yes
verbose_proctitle = yes

# mailnull user id is 26
first_valid_uid = 26
last_valid_uid = 26
mail_uid = mailnull

# mail goup id is 6
first_valid_gid = 6
last_valid_gid = 6
mail_gid = mail

maildir_copy_with_hardlinks = yes
#See for client workarounds
protocol imap {
  mail_plugins = quota imap_quota
  imap_client_workarounds = outlook-idle delay-newmail
protocol pop3 {
  mail_plugins = quota
  pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
protocol lda {
  postmaster_address = postmaster@EXAMPLE.COM
  mail_plugins = quota
auth default {
# Having "login" also as a mechanism make sure outlook can use the auth smtpd as well
  mechanisms = plain login
  passdb sql {
    args = /usr/local/etc/dovecot-sql.conf
  userdb sql {
    args = /usr/local/etc/dovecot-sql.conf
  userdb prefetch {
  user = nobody
  socket listen {
    master {
      path = /var/run/dovecot/auth-master
      mode = 0660
      user = mailnull
      group = mail
    client {
      path = /var/spool/postfix/private/auth
      mode = 0660
      user = postfix
      group = mail
dict {
#Adjust as needed
plugin {
  quota = maildir:storage=500M:messages=1000



We can now move onto Postfix ( ). You'll have to edit things here so don't simply copy and paste. A good reference to understand what each of these parameters means is man postfix.conf(5) ( )

The main resource I used to evaluate the DNSBL's ( ) used was the Intra2net Blacklist Monitor ( ). I also did some research on each lists history of false positives and attitude towards how they add to the blacklist. As such all the blacklists used here have a very very low occurrence of false positives. When combined with the other checks that are in place very little spam should get through. That being said if you are running a mail server with a wide variety of users that might be getting mail from all kinds of locations you might reduce the amount of blacklists. If I had to pick only two both and are widely used with a history of virtually no false positives.

You also might consider using Barracuda's DNSBL, it's free, but you do have to request access via their website ( ).

<box 100% round blue|Add this to the end of /usr/local/etc/postfix/>

# ---------------------- LOCAL SETTINGS ----------------------
myhostname                      =
#Note: If you are using a jail make sure you specify the jails IP also so that ClamSMTPd will work
mynetworks                      =
mydestination                   = $myhostname, localhost.$mydomain, localhost
#uncomment if you need relay_domains. Do not list domains in both relay and virtual
#relay_domains                  = proxy:mysql:/usr/local/etc/postfix/
# ---------------------- VIRTUAL DOMAINS START ----------------------
virtual_mailbox_domains         = proxy:mysql:/usr/local/etc/postfix/
virtual_mailbox_base            = /var/vmail
virtual_mailbox_maps            = proxy:mysql:/usr/local/etc/postfix/
virtual_alias_maps              = proxy:mysql:/usr/local/etc/postfix/
virtual_mailbox_limit_maps      = proxy:mysql:/usr/local/etc/postfix/
virtual_minimum_uid             = 26
virtual_uid_maps                = static:26
virtual_gid_maps                = static:6
virtual_transport               = dovecot
dovecot_destination_recipient_limit = 1
# ---------------------- VIRTUAL DOMAINS END ----------------------
# ---------------------- SASL PART START ----------------------
smtpd_sasl_auth_enable          = yes
smtpd_sasl_exceptions_networks  = $mynetworks
smtpd_sasl_security_options     = noanonymous
smtpd_sasl_type                 = dovecot
# Can be an absolute path, or relative to $queue_directory
smtpd_sasl_path                 = private/auth
# ---------------------- SASL PART END ----------------------
# ---------------------- TLS PART START ----------------------
smtp_tls_cert_file              = /etc/ssl/postfix/smtpd.cert
smtp_tls_key_file               = /etc/ssl/postfix/smtpd.key
smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache
smtp_tls_security_level         = may

smtpd_tls_cert_file             = /etc/ssl/postfix/smtpd.cert
smtpd_tls_key_file              = /etc/ssl/postfix/smtpd.key
smtpd_tls_session_cache_database = btree:$data_directory/smtpd_tls_session_cache
smtpd_tls_security_level        = may
smtpd_tls_auth_only             = yes
smtpd_tls_received_header       = yes
smtpd_tls_loglevel              = 1
tls_random_source               = dev:/dev/urandom
# ---------------------- TLS PART END ----------------------
# ---------------------- CHECKS PART START -----------------
smtpd_helo_required             = yes
disable_vrfy_command            = yes
smtpd_recipient_restrictions =

smtpd_data_restrictions =

#content_filter                  = scan:IP_of_JAIL:10025

#smtpd_milters = inet:IP_of_JAIL:8891
#non_smtpd_milters = inet:IP_of_JAIL:8891
# ---------------------- CHECKS PART END -------------------


<box 100% round blue|Un-comment the following in /usr/local/etc/postfix/>

smtps     inet  n       -       n       -       -       smtpd
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject


<box 100% round blue|Add this to the end of /usr/local/etc/postfix/>

dovecot    unix -        n       n       -       -       pipe
  flags=DRhu user=mailnull:mail argv=/usr/local/libexec/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}


ClamAV & ClamSMTP

Next we're going to setup anti-virus scanning with ClamAV ( ). ClamAV will use a lot of memory (140-160+ megs). If you're running in a memory constrained environment it might be worth it to instead simply strip all attachments. It's certainly not ideal but it's something to consider ( something like ).

We'll be using ClamSMTP ( ) to interface with ClamAV.

portmaster --packages-build --delete-build-only --force-config security/clamav
portmaster --packages-build --delete-build-only --force-config security/clamsmtp

<box 100% round blue|/usr/local/etc/clamd.conf needs to include the following>

TemporaryDirectory /tmp
LocalSocket /var/run/clamav/clamd.sock
User clamav


<box 100% round blue|/usr/local/etc/clamsmtpd.conf needs to include the following>

# The address clamd is listening on
ClamAddress: /var/run/clamav/clamd.sock


In the following set the ip of your jail, localhost won't work. <box 100% round blue|Add this to /usr/local/etc/postfix/>

# AV scan filter (used by content_filter)
scan      unix  -       -       n       -       16      smtp
        -o smtp_send_xforward_command=yes
        -o smtp_enforce_tls=no
# For injecting mail back into postfix from the filter
IP_of_jail:10026 inet  n -       n       -       16      smtpd
        -o content_filter=
        -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
        -o smtpd_helo_restrictions=
        -o smtpd_client_restrictions=
        -o smtpd_sender_restrictions=
        -o smtpd_recipient_restrictions=permit_mynetworks,reject
        -o mynetworks_style=host
        -o smtpd_authorized_xforward_hosts=IP_of_jail


<box 100% round blue|Add this to /usr/local/etc/postfix/>

content_filter = scan:[IP_of_jail]:10025


The -c flag is how many times a day freshclam will check for virus definition updates. <box 100% round blue|Add this to the jails /etc/rc.conf.>

clamav_freshclam_flags="--quiet -a JAIL_IP -c 12"


Once your server is setup you can test to make sure everything is working correctly by sending yourself the Eicar anti-virus test file ( ). Check your /var/log/maillog, you should see a line from clamsmtpd that shows a status message showing it detected the Eicar file as a virus.


SpamAssassin is relatively simple to install. Depending on your mail setup you can also use Dovecot to filter messages marked as spam by SpamAssassin to a spam folder. For my setup, since it's primarily used for pop3, I let end user mail clients trust SpamAssassin and filter spam to a spam folder.

portmaster --packages-build --delete-build-only --force-config mail/p5-Mail-SpamAssassin/

<box 100% round blue|/etc/rc.conf>

spamd_flags="-u spamd -c --socketpath=/var/run/SpamAssassin.sock"


<box 100% round blue|Add this to /usr/local/etc/postfix/>

spamassassin unix  -       n       n       -       -       pipe
 . user=nobody argv=/usr/local/bin/spamc -u mailnull -U /var/run/SpamAssassin.sock -e /usr/local/sbin/sendmail -oi -f ${sender} ${recipient}


<box 100% round blue|/usr/local/etc/postfix/ change>

smtp      inet  n       -       n       -       -       smtpd


smtp      inet  n       -       n       -       -       smtpd -o content_filter=spamassassin


Finally setup this directory SpamAssassin will need.

mkdir -p /var/spool/mqueue/.spamassassin
chown spamd:spamd /var/spool/mqueue/.spamassassin

If you have a Firewall you need to open up port 2703 for the Razor2 module to work in SpamAssassin. You can test SpamAssassin with this Check your mails headers, you should see various X-Spam headers showing that the email was scanned and the score it received.


The downside of filtering spam is the possibility of false positives. Although this setup uses blacklists and other checks that should result in a minute amount, if any, false positives a legitimate email being blocked isn't impossible. One way to reduce the possibility even further is with Postpals ( ). Postpals is a simple policy daemon for Postfix that keeps a database of outgoing mail, specifically recipients and relays associated to them. It then will white-list email coming back from those senders, thereby allowing email through even if that server ends up on a blacklist. The default is to purge entries from database if not seen within 4 weeks but you can change this. Let's install it.

portmaster --packages-build --delete-build-only mail/postpals/

<box 100% round blue|/etc/rc.conf>



Now let's setup Postfix. Remember to put all relay checks first so you don't create an open relay (i.e. reject_unauth_destination). <box 100% round blue|/usr/local/etc/postfix/ add 'check_policy_service inet:IP_of_Jail:10040'>

#This is roughly how the following part of your should look to get postpals working, adapt it to fit your setup.
smtpd_recipient_restrictions =
    check_policy_service inet:IP_of_Jail:10040



Running a mail server isn't just about receiving mail it's also about sending mail. We're going to setup SPF ( ) and DKIM ( ). DomainKeys Identified Mail (DKIM) is a method for associating a domain name to an email. The primary advantage of this system is it allows the signing domain to reliably identify a stream of legitimate email. This allows domain-based blacklists and white-lists to be more effective. DKIM is the newer version of Domainkeys. Some mail servers run both, but to be clear Domainkeys is the older standard and everyone is moving to DKIM. As such this guide will cover running DKIM. SpamAssassin will check for a valid DKIM and adjust the spam score accordingly if it fails. Emails sent from your server are less likely to end up in a spam folder with DKIM and SPF.

The Sender Policy Framework (SPF) is an open standard specifying a technical method to prevent sender address forgery. This can be installed very quickly by simply adding a TXT DNS record. SPF can be setup for your domain in a few minutes. Simply go to the OpenSPF site ( ) and use the wizard that's on the front page. SpamAssassin also checks an email against SPF DNS records and adjusts the spam score accordingly.

DKIM is a little more complex and we're going to use OpenDKIM ( ) to sign emails. Before we start installing things you will need to be familiar with the term 'Selector' as it will be used when setting up DKIM.

<note> A selector is added to the domain name, used to find DKIM public key information. It is specified as an attribute for a DKIM signature, and is recorded in the DKIM-Signature header field. Validation uses the selector as an additional name component, to give differential DNS query names. There are different DKIM DNS records associated with different selectors, under the same domain name.

For example:

Hence, selectors are used to permit multiple keys under the same organization's domain name. This can be used to give separate signatory controls among departments, date ranges, or third parties acting on behalf of the domain name owner. </note>

Let's start installing OpenDKIM (full documentation can also be found at ).

portmaster --packages-build --delete-build-only --force-config mail/opendkim/

At this point it's a good idea to choose a Selector. Current convention is to use a code for the current month and year, or just the year. However, you are free to choose any name you wish, especially if you have a selector assignment scheme in mind. My Selector is 'dkim2010' for example.

Now let's create a directory and the key we will be using.

mkdir -p /etc/ssl/dkim/

The following will generate 2 files, default.private and default.txt. You should rename these to something other than default. Something like SELECTOR.key.pem & SELECTOR.txt (where “SELECTOR” is the name you chose).

cd /etc/ssl/dkim/

Now add a TXT DNS record containing the base64 encoding of your public key, which is everything between the quotes in the default.txt file generated above. The name of the TXT record should be (where “SELECTOR” is the name you chose and “” is your domain name).

Let's add the user that OpenDKIM will run as.

pw groupadd opendkim
pw adduser opendkim -g opendkim -d /nonexistent -s /usr/sbin/nologin -c "OpenDKIM user"
chmod 700 /etc/ssl/dkim/
chown opendkim:opendkim /etc/ssl/dkim/
chmod 600 /etc/ssl/dkim/default.private
chown opendkim:opendkim /etc/ssl/dkim/default.private

<box 100% round blue|/etc/rc.conf>



cp /usr/local/etc/mail/opendkim.conf.sample /usr/local/etc/mail/opendkim.conf

<box 100% round blue|/usr/local/etc/mail/opendkim.conf change the following>

KeyFile              /etc/ssl/dkim/default.private
Selector             yourselector
Socket               inet:8891@localhost


Finally lets configure Postfix. <box 100% round blue|Add this to /usr/local/etc/postfix/>

smtpd_milters = inet:IP_of_JAIL:8891
non_smtpd_milters = inet:IP_of_JAIL:8891


To test to see if everything is setup correctly send an email from your mail server to . The service will perform a simple check of various sender authentication mechanisms. You will receive an email back with a report that shows if DKIM and SPF are working. As always check your /var/log/maillog if you experience any problems.


The Nginx web server is a fast, lightweight server designed to efficiently handle the needs of both low and high traffic websites. Nginx works great on FreeBSD, the author of Nginx originally programmed it for a very high traffic website that is run on FreeBSD servers.

# Make sure to select HTTP_SSL_MODULE
portmaster --packages-build --delete-build-only --force-config www/nginx

With Nginx installed it needs to be setup. <box 100% round blue|/etc/rc.conf>



Lets create the SSL certificates Nginx will use.

mkdir -p /etc/ssl/nginx
cd /etc/ssl/nginx
openssl req -new -x509 -nodes -out cert.pem -keyout cert.key -days 365

Now secure the files.

chmod 400 cert.key
chmod 444 cert.pem

Now we need to setup Nginx to use PHP-FPM for Postfixadmin. This willredirect all traffic to https so that everything is secure (do not run postfixadmin without https, you will be setting up passwords). <box 100% round blue|/usr/local/etc/nginx/nginx.conf>

    server {
    ## This will redirect http (port 80) to https (port 443)
        listen       80;
        location / {
            rewrite ^$request_uri? permanent;

    server {
        listen       443;
        access_log  /var/log/nginx/postfixadmin-access.log;

        root   /usr/local/www/postfixadmin;
        index  index.php index.html index.htm;

    ## SSL
        ssl                  on;
        ssl_certificate      /etc/ssl/nginx/cert.pem;
        ssl_certificate_key  /etc/ssl/nginx/cert.key;

        ssl_session_timeout  5m;

        ssl_protocols  SSLv2 SSLv3 TLSv1;
        ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
        ssl_prefer_server_ciphers   on;

    ## Images and static content are treated differently
        location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|xml)$ {
            access_log        off;
            expires           30d;

    ## Parse all .php files in the document_root directory
       location  ~ .php$ {
            fastcgi_split_path_info ^(.+\.php)(.*)$;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            fastcgi_param HTTPS on;
            fastcgi_param REDIRECT_STATUS 200;
            include fastcgi_params;
            fastcgi_intercept_errors        on;
            fastcgi_ignore_client_abort     off;
            fastcgi_connect_timeout 60;
            fastcgi_send_timeout 180;
            fastcgi_read_timeout 180;
            fastcgi_buffer_size 128k;
            fastcgi_buffers 4 256k;
            fastcgi_busy_buffers_size 256k;
            fastcgi_temp_file_write_size 256k;


Although we aren't dealing with user uploads you should be aware of the following ( ). <note warning> It is common with Nginx to pass every URI ending in .php to the PHP parser, if using a default PHP build this might lead to security issues. Nginx is a reverse proxy and as such does not have a concept of file unless you specifically tell it to. So if your configuration looks like this.

location ~* \.php$ {
	fastcgi_pass backend;

Then you are probably vulnerable. The issue is that PHP when configured incorrectly tries to guess which file you want to execute if the full path does not lead to a file. Say you have the URI /forum/avatar/1232.jpg/index.php. The File does not exist but /forum/avatar/1232.jpg does, Nginx does not care and gladly passes the request to PHP as you have instructed it to, PHP sees the file does not exist and that /forum/avatar/1232.jpg does and chooses to execute this. If this file is a user upload it might contain embedded PHP code and you are now vulnerable to arbitrary code execution.

The solution is to set cgi.fix_pathinfo=0 in the php.ini file, this causes PHP to try the literal path given and will thus not execute the jpg file. If for backwards compatibility reasons you cannot change this setting you need to ensure that Nginx is passing PHP an actual file or specifically disable PHP access to any directory containing user uploads. </note>

Additionally we can password protect PostfixAdmin with Nginx ( ). This just adds another barrier and if a lot of people don't need to access the backend it's not a bad option (more security layers for PHP is always a good thing). First we need to generate an htpasswd file and passwords must be encoded by function crypt(3). If Apache isn't installed you can use this Python script ( ) or this online generator ( ). Make sure you select “crypt” as the algorithm.

mkdir -p /usr/local/etc/nginx/htpasswd
# Now put your generated htpasswd file in that directory, name it something like postfixadmin

<box 100% round blue|/usr/local/etc/nginx/nginx.conf>

        location / {
            auth_basic            "Restricted";
            auth_basic_user_file  htpasswd/postfixadmin;


You can also use Nginx's HTTP Access Module ( ). This module provides a simple host-based access control to control access for specific IP-addresses of clients.

<box 100% round blue|/usr/local/etc/nginx/nginx.conf>

        location / {
            deny    all;


You can also combine both of the modules together so that if an IP matches there will be no password prompt but if you're in a remote location you can still access the backend via a login and password. <box 100% round blue|/usr/local/etc/nginx/nginx.conf>

        location / {
            satisfy any;
            deny   all;
            auth_basic            "Restricted";
            auth_basic_user_file  htpasswd/postfixadmin;


To rotate logs daily add the following (man newsyslog.conf for other options on how often logs will be rotated). <box 100% round blue|/etc/newsyslog.conf>

/var/log/nginx/*.log    root:wheel      640  7     *    @T00  GJ    /var/run/ 30



PHP-FPM (FastCGI Process Manager) ( ) is an alternative PHP FastCGI implementation and is the recommended way to deploy PHP with NGINX.

# Make sure you select FPM to build the PHP-FPM version. Unless you need them for something else you can unselect CLI and CGI from the build options.
portmaster --packages-build --delete-build-only --force-config lang/php5/
# Optionally you can install PHP5-IMAP
portmaster --packages-build --delete-build-only --force-config mail/php5-imap/

Lets finish setting up php.

cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini

Now we can set the timezone. Valid options are located at the PHP website ( ) <box 100% round blue|/usr/local/etc/php.ini>

date.timezone = "America/Los_Angeles"


<box 100% round blue|/etc/rc.conf>



Postfix Admin

Postfix Admin ( ) is a web based interface used to manage mailboxes, virtual domains, and aliases. It also features support for vacation/out-of-the-office messages. It's very easy to get up and running.

portmaster --packages-build --delete-build-only --force-config mail/postfixadmin/

Now edit /usr/local/www/postfixadmin/ Fill in your database settings so it can connect to the database we setup earlier.

<box 100% round blue|/usr/local/www/postfixadmin/>

$CONF['configured'] = false;


$CONF['configured'] = true;


Then go to in a web browser. You should see a list of 'OK' messages. The setup.php script will attempt to create the database structure. Assuming everything is OK you can specify a password (which you'll need to use setup.php again in the future); when you submit the form the hashed value - which you need to enter into is echoed out - with appropriate instructions on what to do with it.

Now create the admin user using the form displayed. That's all that's needed.

Make sure you're familiar with RFC 2142 `MAILBOX NAMES FOR COMMON SERVICES, ROLES AND FUNCTIONS' ( ). Setup 'Default Aliases' in /usr/local/www/postfixadmin/ Then when you create a domain in Postfixadmin check the 'Add default mail aliases' box.

Postfix Null Client

This is an optional step. If you're setting up your mail server in a FreeBSD jail then you might want to turn the host system (or other jails on the server) into null clients. A null client is a machine that can only send mail. It receives no mail from the network, and it does not deliver any mail locally. All this will be done on the host system or jails other than your mail jail. Install Postfix as you normally would (as detailed earlier).

<box 100% round blue|Add this to the end of /usr/local/etc/postfix/>

myorigin = $mydomain
relayhost = $mydomain
# comment out inet_interfaces if this is in a jail
inet_interfaces = loopback-only
local_transport = error:local delivery is disabled


<box 100% round blue|Comment out the local delivery agent entry /usr/local/etc/postfix/>

#local     unix  -       n       n       -       -       local


You can then test to make sure everything is working by sending an email to an account that has been setup from where you setup Postfix as a null client (i.e. mail ). Also make sure you have a alias setup on your main mail server.

Memory Usage Optimization

The default setups for a few of these programs can eat up a lot of memory. If you don't have a dedicated mail server or only have a low volume of email then the default settings with a few of these programs are a huge overkill.

For PHP-FPM the default is to spawn 20 processes to handle requests. Depending on your expected load adjust it accordingly and monitor how the server reacts.

<box 100% round blue|/usr/local/etc/php-fpm.ini>

; The number of child processes created on startup.
pm.start_servers = 5


SpamAssassin can eat up a ton of memory per child process. For a small mail server 1 child is plenty (messages simply get queued, so 1 child process just causes a delay under high volume). The -m flag sets the max number of child processes.

<box 100% round blue|/etc/rc.conf>

spamd_flags="-u spamd -m 1 -c --socketpath=/var/run/SpamAssassin.sock"


MySQL is simply storing the info about email accounts not the actual emails themselves. You can drastically decrease it's memory footprint in a low volume environment.

cp /usr/local/share/mysql/my-small.cnf /usr/local/etc/my.cnf
/usr/local/etc/rc.d/mysql-server restart

For Dovecot remove protocols you won't be using. For example if you don't need imap/imaps then remove it from the protocols listed so Dovecot doesn't spawn child processes to handle connections you will never use. You can also set the amount of login processes Dovecot will spawn at startup. The default is 3 and Dovecot will automatically spawn new processes to handle connections as needed (you need 1 process per connection).

<box 100% round blue|/usr/local/etc/dovecot.conf>

login_processes_count = 1


Things Not Covered

As I said not every email server is the same. Here are a few programs I think you should be aware of but aren't covered specifically here. You should evaluate what your specific needs are and see if you might find any of these tools useful.

Also if you do need Webmail check out Roundcube ( ). It can easily be added to this setup since we're already using PHP-FPM for Postfixadmin.

postfixdovecotsql.txt · Last modified: 2014/10/02 14:52 by admin