• This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn more.

PostgreSQL+Postfix+Nginx+PHP+RoundCube+Dovecot+ SpamAssassin+Clamav+Spamd


Son of Beastie

Thanks: 686
Messages: 4,611

When Installing software I will use portmaster from ports-mgmt/portmaster

Goal of this howto (unsorted):
  • configure mail server that will handle virtual mailboxes, virtual domains and/or relay mail to other hosts.
  • configure webmail
  • let users to connect to their accounts using imaps protocol
  • let user to send mail using smtps protocol
  • configure antivirus and spam filtering software
  • use only OpenSource software available in FreeBSD ports
  • where possible/nessacery encrypt connections with OpenSSL
  • use static uid:gid for mail
  • store mail in /mail/[red]domain[/red]/[red]username[/red]
  • use sockets where possible
  • [red]configure my own mailserver[/red]

Table Of Content

Preparing system
Install PostgreSQL
Install & configure Dovecot
Install & configure Postfix
Install & configure nginx
Install & configure RoundCube
Configure PostgreSQL
Configure PHP
Install and Configure Spamd
Install and Configure Clamav
Install and Configure SpamAssassin
Checking if it works
Change log

I've left some reserved posts for future use {perhaps security related or something else}


Son of Beastie

Thanks: 686
Messages: 4,611

Preparing system

I'll use GENERIC kernel
To be able to run PostgreSQL in jail you need to {you may/will need to tune these values to your needs}
# cat < EOF >> [file]/boot/loader.conf[/file]
# echo 'security.jail.sysvipc_allowed=1' >> [file]/etc/sysctl.conf[/file]
# echo 'jail_sysvipc_allow="YES"' >> [file]/etc/rc.conf[/file]

Sine we'll be using jail, we also want to use nullfs
# echo 'nullfs_load="YES"' >> [file]/boot/loader.conf[/file]

now reboot your FreeBSD, and create new jails, start it and jexec to it.
[red]Everything below will be executed in jail[/red]

Now in your jail
By default OpenSSL from Base system will create 1024 bit RSA keys, I don't like that, It's not enough.
So in /etc/ssl/openssl.cnf
find and set
default_bits            = 4096


Son of Beastie

Thanks: 686
Messages: 4,611

Install PostgreSQL

Install and start postgresql
# portmaster [port]databases/postgresql84-server[/port]
# echo 'postgresql_enable="YES"' >> [file]/etc/rc.conf[/file]
# /usr/local/etc/rc.d/postgresql initdb
# /usr/local/etc/rc.d/postgresql start


Son of Beastie

Thanks: 686
Messages: 4,611

Install & configure Dovecot

Install dovecot
# portmaster [port]mail/dovecot[/port]
make sure to select SSL, PGSQL

Create SSL/TLS certificate for secure connections:
# mkdir -p /etc/ssl/dovecot
# cd /etc/ssl/dovecot
# openssl req -new -x509 -nodes -out cert.pem -keyout key.pem -days 365
# chmod 640 /etc/ssl/dovecot/*
more info: http://forums.freebsd.org/showthread.php?t=6490

Edit /usr/local/etc/dovecot.conf
protocols = imaps
disable_plaintext_auth = no

ssl = yes
ssl_cert_file = /etc/ssl/dovecot/cert.pem
ssl_key_file = /etc/ssl/dovecot/key.pem

mail_privileged_group = mail
dotlock_use_excl = yes
mail_location = maildir:/mail/%d/%n
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

