Configure ClamAV
ClamAV will be our anti-virus system and is very easy to set-up! We just need to update some permissions:
sudo adduser clamav amavis
sudo adduser amavis clamav
Configure SpamAssassin
SpamAssassin’s function is given away by it’s name. We will use this for spam filtering our e-mails.
sudo nano /etc/default/spamassassin
[...]
ENABLED=1
[...]
CRON=1
[...]
We need to create a razor config file for it to work:
sudo razor-admin -create
Start the service:
sudo service spamassassin start
Configure Amavisd-New
Amavisd-New will be our interface between our mail transport program (Postfix) and our spam and virus filters (SpamAssassin / ClamAV).
sudo nano /etc/amavis/conf.d/15-content_filter_mode
Uncomment the spam and anti-virus lines.
use strict;
# You can modify this file to re-enable SPAM checking through spamassassin
# and to re-enable antivirus checking.
#
# Default antivirus checking mode
# Uncomment the two lines below to enable it
#
@bypass_virus_checks_maps = (
\%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);
#
# Default SPAM checking mode
# Uncomment the two lines below to enable it
#
@bypass_spam_checks_maps = (
\%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
1; # insure a defined return
Some fairly key definitions are missing because they are not “free” – thanks Ubuntu. Enable / uncomment them!
sudo nano /etc/amavis/conf.d/01-debian
[...]
$unfreeze = ['unfreeze', 'freeze -d', 'melt', 'fcat'];
[...]
$lha = 'lha';
[...]
Amavis needs permissions here:
sudo chmod -R 775 /var/lib/amavis/tmp
sudo service amavis restart
Configure Mail Logging
Because we mounted parts of our file system through EBS volumes we need to update the mail log locations.
sudo nano /etc/rsyslog.d/50-default.conf
[...]
mail.* -/var/log/postfix/mail.log
[...]
mail.info -/var/log/postfix/mail.info
mail.warn -/var/log/postfix/mail.warn
mail.err /var/log/postfix/mail.err
[..]
sudo service rsyslog restart
Configure MySQL
MySQL set-up largely depends upon your use case and virtual hardware configuration. Shown here is an example, working config.
sudo nano /etc/mysql/my.cnf
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
default-character-set = utf8
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
user = mysql
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
skip-external-locking
default-character-set = utf8
collation_server = utf8_general_ci
character_set_server = utf8
bind-address = 127.0.0.1
key_buffer = 512M
max_allowed_packet = 16M
thread_stack = 192K
thread_cache_size = 8
myisam-recover = BACKUP
max_connections = 200
table_cache = 1024
thread_concurrency = 10
query-cache-type = 1
query_cache_limit = 8M
query_cache_size = 128M
log_error = /var/log/mysql/error.log
server-id = 1
expire_logs_days = 10
max_binlog_size = 100M
[mysqldump]
quick
quote-names
max_allowed_packet = 16M
[mysql]
no-auto-rehash
default-character-set = utf8
[isamchk]
key_buffer = 64M
sort_buffer = 64M
read_buffer = 16M
write_buffer = 16M
[myisamchk]
key_buffer = 64M
sort_buffer = 64M
read_buffer = 16M
write_buffer = 16M
!includedir /etc/mysql/conf.d/
If you require MySQL to be available from other locations you will need to comment out the “bind-address = 127.0.0.1″ line.
sudo service mysql restart
Configure PHP
The default installation of PHP on Ubuntu 10.04 is good. There isn’t much wrong with it, but there are always changes that need making. Most notably is the session clean up (which should be exclusively by cron jobs due to potential security risks of giving the web user access) and the configuration of either iconv or mb_string. There are 3 php.ini files, one for Apache (which is the only one I will show here), one for CLI and one for CGI. You should probably make pretty similar changes to all of them.
sudo nano /etc/php5/apache2/php.ini
engine = On
short_open_tag = Off
asp_tags = Off
precision = 14
y2k_compliance = On
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = 100
allow_call_time_pass_reference = Off
safe_mode = Off
safe_mode_gid = Off
safe_mode_include_dir =
safe_mode_exec_dir =
safe_mode_allowed_env_vars = PHP_
safe_mode_protected_env_vars = LD_LIBRARY_PATH
disable_functions =
disable_classes =
expose_php = On
max_execution_time = 330
max_input_time = 360
memory_limit = 128M
error_reporting = E_ALL & ~E_DEPRECATED
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = Off
variables_order = "GPCS"
request_order = "GP"
register_globals = Off
register_long_arrays = Off
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 18M
magic_quotes_gpc = Off
magic_quotes_runtime = Off
magic_quotes_sybase = Off
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
default_charset = UTF-8
doc_root =
user_dir =
enable_dl = Off
file_uploads = On
upload_max_filesize = 12M
max_file_uploads = 20
allow_url_fopen = On
allow_url_include = Off
default_socket_timeout = 60
pdo_mysql.cache_size = 2000
pdo_mysql.default_socket =
define_syslog_variables = Off
SMTP = localhost
smtp_port = 25
mail.add_x_header = On
sql.safe_mode = Off
odbc.allow_persistent = On
odbc.check_persistent = On
odbc.max_persistent = -1
odbc.max_links = -1
odbc.defaultlrl = 4096
odbc.defaultbinmode = 1
ibase.allow_persistent = 1
ibase.max_persistent = -1
ibase.max_links = -1
ibase.timestampformat = "%Y-%m-%d %H:%M:%S"
ibase.dateformat = "%Y-%m-%d"
ibase.timeformat = "%H:%M:%S"
mysql.allow_local_infile = On
mysql.allow_persistent = On
mysql.cache_size = 2000
mysql.max_persistent = -1
mysql.max_links = -1
mysql.default_port =
mysql.default_socket =
mysql.default_host =
mysql.default_user =
mysql.default_password =
mysql.connect_timeout = 60
mysql.trace_mode = Off
mysqli.max_persistent = -1
mysqli.allow_persistent = On
mysqli.max_links = -1
mysqli.cache_size = 2000
mysqli.default_port = 3306
mysqli.default_socket =
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off
mysqlnd.collect_statistics = On
mysqlnd.collect_memory_statistics = Off
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0
sybct.allow_persistent = On
sybct.max_persistent = -1
sybct.max_links = -1
sybct.min_server_severity = 10
sybct.min_client_severity = 10
bcmath.scale = 0
session.save_handler = files
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 0
session.gc_divisor = 1000
session.gc_maxlifetime = 2700
session.bug_compat_42 = Off
session.bug_compat_warn = Off
session.referer_check =
session.entropy_length = 0
session.entropy_file =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 1
session.hash_bits_per_character = 5
url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"
mssql.allow_persistent = On
mssql.max_persistent = -1
mssql.max_links = -1
mssql.min_error_severity = 10
mssql.min_message_severity = 10
mssql.secure_connection = Off
mbstring.language = Neutral
mbstring.internal_encoding = UTF-8
mbstring.http_input = auto
mbstring.http_output = UTF-8
mbstring.encoding_translation = On
mbstring.detect_order = auto
mbstring.substitute_character = none;
mbstring.func_overload = 0
tidy.clean_output = Off
soap.wsdl_cache_enabled = 1
soap.wsdl_cache_dir = "/tmp"
soap.wsdl_cache_ttl = 86400
soap.wsdl_cache_limit = 5
ldap.max_links = -1
sudo service apache2 graceful
Configure Apache and a test site
Apache / PHP configuration again largely depends upon your use case. The following will set up a working system and configure a site at “http://www.domain.com” for you.
Create some directories for vhosts and sites first:
sudo mkdir /etc/apache2/vhosts/db-maintenance/ /etc/apache2/vhosts/catch-all/ /etc/apache2/vhosts/redirects/ /var/www/default /var/www/www.domain.com /var/www/db-maintenance.domain.com
Technically you are not supposed to edit the main apache2.conf file file, but I found some parts of it not to my liking.
sudo nano /etc/apache2/apache2.conf
ServerRoot "/etc/apache2"
LockFile /var/lock/apache2/accept.lock
PidFile ${APACHE_PID_FILE}
Timeout 300
KeepAlive Off
MaxKeepAliveRequests 100
KeepAliveTimeout 15
<IfModule mpm_prefork_module>
StartServers 8
MinSpareServers 5
MaxSpareServers 20
MaxClients 256
MaxRequestsPerChild 4000
</IfModule>
<IfModule mpm_worker_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxClients 150
MaxRequestsPerChild 0
</IfModule>
<IfModule mpm_event_module>
StartServers 2
MaxClients 150
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestsPerChild 0
</IfModule>
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
AccessFileName .htaccess
<Files ~ "^\.ht">
Order allow,deny
Deny from all
Satisfy all
</Files>
DefaultType text/plain
HostnameLookups Off
ErrorLog /var/log/apache2/error.log
LogLevel warn
Include /etc/apache2/mods-enabled/*.load
Include /etc/apache2/mods-enabled/*.conf
Include /etc/apache2/httpd.conf
Include /etc/apache2/ports.conf
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
CustomLog /var/log/apache2/access.log vhost_combined
Include /etc/apache2/conf.d/
#Include /etc/apache2/vhosts/db-maintenance/*.conf
Include /etc/apache2/vhosts/*.conf
Include /etc/apache2/vhosts/redirects/*.conf
Include /etc/apache2/vhosts/catch-all/*.conf
This is the user editable file which we will use to set a couple of options and define a default directory.
sudo nano /etc/apache2/httpd.conf
DocumentRoot "/var/www/default"
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory "/var/www/default">
Options Includes FollowSymLinks MultiViews -Indexes
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
DirectoryIndex index.php index.html index.htm
AddDefaultCharset UTF-8
Next we will create some default site pages so you can check everything is working. The main (www) site first:
sudo nano /var/www/www.domain.com/index.php
<?php phpinfo();
Then the database maintenance site:
sudo nano /var/www/db-maintenance.domain.com/index.php
<?php
echo 'Sorry, this site is currently undergoing scheduled maintenance.
Please check back soon!';
And a default page to catch incorrect sub-domains:
sudo nano /var/www/default/index.php
<?php
echo 'We think that you may have mis-typed!
Were you looking for: <a href="http://www.domain.com">www.domain.com</a>?';
The premise of these files will be that if you visit “http://www.domain.com” you will receive a PHP info screen, but if you visit a none-existent sub-domain (e.g. “http://totally-random.domain.com”) you will end up at the default location (assuming you have set your DNS to a catch all, *, setting in your zone files). If you were to enable the db-maintenance vhosts in the apache2.conf file the idea will be your users will go there instead of your main site. None of this will work yet because we have not set up any vhost files.
The first vhost will be the default (catch-all):
sudo nano /etc/apache2/vhosts/default.conf
<VirtualHost *:80>
DocumentRoot /var/www/default/
ServerName localhost
<Directory "/var/www/default/">
Options -Indexes
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
The only key points to note are the choice of document root (you may want the trailing slash, you may not), the exclusion of indexing (i.e. you don’t want anyone able to view all folder contents) and the ability to over-ride the settings using an .htaccess file should you wish to (I prefer to make those changes at this level). The next vhost will be the main site:
sudo nano /etc/apache2/vhosts/www.domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
DocumentRoot /var/www/www.domain.com
ServerName www.domain.com
<Directory "/var/www/www.domain.com/">
Options -Indexes FollowSymLinks
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Hopefully everything should be fairly obvious and you will note we have used a unique “ServerName” value. Lastly we need the database maintenance vhost:
sudo nano /etc/apache2/vhosts/db-maintenance/www.domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
DocumentRoot /var/www/db-maintenance.domain.com
ServerName www.domain.com
<Directory "/var/www/db-maintenance.domain.com/">
Options -Indexes FollowSymLinks
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /index.php [L,QSA]
</IfModule>
</VirtualHost>
This time we have also included some re-write rules which mean that every address a user tries when maintenance is being carried out (e.g. http://www.domain.com/one-of-your-pages.php) will be handled by just one file, index.php, so you only need to create a single maintenance file. This technique is commonly used in MVC patterns.
Apache needs a restart / reload before these changes are applied:
sudo service apache2 graceful
Now if you test your site using “http://www.domain.com” you should receive a PHP info screen, but if you visit a none-existent sub-domain (e.g. “http://totally-random.domain.com) you should receive the default message. If you enable database maintenance (by un-commenting the relevant line in the apache2.conf and restarting Apache) and visit your site using “http://www.domain.com/anything.php” you should see the maintenance message. If at this point you don’t see these pages you should check your zone settings with your domain registrar or DNS manager.
Obviously it would be better that all incorrectly typed (non-existing) domains were forwarded on to an actual domain of your choosing. This isn’t hard to achieve with a catch-all vhost:
sudo nano /etc/apache2/vhosts/catch-all/domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
ServerName unknown.domain.com
ServerAlias *.domain.com
KeepAlive Off
RewriteEngine On
RewriteRule ^/(.*)$ http://www.domain.com/$1 [R=301,L]
</VirtualHost>
This time we are using a “ServerAlias” in addition to an arbitrary “ServerName” with a re-write rule to forward everything (including a page name and GET data) to the correct location on the actual site.
It would also be better if the domain without a sub-domain (i.e. http://domain.com) redirected to your main site. Again this isn’t hard to achieve with another vhost:
sudo nano /etc/apache2/vhosts/redirects/domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
ServerName domain.com
KeepAlive Off
RewriteEngine On
RewriteRule ^/(.*)$ http://www.domain.com/$1 [R=301,L]
</VirtualHost>
Nothing new in this one, just another re-write rule. Once again we need to reload our Apche settings:
sudo service apache2 graceful
Now if you test your site using a none-existent sub-domain (e.g. “http://totally-random.domain.com) or the raw domain “http://domain.com” you should be 301 redirected to “http://www.domain.com.”
Install PhpMyAdmin (optional)
While we are configuring sites it is a good idea to install this very convenient tool to manage MySQL databases. It will need its own vhost:
sudo nano /etc/apache2/vhosts/database.domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
DocumentRoot /var/www/database.domain.com
ServerName database.domain.com
<Directory "/var/www/database.domain.com/">
Options -Indexes FollowSymLinks
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Get the actual program, extract it and edit its settings. You will probably need to customise this link I’m afraid because it changes often!
cd /var/www
sudo wget "http://sourceforge.net/projects/phpmyadmin/files/phpMyAdmin/3.4.3.2/phpMyAdmin-3.4.3.2-english.tar.gz/download" -O /var/www/phpmyadmin.tar.gz
sudo tar xvfz /var/www/phpmyadmin.tar.gz
sudo rm /var/www/phpmyadmin.tar.gz
sudo mv /var/www/phpMyAdmin-3.4.3.2-english /var/www/database.domain.com
sudo mv /var/www/database.domain.com/config.sample.inc.php /var/www/database.domain.com/config.inc.php
sudo nano /var/www/database.domain.com/config.inc.php
[...]
$cfg['blowfish_secret'] = 'choose a suitably random string here';
[...]
$cfg['Servers'][$i]['extension'] = 'mysqli';
[...]
Clean up any temporary files left by our editor:
sudo rm /var/www/database.domain.com/config.inc.php~
It’s also a good idea to password this directory so we’ll create a general password file for use with our restricted sites:
sudo htpasswd -c /var/www/.htpasswd user
And if you want to add another user:
sudo htpasswd /var/www/.htpasswd another_user
Now we need to tell the phpMyAdmin directory to use this extra authentication:
sudo nano /var/www/database.domain.com/.htaccess
AuthUserFile /var/www/.htpasswd
AuthName "Database Management"
AuthType Basic
require valid-user
Restart Apache to check that everything is working:
sudo service apache2 graceful
If you visit “http://database.domain.com” you should be able to log in and then use your MySQL username (root) and password (root_db_password) to manage your MySQL databases.
Install PostfixAdmin
This is the tool that we will use to manage our e-mail domains / users and it must be installed at this stage because it dictates the structure of the database that Postfix and Dovecot will use for e-mail delivery.
We need to create a MySQL database and user for this. This can either be done through phpMyAdmin or via the MySQL command prompt, either way the 4 commands are the same.
mysql -uroot -proot_db_password
CREATE USER 'vmail'@'%' IDENTIFIED BY 'vmail_db_password';
GRANT USAGE ON * . * TO 'vmail'@'%' IDENTIFIED BY 'vmail_db_password' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;
CREATE DATABASE IF NOT EXISTS `vmail` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
GRANT ALL PRIVILEGES ON `vmail` . * TO 'vmail'@'%';
exit;
This new site needs a vhost too:
sudo nano /etc/apache2/vhosts/mailadmin.domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
DocumentRoot /var/www/mailadmin.domain.com
ServerName mailadmin.domain.com
<Directory "/var/www/mailadmin.domain.com/">
Options -Indexes FollowSymLinks
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Get the actual program, extract it and edit its settings. You may need to customise this link I’m afraid because it changes occasionally!
cd /var/www
sudo wget "http://sourceforge.net/projects/postfixadmin/files/postfixadmin/postfixadmin-2.3.3/postfixadmin-2.3.3.tar.gz/download" -O /var/www/postfixadmin.tar.gz
sudo tar xvfz /var/www/postfixadmin.tar.gz
sudo rm /var/www/postfixadmin.tar.gz
sudo mv /var/www/postfixadmin-2.3.3 /var/www/mailadmin.domain.com
sudo nano /var/www/mailadmin.domain.com/config.inc.php
[...]
$CONF['configured'] = true;
[...]
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'vmail';
$CONF['database_password'] = 'vmail_db_password';
$CONF['database_name'] = 'vmail';
$CONF['database_prefix'] = '';
[...]
$CONF['admin_email'] = 'postmaster@domain.com';
[...]
$CONF['default_aliases'] = array (
'abuse' => 'abuse@domain.com',
'hostmaster' => 'hostmaster@domain.com',
'postmaster' => 'postmaster@domain.com',
'webmaster' => 'webmaster@domain.com'
);
[...]
Now we need to tell the PostfixAdmin directory to use our general .htpasswd file:
sudo nano /var/www/mailadmin.domain.com/.htaccess
AuthUserFile /var/www/.htpasswd
AuthName "E-mail Management"
AuthType Basic
require valid-user
Restart Apache to apply the changes:
sudo service apache2 graceful
You now need to visit “http://mailadmin.domain.com/setup.php” to install the database tables, then enter a password to generate a hash. Please copy this value, but do not proceed with any further prompts. Instead return to the terminal window:
sudo nano /var/www/mailadmin.domain.com/config.inc.php
[...]
$CONF['setup_password'] = 'the password hash';
[...]
Clean up any temporary files left by our editor:
sudo rm /var/www/database.domain.com/config.inc.php~
Now return to “http://mailadmin.domain.com/setup.php”, but don’t refresh, and configure a system admin user using the un-hashed set-up password and whatever details you wish for the account. Then visit “http://mailadmin.domain.com/” and login as your admin user.
Visit the domain list, add domain.com as a new domain and create any e-mail addresses that you want here (e.g. info@domain.com, your_name@domain.com etc). You should also create the four default accounts (webmaster@domain.com, postmaster@domain.com, abuse@domain.com and hostmaster@domain.com) for it too. These can just be aliases to another account if you wish, but it’s a good idea to have them set up.
Create SSL certificates (for Postfix / Dovecot)
In order to accept secure connections we need to generate some self signed certificates for e-mail use. If you wish you can use official certificates, but that is outside the scope of this guide.
cd /home/ubuntu/smtp-certificates
sudo openssl req -new -outform PEM -out smtpd.cert -newkey rsa:2048 -nodes -keyout smtpd.key -keyform PEM -days 3650 -x509
Country Name (2 letter code) [AU]: UK
State or Province Name (full name) [Some-State]: Greater London
Locality Name (eg, city) []: London
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Company Name
Organizational Unit Name (eg, section) []: Trading Name
Common Name (eg, YOUR name) []: subdomain.domain.com
Email Address []: postmaster@domain.com
Create a self-singed root certificate.
sudo openssl req -new -x509 -extensions v3_ca -keyout cakey.pem -out cacert.pem -days 3650
sudo chmod 0640 /home/ubuntu/smtp-certificates/smtpd.key
sudo chmod 0640 /home/ubuntu/smtp-certificates/cakey.pem
For far more detail on this process please visit Ubuntu’s own guide on the subject.
Configure Postfix
Postfix will be our mail server, replacing the ancient Sendmail. Configuration is fairly short, but getting it right is a bit of a black art. To keep our database configuration files contained we’ll put them in a single folder that Postfix owns:
sudo mkdir /etc/postfix/sql
sudo chown postfix:postfix /etc/postfix/sql
sudo chmod 0750 /etc/postfix/sql
What we are essentially doing is using MySQL to determine what happens to the mail. There are 8 different possibilities for this. First up is the relay domains rule, which tells Postfix which domains are to have their mail passed on to another server.
sudo nano /etc/postfix/sql/relay_domains.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT domain FROM domain WHERE domain = '%s' AND backupmx = '1'
We then define a rule for domain forwarding catch-all, e.g. anything@my-domain.com -> anything@mydomain.com:
sudo nano /etc/postfix/sql/virtual_alias_domain_catchall_maps.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT goto FROM alias, alias_domain WHERE alias_domain = '%d' AND address = CONCAT('@', target_domain) AND alias.active = '1' AND alias_domain.active = '1'
We can also define a rule for account forwarding for certain addresses within a domain, e.g. userX@my-domain.com may be forwarded to userX@mydomain.com, but userY@my-domain.com may or may not be forwarded. This query selects the correct mailbox directory for that scenario:
sudo nano /etc/postfix/sql/virtual_alias_domain_mailbox_maps.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT CONCAT(domain, '/', maildir) FROM mailbox, alias_domain WHERE alias_domain = '%d' AND username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = '1' AND alias_domain.active = '1'
Continuing from the previous rule we also need a query to select the account that a user on an aliased domain will be sent to:
sudo nano /etc/postfix/sql/virtual_alias_domain_maps.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT goto FROM alias, alias_domain WHERE alias_domain = '%d' AND address = CONCAT('%u', '@', target_domain) AND alias.active = '1' AND alias_domain.active = '1'
We can also just define simple aliases on a per address basis, e.g. userX@my-domain.com could be forwarded to anything@yahoo.com, or in fact multiple recipients. This query handles those.
sudo nano /etc/postfix/sql/virtual_alias_maps.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT goto FROM alias WHERE address = '%s' AND active = '1'
We need a simple query to select the mailbox for a certain domain:
sudo nano /etc/postfix/sql/virtual_mailbox_domains.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT domain FROM domain WHERE domain = '%s' AND active = '1'
If the mailboxes are defined with disk space quotas we need to be able to access them:
sudo nano /etc/postfix/sql/virtual_mailbox_limit_maps.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT quota FROM mailbox WHERE username = '%s' AND active = '1'
And finally just get a mailbox location for a certain user on a domain:
sudo nano /etc/postfix/sql/virtual_mailbox_maps.cf
user = vmail
password = vmail_db_password
hosts = localhost
dbname = vmail
query = SELECT CONCAT(domain, '/', maildir) FROM mailbox WHERE username = '%s' AND active = '1'
We can then configure the two main Postfix files:
sudo nano /etc/postfix/main.cf
smtpd_banner = $myhostname ESMTP $mail_name
biff = no
append_dot_mydomain = no
myhostname = subdomain.domain.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = subdomain.domain.com, localhost.domain.com, localhost
relayhost =
mynetworks = 127.0.0.0/8
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
message_size_limit = 50000000
# Virtual mail set-up
virtual_mailbox_base = /home/vmail
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
virtual_create_maildirsize = yes
virtual_mailbox_extended = yes
relay_domains = proxy:mysql:/etc/postfix/sql/relay_domains.cf
virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/virtual_mailbox_domains.cf
virtual_alias_maps = proxy:mysql:/etc/postfix/sql/virtual_alias_maps.cf,
proxy:mysql:/etc/postfix/sql/virtual_alias_domain_maps.cf,
proxy:mysql:/etc/postfix/sql/virtual_alias_domain_catchall_maps.cf
virtual_mailbox_maps = proxy:mysql:/etc/postfix/sql/virtual_mailbox_maps.cf,
proxy:mysql:/etc/postfix/sql/virtual_alias_domain_mailbox_maps.cf
virtual_mailbox_limit_maps = proxy:mysql:/etc/postfix/sql/virtual_mailbox_limit_maps.cf
virtual_mailbox_limit_override = yes
virtual_maildir_limit_message = Sorry, that user is over their e-mail quota. Please try again later.
virtual_overquota_bounce = yes
# SASL Related
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_local_domain =
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_sasl2_auth_enable = yes
smtpd_sasl_authenticated_header = yes
smtpd_sasl_tls_security_options = noanonymous
#smtpd_sasl_exceptions_networks = $mynetworks
# TLS Commands
smtpd_tls_auth_only = no
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtpd_tls_cert_file = /home/ubuntu/smtp-certificates/smtpd.cert
smtpd_tls_key_file = /home/ubuntu/smtp-certificates/smtpd.key
smtpd_tls_CAfile = /home/ubuntu/smtp-certificates/cacert.pem
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom
# Include Spam settings
disable_vrfy_command = yes
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks,
reject_non_fqdn_hostname,
reject_invalid_hostname,
permit
smtpd_recipient_restrictions = reject_unauth_pipelining,
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_invalid_hostname,
reject_non_fqdn_hostname,
reject_non_fqdn_sender,
reject_non_fqdn_recipient,
reject_unknown_sender_domain,
reject_unknown_recipient_domain,
reject_rbl_client zen.spamhaus.org,
reject_rbl_client dnsbl.sorbs.net,
reject_rbl_client dsn.rfc-ignorant.org,
reject_rbl_client bl.spamcop.net,
permit
smtpd_error_sleep_time = 1s
smtpd_soft_error_limit = 10
smtpd_hard_error_limit = 20
content_filter = smtp-amavis:[127.0.0.1]:10024
sudo nano /etc/postfix/master.cf
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - n - - smtpd
smtps inet n - - - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o broken_sasl_auth_clients=yes
pickup fifo n - - 60 1 pickup
-o content_filter=
-o receive_override_options=no_header_body_checks
cleanup unix n - - - 0 cleanup
qmgr fifo n - n 300 1 qmgr
tlsmgr unix - - n 1000? 1 tlsmgr
rewrite unix - - - - - trivial-rewrite
bounce unix - - - - 0 bounce
defer unix - - - - 0 bounce
trace unix - - - - 0 bounce
verify unix - - - - 1 verify
flush unix n - - 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - - - - smtp
relay unix - - - - - smtp
-o smtp_fallback_relay=
showq unix n - - - - showq
error unix - - - - - error
retry unix - - - - - error
discard unix - - - - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - - - - lmtp
anvil unix - - - - 1 anvil
scache unix - - - - 1 scache
maildrop unix - n n - - pipe
flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
uucp unix - n n - - pipe
flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail unix - n n - - pipe
flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp unix - n n - - pipe
flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix - n n - 2 pipe
flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman unix - n n - - pipe
flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
${nexthop} ${user}
dovecot unix - n n - - pipe
flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -d ${recipient}
smtp-amavis unix - - - - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20
127.0.0.1:10025 inet n - - - - smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
That is all for Postfix, but before we can use the mail system we need to tweak Dovecot.
Configure Dovecot
Dovecot will be our POP3(S) / IMAP(S) server and handle our SASL authentication. Again, there’s not too much to set up, but if you get a setting or two wrong, it simply won’t work properly!
We are using Dovecot to handle our authentication, and again we will do this from our MySQL tables to ease management:
sudo nano /etc/dovecot/dovecot-sql.conf
driver = mysql
connect = host=127.0.0.1 dbname=vmail user=vmail password=vmail_db_password
user_query = SELECT concat('/home/vmail/', domain, '/', maildir) as home, concat('maildir:/home/vmail/', domain, '/', maildir) as mail, 5000 AS uid, 5000 AS gid, concat('maildir:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'
default_pass_scheme = MD5
password_query = SELECT username as user, password FROM mailbox WHERE username = '%u' AND active = '1'
And then we need to configure Dovecot’s main configuration file:
sudo nano /etc/dovecot/dovecot.conf
protocols = pop3 pop3s imap imaps
log_path = /var/log/dovecot/log
info_log_path = /var/log/dovecot/info
log_timestamp = "%Y-%m-%d %H:%M:%S "
ssl = yes
ssl_cert_file = /home/ubuntu/smtp-certificates/smtpd.cert
ssl_key_file = /home/ubuntu/smtp-certificates/smtpd.key
disable_plaintext_auth = no
login_dir = /var/run/dovecot/login
login_chroot = yes
login_user = dovecot
login_greeting = Dovecot ready.
mail_location = maildir:/home/vmail/%d/%u
mail_debug = no
auth_executable = /usr/lib/dovecot/dovecot-auth
auth_verbose = no
auth_debug = no
auth_debug_passwords = no
protocol imap {
login_executable = /usr/lib/dovecot/imap-login
mail_executable = /usr/lib/dovecot/imap
imap_max_line_length = 65536
}
protocol pop3 {
login_executable = /usr/lib/dovecot/pop3-login
mail_executable = /usr/lib/dovecot/pop3
pop3_uidl_format = %08Xu%08Xv
}
protocol lda {
log_path = /var/log/dovecot/lda
auth_socket_path = /var/run/dovecot/auth-master
postmaster_address = postmaster@domain.com
}
auth default {
mechanisms = plain login
passdb sql {
args = /etc/dovecot/dovecot-sql.conf
}
userdb sql {
args = /etc/dovecot/dovecot-sql.conf
}
socket listen {
master {
path = /var/run/dovecot/auth-master
mode = 0600
user = vmail
group = vmail
}
client {
path = /var/spool/postfix/private/auth
mode = 0660
user = postfix
group = postfix
}
}
}
There are some files designed to be automatically included. They shouldn’t be, but just in case:
sudo mv /etc/dovecot/conf.d/01-dovecot-postfix.conf 01-dovecot-postfix.conf.bak
sudo mv /etc/dovecot/auth.d/01-dovecot-postfix.auth 01-dovecot-postfix.auth.bak
Before we can use the mail system the new settings need to be loaded in.
sudo service dovecot restart
sudo service postfix restart
Configure Squirrelmail (optional)
Squirrelmail is a convenient web-mail client. It will need a vhost file:
sudo nano /etc/apache2/vhosts/mail.domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
DocumentRoot /var/www/mail.domain.com
ServerName mail.domain.com
<Directory "/var/www/mail.domain.com/">
Options -Indexes FollowSymLinks
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
Get the actual program, extract it and edit its settings. You may need to check this link I’m afraid because it may change.
cd /var/www
sudo wget "http://sourceforge.net/projects/squirrelmail/files/stable/1.4.22/squirrelmail-1.4.22.tar.gz/download" -O /var/www/squirrelmail.tar.gz
sudo tar xvfz /var/www/squirrelmail.tar.gz
sudo rm /var/www/squirrelmail.tar.gz
sudo mv /var/www/squirrelmail-1.4.22 /var/www/mail.domain.com
Squirrelmail needs a private folder, so we need to create and secure it:
sudo mkdir /var/www/private
sudo mkdir /var/www/private/mail.domain.com
sudo mkdir /var/www/private/mail.domain.com/data
sudo mkdir /var/www/private/mail.domain.com/attachments
sudo chown -R root:www-data /var/www/private/mail.domain.com
sudo chmod -R 0730 /var/www/private/mail.domain.com
We can now configure it. There is a Perl script to do this, but I find it faster just to edit the settings manually.
sudo mv /var/www/mail.domain.com/config/config_default.php /var/www/mail.domain.com/config/config.php
sudo nano /var/www/mail.domain.com/config/config.php
[...]
$org_name = 'Your Website Name';
[...]
$domain = 'domain.com';
[...]
$smtp_auth_mech = 'plain';
[...]
$data_dir = '/var/www/private/mail.domain.com/data/';
[...]
$attachment_dir = '/var/www/private/mail.domain.com/attachments/';
[...]
$default_charset = 'utf-8';
[...]
Clean up any temporary files left by our editor:
sudo rm /var/www/mail.domain.com/config/config.php~
To apply the changes Apache needs to have the data reloaded:
sudo service apache2 graceful
If you visit “http://mail.domain.com” you will be able to send and receive e-mails. Of course this is no replacement for a mail client, but it’s useful to have on occasion!
Configure Subversion (optional)
We will bring your website (www.domain.com) under version control. This step will create the repositories and set their permissions to allow the web user access.
It’s unlikely that you’ll want anyone to be able to access your files so we can generate a password file for SVN access:
sudo htpasswd -c /svn-repositories/passwd svn-username
We can then create a folder, a repository within the folder and import our files:
sudo mkdir /svn-repositories/www.domain.com
sudo svnadmin create /svn-repositories/www.domain.com
sudo svn import /var/www/www.domain.com file:////svn-repositories/www.domain.com
This repository needs to be owned by the web user and be granted certain permissions:
sudo chown -R www-data:www-data /svn-repositories/www.domain.com
sudo chmod -R g+rws /svn-repositories/www.domain.com
We can then remove our existing site directory and recreate it from our repository:
cd /var/www
sudo rm -rf /var/www/www.domain.com/
sudo svn co file:///svn-repositories/www.domain.com
sudo chown -R www-data:www-data /var/www/www.domain.com/
We can also create a trigger so that every time you commit a change it goes live. This probably wants some modification for production use, i.e. Local -> Staging -> Production with the later step conducted manually.
sudo nano /svn-repositories/www.domain.com/hooks/post-commit
#!/bin/bash
cd /var/www/www.domain.com
/usr/bin/svn update
This also needs the correct permissions to run:
sudo chown www-data:www-data /svn-repositories/www.domain.com/hooks/post-commit
sudo chmod +x /svn-repositories/www.domain.com/hooks/post-commit
We also need to add this repository to a vhost file so that you can check it out. I would suggest a new one for this purpose. Multiple locations can be added, but for now we just have one.
sudo nano /etc/apache2/vhosts/svn.domain.com.conf
<VirtualHost *:80>
ServerAdmin webmaster@domain.com
DocumentRoot /var/www/default/
ServerName svn.domain.com
<Directory "/var/www/default/">
Options -Indexes FollowSymLinks
AllowOverride FileInfo
Order allow,deny
Allow from all
</Directory>
<Location /svn/www.domain.com>
DAV svn
SVNPath /svn-repositories/www.domain.com
AuthType Basic
AuthName "www.domain.com subversion repository"
AuthUserFile /svn-repositories/passwd
Require valid-user
</Location>
</VirtualHost>
Before this can be tested Apache needs to be reloaded:
sudo service apache2 graceful
You can now check out the repository from “http://svn.domain.com/svn/www.domain.com” using the command line or client (Linux / OSX) or a client such as Tortoise SVN on Windows. You will need to validate using the username / password that you created in this step. If you would rather use the .htpasswd file used for the database and mail admin just update the vhost file with that location.
Download this guide
To make this guide more useful I've added a feature to allow you to save it offline in a simple HTML format. If you have not customised this guide to your own values you may wish to do so here before you download it. There are a few options here:
Guide contents
- Hosting a website on Amazon EC2 - The goals and assumptions of this guide
- Preparing required tools - Create an AWS account, configure Elastic Fox and add an SSH tool
- Customise this guide - Allow all commands to be tailored to you (optional)
- Core software installation - Install some common software to the server image
- Depending upon your chosen configuration there is a choice here:
- Create and attach new EBS volumes - New server that you may want to split in future
- Attach existing EBS volumes - If you have used this guide before and have EBS volumes
- No attached EBS volumes - If you are not using the cloud or don't want to use them
- Depending upon your chosen configuration there is another choice here:
- Software Configuration - Set up the system to work as a multi-function server (from 5a or 5c)
- Software Configuration from existing EBS volumes - Use settings from EBS volumes (from 5b)
- Backing up and clean up - Configure Crons, log rotation etc