protocol imap {
  imap_client_workarounds = delay-newmail netscape-eoh tb-extra-mailbox-sep
  mail_plugins = quota imap_quota

protocol managesieve {


protocol lda {
  postmaster_address = postmaster@example.com
  sendmail_path = /usr/sbin/sendmail
  mail_plugins = quota

auth_username_format = %Lu

auth default {
  mechanisms = plain

  passdb sql {
    args = /usr/local/etc/dovecot-sql.conf
  userdb prefetch {
    # keep this, otherwise quota won't work

  user = root

  socket listen {
    master {
      path = /var/run/dovecot/auth-master
      mode = 0600
      user = mailnull
      group = mail
    client {
      path = /var/run/dovecot/auth-client
      mode = 0660
        user = postfix
        group = mail


dict {


plugin {
  quota = maildir:User quota
  quota_rule = *:storage=1GB
Edit /usr/local/etc/dovecot-sql.conf
driver = pgsql
connect = host=/tmp dbname=mail user=dovecot password=DovecotPassword
default_pass_scheme = PLAIN-MD5

password_query = \
  SELECT username, domain, password, '*:bytes=' || quota || '[red]M[/red]' AS userdb_quota_rule \
  FROM mailbox WHERE username = '%n' AND domain = '%d' AND active = true
M in red means that SQL query will return quota in Megabytes (Consider it as modifier)

TIP: host can be IP, hostname of path to PostgreSQL socket

Enable dovecot at jail startup
# echo 'dovecot_enable="YES"' >> [file]/etc/rc.conf[/file]
NOTE: you may want to install mail/dovecot-sieve, it will help you to automatically move different mails to different folders :)


Son of Beastie

Thanks: 686
Messages: 4,611

Install & configure Postfix

Stop sendmail
# /etc/rc.d/sendmail stop
Install postfix
# portmaster [port]mail/postfix[/port]
Added group "postfix".
Added group "maildrop".
Added user "postfix".
You need user "postfix" added to group "mail".
Would you like me to add it [y]? [red][b]y[/b][/red]
Would you like to activate Postfix in /etc/mail/mailer.conf [n]? [red][b]y[/b][/red]
make sure to select DOVECOT, TLS, PGSQL, VDA

Make system use postfix instead of sendmail
# cat < EOF >> [file]/etc/rc.conf[/file]
# cat < EOF >> [file]/etc/periodic.conf[/file]
Create and secure the SMTP SSL certificate:
# mkdir -p /etc/ssl/postfix
# cd /etc/ssl/postfix
# openssl req -new -x509 -nodes -out smtpd.pem -keyout smtpd.pem -days 365
# chmod 640 /etc/ssl/postfix/*
# chgrp -R postfix /etc/ssl/postfix
edit red test in /usr/local/etc/postfix/main.cf
# The soft_bounce parameter provides a limited safety net for
# testing.  When soft_bounce is enabled, mail will remain queued that
# would otherwise bounce. This parameter disables locally-generated
# bounces, and prevents the SMTP server from rejecting mail permanently
# (by changing 5xx replies into 4xx replies). However, soft_bounce
# is no cure for address rewriting mistakes or mail routing mistakes.

smtpd_recipient_restrictions =
  reject_rbl_client bl.spamcop.net

smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks

virtual_mailbox_base = /mail
virtual_mailbox_maps = pgsql:/usr/local/etc/postfix/pgsql_virtual_mailbox_maps.cf
virtual_mailbox_domains = pgsql:/usr/local/etc/postfix/pgsql_virtual_mailbox_domains.cf
virtual_alias_maps = pgsql:/usr/local/etc/postfix/pgsql_virtual_alias_maps.cf
local_recipient_maps = $virtual_mailbox_maps
virtual_create_maildirsize = yes
virtual_mailbox_extended = yes

[b]# I use static uid:gid, dynamic ones caused problems for me {permission related}[/b]
virtual_uid_maps = static:26
virtual_gid_maps = static:6

virtual_transport = dovecot

smtpd_delay_reject = yes
smtpd_helo_required = yes

broken_sasl_auth_clients = yes
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = /var/run/dovecot/auth-client
smtpd_sasl_security_options = noanonymous

smtp_use_tls = yes
smtpd_use_tls = yes
smtp_tls_note_starttls_offer = yes
smtpd_tls_key_file = /etc/ssl/postfix/smtpd.pem
smtpd_tls_cert_file = /etc/ssl/postfix/smtpd.pem
smtpd_tls_CAfile = /etc/ssl/postfix/smtpd.pem
smtpd_tls_loglevel = 0
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom
myhostname = [red]example.com[/red]
mydomain = [red]example.com[/red]
mydestination = [red]localhost.$mydomain, localhost[/red]
#relay_domains = pgsql:/usr/local/etc/postfix/pgsql_relay_domains.cf

uncomment this in /usr/local/etc/postfix/master.cf
[red]smtps     inet  n       -       n       -       -       smtpd
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject[/red]
  -o message_size_limit=26214400
message_size_limit will change message size limit from 10240000B (default) to 25M

and add this to /usr/local/etc/postfix/master.cf
dovecot    unix -        n       n       -       -       pipe
  flags=DRhu user=mailnull:mail argv=/usr/local/libexec/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}

user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT domain FROM mailbox_relay_domains WHERE domain = '%s' AND active = true
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT dest_username || '@' || dest_domain FROM mailbox_aliases WHERE address = '%s' AND active = true
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT domain FROM mailbox WHERE domain = '%s' AND active = true
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT quota FROM mailbox WHERE username = '%u' AND domain = '%d' AND active = true
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT domain || '/' || username FROM mailbox WHERE username = '%u' AND domain = '%d' AND active = true
TIP: as host you can use IP, hostname or path to postgreSQL socket

Secure Postfix’s PGSQL files:
# chmod 640 /usr/local/etc/postfix/pgsql_*
# chgrp postfix /usr/local/etc/postfix/pgsql_*

Create our virtual mail directories:
# mkdir /mail
# chown mailnull:mail /mail

Enable postfix at jail startup
echo 'postfix_enable="YES"' >> [file]/etc/rc.conf[/file]


Son of Beastie

Thanks: 686
Messages: 4,611

Install & configure nginx

Install nginx and spawn_fcgi
# portmaster [port]www/nginx[/port] [port]www/spawn-fcgi[/port]
enable service
# cat < EOF >> [file]/etc/rc.conf[/file]
this will start spawn_fcgi with socket, instead of listening to some port

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
[B]        # This will redirect http to https :D[/B]
        listen       80;
        server_name example.com;
        location / {
            rewrite ^ https://example.com/$request_uri? permanent;

    server {
        listen       443;
        server_name  example.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   /usr/local/www/roundcube;
            index  index.php;

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/www/nginx-dist;

               location ~ \.php$ {
            root           /usr/local/www/roundcube;
            fastcgi_pass   unix:/var/run/spawn_fcgi.socket;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /usr/local/www/roundcube/$fastcgi_script_name;
            include        fastcgi_params;

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #location ~ /\.ht {
        #    deny  all;

        ssl                  on;
        ssl_certificate      /etc/ssl/www/www.pem;
        ssl_certificate_key  /etc/ssl/www/www.pem;
        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;

        client_max_body_size 25m;

This will listen on https port, it's a good idea to make another host to listen on port 80, and redirect to https

client_max_body_size 25m; this will set max body size to 25M which means, that you can upload up to 25M per file. It's important to set this variable, not only in php.ini {see Configure PHP section below

TIP: you can install nginx+RoundCube+PHP in different jail. I think this would add extra security layer to mail server. One thing I dislike about RoundCube is that It can't connect to db via socket


Son of Beastie

Thanks: 686
Messages: 4,611

Install and configure RoundCube

portmaster [port]mail/roundcube[/port]
Make sure to select PGSQL, SSL
When asked for php5 config make sure to select FASTCGI

in /usr/local/www/roundcube/config/db.inc.php set
[b]# you should be able to use hostname instead of IP as well
# unfortunatly it's not possible to specify socket :([/b]
$rcmail_config['db_dsnw'] = 'pgsql://roundcube:[red]RoundcubePassword[/red]@'
in /usr/local/www/roundcube/config/main.inc.php set
$rcmail_config['log_driver'] = 'syslog';
$rcmail_config['force_https'] = TRUE;
$rcmail_config['default_host'] = 'ssl://example.com';
$rcmail_config['default_port'] = 993;
$rcmail_config['imap_auth_type'] = auth;
$rcmail_config['username_domain'] = 'example.com';
$rcmail_config['mail_domain'] = 'example.com';
$rcmail_config['smtp_server'] = 'ssl://example.com';
$rcmail_config['smtp_port'] = 465;
$rcmail_config['smtp_user'] = '%u';
$rcmail_config['smtp_pass'] = '%p';
$rcmail_config['sendmail_delay'] = 20;
$rcmail_config['session_lifetime'] = 30;
[b]// set some random password[/b]
$rcmail_config['des_key'] = 'SomeRandom24charPassword';
$rcmail_config['language'] = lv_LV;
$rcmail_config['product_name'] = 'Example.com :)';
$rcmail_config['create_default_folders'] = TRUE;
$rcmail_config['default_charset'] = 'ISO-8859-13';
$rcmail_config['identities_level'] = 1;
$rcmail_config['max_pagesize'] = 100;
$rcmail_config['log_logins'] = true;
[b]// this doesn't seam to work.... perhaps a bug...[/b]
$rcmail_config['timezone'] = 'Europe/Riga';
$rcmail_config['show_images'] = 1;
[b]// most average users probably prefer html emails[/b]
$rcmail_config['htmleditor'] = TRUE;
to get info what these variables means read config file, it's pretty well commented


Son of Beastie

Thanks: 686
Messages: 4,611

Configure PostgreSQL

PostreSQL configuration for RoundCube
# su pgsql
$ psql template1
-- create users
CREATE USER postfix ENCRYPTED password 'PostfixPassword';
CREATE USER dovecot ENCRYPTED password 'DovecotPassword';
CREATE ROLE mailman WITH USER postfix, dovecot;
-- create group

\c mail

-- virtual mailboxes
CREATE TABLE mailbox (
	username	VARCHAR(128) NOT NULL,
	domain		VARCHAR(128) NOT NULL,
	password	CHAR(32) NOT NULL,
	PRIMARY KEY (username, domain)

-- virtual mailbox aliases
CREATE TABLE mailbox_aliases (
	address		VARCHAR(256) PRIMARY KEY,
	dest_username	VARCHAR(128) NOT NULL,
	dest_domain	VARCHAR(128) NOT NULL,
	FOREIGN KEY (dest_username, dest_domain) REFERENCES mailbox (username, domain) ON DELETE CASCADE

-- relay domains
CREATE TABLE mailbox_relay_domains (
	domain		VARCHAR(256) PRIMARY KEY,

-- grant permissions
GRANT SELECT ON mailbox TO dovecot;
GRANT SELECT ON mailbox,mailbox_aliases,mailbox_relay_domains TO postfix;

-- create virtual domain
-- currently virtual domains doesn't work for me
INSERT INTO domains_relay_domains VALUES ('example.com');

-- create user
INSERT INTO mailbox VALUES ('test','example.com','MD5 Hash of password');

-- create virtual aliases
INSERT INTO mailbox_aliases VALUES ('postmaster@example.com', 'test', 'example.com');
INSERT INTO mailbox_aliases VALUES ('root@example.com', 'test', 'example.com');
INSERT INTO mailbox_aliases VALUES ('example@example.com', 'test', 'example.com');
INSERT INTO mailbox_aliases VALUES ('abuse@example.com', 'test', 'example.com');

-- create roundcube and database
CREATE USER roundcube ENCRYPTED password 'RoundcubePassword';
\c - roundcube

-- create tables etc for roundcube
\i /usr/local/www/roundcube/SQL/postgres.initial.sql

-- exit psql

in /usr/local/pgsql/data/pg_hba.conf you need to set
who is allowed access postgresql and from where. This file is pretty well
written so, you should read it yourself :)

Add this entry
local   mail        postfix,dovecot                        md5
host    webmail     roundcube           [red]jail_IP[/red]/32         md5

If postfix and dovecot connects to database over net, then
host    mail        postfix,dovecot     [red]jail_IP[/red]/32         md5
host    webmail     roundcube           [red]jail_IP[/red]/32         md5
also for db security I suggest you change password for pgsql user and set password authentication for everything and everyone form anywhere :)


Son of Beastie

Thanks: 686
Messages: 4,611

Configure PHP

To attach files in webmail interface (roundcube) we need to configure php to allow uploading bigger files {by default it's about 2MB, which is very, very small}

copy example config file
# cp /usr/local/etc/php.ini-recommended /usr/local/etc/php.ini
edit variables in /usr/local/etc/php.ini
; Maximum size of POST data that PHP will accept.
post_max_size = 25M

; Maximum allowed size for uploaded files.
upload_max_filesize = 20M

; Maximum number of files that can be uploaded via a single request
max_file_uploads = 10
EDIT: after editing /usr/local/etc/php.ini if spawn-fcgi is started, you need to restart it, for new setting to take effect
# /usr/local/etc/rc.d/spawn-fcgi restart


Son of Beastie

Thanks: 686
Messages: 4,611

[red]This section must be done on host system, not in jail[/red]

Install and configure spamd (common)
# portmaster [port]mail/spamd[/port]
# cat < EOF >> [file]/etc/rc.conf[/file]
obspamd_flags="-l IP_mail_server -h example.com"
-l is optional, if you omit it, spamd will listen on all aliased IP's AFAIK

enable and configure pf { this one you figure out yourself :) }

you need to mount fdescfs to /dev/fs for graylisting to work.
# echo 'fdescfs /dev/fd fdescfs rw 0 0' >> [file]/etc/fstab[/file]
And you need to create spamd database
# touch /var/db/spamd
# chown _spamd:_spamd /var/db/spamd
# chmod ug=rw,o= /var/db/spamd

Configure spamd (blacklisting)

to obspamd_flags in /etc/rc.conf add -b flag

I haven't yet figured out how exactly and does it work at all.
Run # crontab -e and add this
48      *       *       *       *       /usr/local/sbin/spamd-setup
This should fetch blacklists

to /etc/pf.conf add this
table <spamd> persist
rdr pass inet proto tcp from <spamd> to any port smtp -> port spamd

# let spamd-setup update blacklist
pass out on $e_if0 inet proto tcp from [red]Host_IP[/red] to any port { spamd, spamd-cfg } keep state
pass out on $e_if0 inet proto udp from [red]Host_IP[/red] to any port spamd-sync keep state

Configure spamd (graylisting)

grayisting may have a one serious dissadvangate. It may delay mail for more than 35 minutes... In case of gmail, it can be even longer, because gmail may try to deliver mail from different servers. You can probably resolve this by whitelisting all gmail IP's {but I don't have such a list, unfortunately}

to /etc/pf.confadd this
table <spamd-white> persist
rdr pass inet proto tcp from !<spamd-white> to any port smtp -> port spamd

TIP: you can combine bough methods :D also you can create your own whitelist and use it as well


Son of Beastie

Thanks: 686
Messages: 4,611

Install and configure Clamav

install clamsmtp and clamav
# portmaster [port]security/clamav[/port] [port]security/clamsmtp[/port]
copy example config file
# cp /usr/local/etc/clamd.conf.default /usr/local/etc/clamd.conf
# cp /usr/local/etc/clamsmtpd.conf-sample /usr/local/etc/clamsmtpd.conf
# cp /usr/local/etc/freshclam.conf.default /usr/local/etc/freshclam.conf
Edit /usr/local/etc/clamd.conf
TemporaryDirectory /tmp
LocalSocket /var/run/clamav/clamd.socket
User clamav
Edit /usr/local/etc/clamsmtpd.conf the way you like
ClamAddress: /var/run/clamav/clamd.socket
Header: X-Virus-Scanned: ClamAV using ClamSMTP
TempDirectory: /tmp
Action: drop
Quarantine: off
User: clamav
make sure that clamsmtpd and clamd use same user and socket

Now you need to add fallowing to /usr/local/etc/postfix/master.cf
# 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
[red]IP_of_jail[/red]: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=[red]IP_of_jail[/red]
to /usr/local/etc/postfix/main.cf add
content_filter = scan:[[red]IP_of_jail[/red]]:10025

enable clamav, freshclam and clamsmtpd at startup
cat < EOF >> [file]/etc/rc.conf[/file]
clamav_freshclam_flags="--quiet -a jail_ip -c 24"
-c 12 means, that freshclam should update virus database every 2 hours (valid values 1-50)


Son of Beastie

Thanks: 686
Messages: 4,611

Install and configure SpamAssassin

# portmaster mail/p5-Mail-SpamAssassin
Configure daemon startup
# cat < EOF >> [file]/etc/rc.conf[/file]
spamd_flags="-c --socketpath=/var/run/SpamAssassin.socket"
This will make spamassassin daemon to listen on socket, instead of opening port

# mkdir /var/spool/mqueue/.spamassassin
# chown _spamd:_spamd /var/spool/mqueue/.spamassassin
now run sa-update
# sa-update
add this to /usr/local/etc/postfix/master.cf
spamassassin unix  -       n       n       -       -       pipe
   user=nobody argv=/usr/local/bin/spamc -u mailnull -U /var/run/SpamAssassin.socket -e /usr/local/sbin/sendmail -oi -f ${sender} ${recipient}
and edit this line in /usr/local/etc/postfix/master.cf
smtp      inet  n       -       n       -       -       smtpd
smtp      inet  n       -       n       -       -       smtpd -o content_filter=spamassassin
edit /usr/local/etc/mail/spamassassin/local.cf as needed.
Personally I set required_score to 4.0


Son of Beastie

Thanks: 686
Messages: 4,611

Checking if it works

You might want to restart server, to check it everything is started :D

send mail to some user that is registered in database, mail should be delivered to /mail/domain/user/...
you can check status of mail queue with # mailq
Also send mail from jail to some other email server and see if you can send mails

Try logging in to your server (imap) with some mail client
Here's example mutt config:
set imap_user = "user@example.com"
set folder = "imaps://example.com:993"
set postponed = "+Drafts"
set spoolfile = "+INBOX"
set record = "+Sent"
This is very simple, simply open example.com in browser, and try to log in. If you can't login with roundcube, but can login with some email client, then roundcube is configured incorrectly
After you have logged in you need to check if you can send emails. Send some email to other server (gmail for example). If Roundcube won't be able to connect to smtps, than it will show error.

If SpamAssassin works, in received mail headers you should see something like this:
X-Spam-Checker-Version: SpamAssassin 3.3.0 (2010-01-18) on bsdroot.lv
X-Spam-Level: *
X-Spam-Status: No, score=1.8 required=5.0 tests=MISSING_SUBJECT,
    TVD_SPACE_RATIO autolearn=no version=3.3.0
If clamav works, in received mail headers you should see something like this:
X-Virus-Scanned: ClamAV using ClamSMTP
NOTE: you my want to create 1 or 2 common certificates, and use it by all services (depending on your configuration). Certificates cost money (at least certificates signed by CA).

NOTE: passwords in config files are save unencrypted, later I'll think how to save them encrypted

More and much more detailed info in references


Son of Beastie

Thanks: 686
Messages: 4,611

OK, it seams I've finished this Howto.
Suggestions and error correction {i'm just a human} are welcomed :D

also don't forget that you don't need to fallow this howto step by step, you can improvize :D

Originally I wanted to use lighttpd but later changed to nginx. I did that because nginx is much easier to set up, because if you make error in config, it's easier to find it.

[red]Don't expect this tutorial to work out of the box. You will need to work yourself. I wrote this from what I could remember.[/red]
But since then, I've rebuild my mail server many times, and this tutorial helped me a lot. When I find stuff that doesn't glue well, I try to fix it. So it's not perfect... :)


Son of Beastie

Thanks: 686
Messages: 4,611

Change log
2010-02-18: Added few lines in nginx.conf to redirect http to https
2010-02-22: Added 2x pf rules to smapd blacklist section. This fixes spamd-setup not being able to update blacklist
2010-02-23: Fixed many console commands (echo), Syntax was wrong, sorry
2010-02-26: Fixed SpamAssassin. Now it scans mail :D
2010-02-26: added Checking if it works section
2010-03-09: Improved PostgreSQL queries in "Configure PostgreSQL" section [didn't test, but they should work, anyway, if it doesn't let me know]
2010-03-09: Fixed may typos pointed to me by osx-addict
2010-03-10: Fixed typo in Postfix section (thanks to osx-addict. Fixed one PostgreSQL query in postfix section, to match updated PostgreSQL query
2010-04-10: in Install & configure Postfix section s/chmod mailnull:mail/chown mailnull:mail/
2010-05-31: Fixed SQL insert query bug in Configure PostgreSQL section pointed out by zloidemon on jabber. Fix in Install and configure Clamav section
2010-11-05: Add some missing info to SpamAssasin section. Some other fixes
2011-01-20: Remove relay_domains from /usr/local/etc/postfix/main.cf, it was not needed, as it serves different purpose. Fix spamd section: you need to create database manually
2011-01-22: Fix beginning of spamd section


Active Member

Thanks: 29
Messages: 198

Great howto.
Makes me try using nginx instead of apache.

One note however, something that i miss, user management!



New Member

Messages: 5

Great how-to killasmurf. Ran into a couple things when starting spamassassin, on initial startup SA wants you to run sa-update first and I believe the command

# chown _spamd:_spamd /var/spool/mqueue/.spamassassin

should be

# chown mailnull:mailnull /var/spool/mqueue/.spamassassin

My error logs complained about SA not having permission to create or write bayes files inside the directory and I noticed in master.cf your starting SA with -u mailnull. I made the change and SA runs perfect.

Hopefully you could add a section on automatically moving mail marked as spam. I created a script that greps inboxes for spam flag for half hour but it'd be nice to have something do it in real time.

Great how-to.


Active Member

Thanks: 3
Messages: 174

I read the entire thing.. End to end.. However, it might be nice on each step to indicate what is in a jail vs not.. I think you're running postgres in a jail..

Also, in the section where you're installing spamd and related 'mail' tools, you mention that they should be in the host environment.. I ran an 'smtp' jail that had everything but the roundcube/apache combination -- so it had sendmail, spamd, clamav, spamasassin, dovecot, and a few milters to glue it together. Worked just fine.. I'll be working on setting up my jails again this evening after I get home and want to set things up like :

  • smtp.example.com : (sendmail or postfix), spamd, clamav, spamasassin, dovecot, maildir folders for mail
  • http://www.example.com : (apache or nginx), roundcube (in a non-public directory--must know exact URL - discourage hackers), other pages, forward to other virtual domains,etc.
  • db.example.com : postgres -- if I can get it to behave in a jail -- otherwise it will be back in host environment
  • host.example.com : host environment -- user accounts,etc.
  • dns.example.com : dns lookups (eventually)
  • proxy.example.com: privoxy proxy server (or better?) - eventually


Son of Beastie

Thanks: 686
Messages: 4,611

killasmurf86 said:
Preparing system
[red]Everything below will be executed in jail[/red]
killasmurf86 said:
[red]This section should be done on host system, not in jail {at least I did this on host}[/red]

Install and configure spamd (common)
Did you miss ^^^ :D

Yes, having postfix on host system, has some advantages, that I discovered later.
I wanted all jails to send daily messages to postfix jail, host couldn't do it, because it had ip alias which matched destination :D then I moved postfix, dovecot and postgresql to host (I have single server)
However I'll probably move postgresql back to jail later.

Bare with me, I'm new to this. This is my first mailserver configuration :D
I gained a lot of knowledge while doing this, however there are still many things I don't know, especially related to security. :D


Active Member

Thanks: 3
Messages: 174

Thanks for the update.. I did see the few notes about what was in jails but was looking for something (perhaps at the top) outlining the game plan (e.g. host + 4 jails with what is in each jail) or similar..

Are you interested in fixing typo's? There are a handful of places with typos such as the portmaster line for nginx (which says something like "portmaster ngnix" instead of "portmaster nginx")... I've also got an issue with one of the Dovecot SQL files with it complaining about the 'host' line not being recognized.. I've got to look into that further.

In my older FreeBSD 7.1 setup I had a jailed mail server with sendmail and the rest of what you've got (dovecot, spamd,excluding roundcube which was in an Apache jail) and all jails were able to talk to one another w/o any issues. I did have to install something from the mail tools area to forward outgoing messages to my jailed sendmail as I couldn't get the other minimal (e.g. host) sendmail to behave for this sort of 'forward' config... I don't recall what tool it was though. I'll have to look when I'm home later if there's interest, although postfix can probably be configured to do that.

I would not put roundcube (or any other PHP based site) outside of a jailed environment.. I've had one break-in due to an issue that was discovered last year (in roundcube) before I ran FreeBSD (was on Linux at the time) and a rootkit was installed and .... (you get the picture).. PHP scripts are what I'm scared most about -- they're an easy back door into a system to take control..