Setup Email Server From Scratch On FreeBSD #2 - 02 LAMP Install

01 Server Setup <- Intro -> 03 Postfix SMTPD

We believe in data independence, and support others who want data independence.
Debian Email From Scratch version 2 finished 2025-07-30.

We are still adding to it but it all works!



##############
# FAMP Setup #
##############

LAMP stands for Linux Apache MySQL PHP ... a stack of servers and languages used
to provide dynamic database driven website services. They are required for the
mail stack and are used to run postfixadmin, and roundcube webmail. We'll set
this up first before starting on the actual mail stack installation.

###################
# INSTALL MARIADB #
###################
apt update -y && apt upgrade -y
apt-get install mariadb-server mariadb-client
systemctl enable mariadb
systemctl start mariadb
systemctl status mariadb

mysql_secure_installation

You can leave the mysql root password blank if you are concerned it might be
forgotten but then the database is only as secure as the root account. In
this case it is a good idea to use unix socket and disallow root remotely.

Do not restrict mysql to unix socket if remote access is needed
Do restrict root login to local connections

If you do not have any untrusted users on the system you do not need reset
default root password and can leave it blank.

Switch to unix_socket authenitcaiton: n
Change root password: n
Remove anonymous users: Y
Disallow root login remotely: Y
Remove test database and access to it: Y
Reload privilege tables now: Y

Login as root user, for the full command there is no space after -p and before the password
If your password was blank just use 'mysql'
mysql -u root -p<myqlrootpassword>

mysql

Create an admin account with a password as below, replace secretpassword with your password
GRANT ALL ON *.* TO 'admin'@'localhost' IDENTIFIED BY 'secretpassword' WITH GRANT OPTION;
FLUSH PRIVILEGES;

You can use either
For collation general_ci is faster but may not sort as well for all languates, unicode_520 is more precise

collation-server = utf8mb4_unicode_520_ci
collation-server = utf8mb4_general_ci


nano /usr/local/etc/mysql/conf.d/server.cnf
[server] lower_case_table_names=1 character-set-server = utf8mb4 # collation-server = utf8mb4_unicode_520_ci collation-server = utf8mb4_general_ci # this is only for the mysqld standalone daemon [mysqld] lower_case_table_names=1 ..... farther down character-set-server = utf8mb4 # collation-server = utf8mb4_unicode_520_ci collation-server = utf8mb4_general_ci ... farther ... [mariadb] lower_case_table_names=1 character-set-server = utf8mb4 # collation-server = utf8mb4_unicode_520_ci collation-server = utf8mb4_general_ci

systemctl restart mariadb

Here is how to create a database and a user with access to only that database

mysql
create database mydatabase;
GRANT ALL ON mydatabase.* TO 'myuser'@'localhost' IDENTIFIED BY 'mysecretpassword' WITH GRANT OPTION;
flush privileges;

systemctl restart mariadb

INSTALL PERL DBD::mysql - usefull if running perl scripts or perl cgi's that need access to modify or repair
the database. If you only write php scripts this is optional.

apt-get install libdbd-mysql-perl

INSTALL APACHE WEB SERVER

apt-get install apache2 -y
systemctl start apache2
systemctl enable apache2
systemctl status apache2

INSTALL PHP - minimal install

apt install php libapache2-mod-php php-cli php-fpm php-json php-mysql php-zip
apt install php-gd php-mbstring php-curl php-xml php-pear php-bcmath

Configure PHP /etc/php/8.2/apache2/php.ini /etc/php/8.2/cli/php.ini
If your PHP version is different replace 8.2 with current version below...
These values can affect max upload and file size when sending attachments with roundcube.
Use fpm only if using php with nginx, we are using apache not nginx.

nano /etc/php/8.2/apache2/php.ini
nano /etc/php/8.2/cli/php.ini
max_execution_time = 600 max_input_time = 1000 max_input_vars = 3000 memory_limit = 256M post_max_size = 48M upload_max_filesize = 32M

a2enconf php8.2-fpm
a2enmod proxy_fcgi setenvif rewrite ssl
if you want cgi enabled
a2enmod cgi

systemctl reload apache2

Use a web browser and go to ...
http://okdeb.com
It Works!

Make a virtual hosts for website and for mail hosts, 2 configuration files 4 virtual containers.

Create web server directories and test files
mkdir -p /var/www/okdeb/html
mkdir -p /var/www/okdeb/mail
mkdir -p /var/www/okdeb/mx
mkdir -p /var/www/okdeb/cgi-bin
echo '<?php print "<!DOCTYPE html lang=\"en\"><html><head><title>It Works!</title></head><body><h1>PHP Works!</h1></body></html>"; ?>' > /var/www/okdeb/html/test.php
echo '<?php print "<!DOCTYPE html lang=\"en\"><html><head><title>It Works!</title></head><body><h1>PHP Works!</h1></body></html>"; ?>' > /var/www/okdeb/mail/test.php
echo '<?php print "<!DOCTYPE html lang=\"en\"><html><head><title>It Works!</title></head><body><h1>PHP Works!</h1></body></html>"; ?>' > /var/www/okdeb/mx/test.php
echo '<?php phpinfo(); ?>' > /var/www/okdeb/html/info.php

chmod 750 /var/www/okdeb
chmod 750 /var/www/okdeb/html
chmod 750 /var/www/okdeb/mail
chmod 750 /var/www/okdeb/mx
chmod 750 /var/www/okdeb/cgi-bin
chmod -R o-rwx /var/www/okdeb/html
chmod -R o-rwx /var/www/okdeb/mail
chmod -R o-rwx /var/www/okdeb/mx
chmod -R o-rwx /var/www/okdeb/cgi-bin
chown -R root:www-data /var/www/okdeb


These VirtualHost definitions can be separated or combined with all 4 VirtualHost
definitions together. With a few hosts separate files would be better, but with
a large number of virtual hosts fewer files is preferable.

A valid http configuration is needed to generate lecerts, and apache in won't run
in https mode without the certs. Setup the virtual http hosts, generate the
letsencrypt certificates, then enable https virtual hosts with the certificates.

cd /etc/apache2/sites-available
nano /etc/apache2/sites-available/okdeb.conf
<VirtualHost _default_:80> ServerName okdeb.com ServerAlias www.okdeb.com ServerAlias 15.204.113.148 ServerAlias [2604:2dc0:202:300::3645] ServerAdmin postmaster@okdeb.com CustomLog ${APACHE_LOG_DIR}/access.log vhost_combined ErrorLog ${APACHE_LOG_DIR}/error.log DirectoryIndex index.php index.html DocumentRoot /var/www/okdeb/html/ <Directory /var/www/okdeb/html/> Options FollowSymLinks AllowOverride All Require all granted </Directory> # # If you want to run cgi's uncomment these and the handler ScriptAlias /cgi-bin/ "/var/www/okdeb/cgi-bin/" <Directory "/var/www/okdeb/cgi-bin"> AllowOverride None Options +ExecCGI -Indexes -MultiViews +SymLinksIfOwnerMatch Require all granted </Directory> </VirtualHost>

nano /etc/apache2/sites-available/mx.okdeb.conf
<VirtualHost *:80> ServerName mx.okdeb.com ServerAlias mail.okdeb.com ServerAlias autoconfig.okdeb.com ServerAlias autodiscover.okdeb.com ServerAdmin postmaster@okdeb.com CustomLog ${APACHE_LOG_DIR}/access.log vhost_combined ErrorLog ${APACHE_LOG_DIR}/error.log DirectoryIndex index.php index.html DocumentRoot /var/www/okdeb/mx/ <Directory /var/www/okdeb/mx/> Options FollowSymLinks AllowOverride All Require all granted </Directory> Alias /mail "/var/www/okdeb/mail/" <Directory /var/www/okdeb/mail/> Options FollowSymLinks AllowOverride All Require all granted </Directory> #RewriteEngine on #RewriteCond %{SERVER_NAME} =mail.okdeb.com [OR] #RewriteCond %{SERVER_NAME} =mx.okdeb.com #RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] </VirtualHost>

cd /etc/apache2/sites-enabled
rm /etc/apache2/sites-enabled/000-default.conf
rm /etc/apache2/sites-enabled/default-ssl.conf
ln -s ../site-available/okdeb.conf
ln -s ../site-available/mx.okdeb.conf

apachectl restart

Test
http://okdeb.com/test.php
http://okdeb.com/info.php
http://mail.okdeb.com/test.php
http://mx.okdeb.com/mail/test.php

##########################
# Setup SSL with certbot #
##########################
you will need a virtual host for certbot

apt install certbot python3-certbot-apache

Certbot needs a virtual host setup for the domain, but we added that above.
Certbot may need module rewrite to be enabled.
Cron did not appear to be installed and certbot will need cron
apt-get install cron
systemctl status cron
systemctl enable cron.service
systemctl start cron.service

Create certificates for web and mail hosts, each cert has 2 hosts

New versions of postfix and Dovecot support SNI, server name indication so
we'll set this up in case we want to host multiple mail domains.
Here are some suggested names ...

<ul>
<li> mail.domain.tld
<li> imap.domain.tld
<li> smtp.domain.tld
<li> mx.domain.tld
<li> mx1.domain.tld
<li> mx1.domain.tld
</ul>

Autoconfig and autodiscover are used to help mail clients automatically
discover mail server setttings. The autodiscover SRV records must be on a
https web host amd can be directly to the main mail servers hostname. We'll
setup autoconfig and autodiscover at the end of setting up Dovecot and
Thunderbird mail client but thinking ahead we'll generate the certs and setup
the directories now.

If website and mail web services are put together it can reduce the number of
directories and certs. Separating regular web and mail services allows
administrators to change one without breaking the other. It is good to reduce
the attack surface somewhat by keeping things like postfixadmin and roundcube
out of the main website structure and only enabling https.

You could make just one certificate for everything, or separate as shown here.

The simplest solution is to point all the MX records for multiple domains to
one or two "main" hostnames. To host multiple virtual domains with a cert for
only 2 hostnames set the MX records in the virtual domain(s) to the
hostname(s) of the primary mail server. The A and AAAA records for these mx
hosts can be the same or separate machines, we'll add a second here pointing
to the same IPv4 and IPv6 address as a placeholder for setting up a backup mx
server at a later date. The second MX record pointing to the same server also
reduces delivery delay when postgrey and postscreen spam filtering and enabled.

Example DNS for okdeb.com - mail sent to user@okdeb.com
MX @ mx.okdeb.com 0
MX @ mail.okdeb.com 10

Example DNS for domain2.com - mail sent to user@domain2.com
MX @ mx.okdeb.com 0
MX @ mail.okdeb.com 10

Example DNS for domain3.net - mail sent to user@domain3.net
MX @ mx.okdeb.com 0
MX @ mail.okdeb.com 10

In this case you could ... and use this one cert for everything, eg in apache
configuation and /etc/postfix/main.cf and /etc/dovecot/conf.d/10-ssl.conf

certbot certonly -a apache --agree-tos --redirect --hsts --staple-ocsp --email postmaster@okdeb.com --cert-name okdeb.com -d okdeb.com,www.okdeb.com,mx.okdeb.com,mail.okdeb.com,autodiscover.okdeb.com

You only have one mail host, 2 MX records, and 1 certificate for everything. Autoconfig
and autodiscover point to one mx.host and only this host shows up in the imap and
smtp connection parameters for the mail client(s).

To use separate mx hosts each domain, mx records will be pointed to thier own
mx hostname. These MX hosts will also need their own A and AAAA records. In
this case the postfix and dovecot cert will need all mx hostnames for all
domains serviced. Customers like this option better as each domain is personalized,
but setup is more complicated.

Example DNS for okdeb.com - mail sent to user@okdeb.com
MX @ mx.okdeb.com 0
MX @ mail.okdeb.com 10

Example DNS for domain2.com - mail sent to user@domain2.com
MX @ mx1.domain2.com 0
MX @ mx2.domain2.com 10

Example DNS for domain3.net - mail sent to user@domain3.net
MX @ imap.domain3.net 0
MX @ smtp.domain3.net 10

This only affects what hostnames show up in the mail client, eg Thunderbird under
connection settings. Autoconfigure can be customized for each domain.

Configure for separate mx hosts for each virtual domain, start with the first
mx host. We have switch from using a common cert to using SNI for postfix and
dovecot. We'll cover virtual domains later.

Make regular website cert

certbot certonly -a apache --agree-tos --redirect --hsts --staple-ocsp --email postmaster@okdeb.com --cert-name okdeb.com -d okdeb.com,www.okdeb.com

Make cert for postfix, dovecot, autodiscoer, and mx/mail website.

certbot certonly -a apache --agree-tos --redirect --hsts --staple-ocsp --email postmaster@okdeb.com --cert-name mx.okdeb.com -d mx.okdeb.com,mail.okdeb.com,autodiscover.okdeb.com

When you want to add or remove hosts from a cert rerun certbot without the unwanted
hostname or with the new hostnames, certbot will prompt to update the cert.

certbot certonly -a apache --agree-tos --redirect --hsts --staple-ocsp --email postmaster@okdeb.com --cert-name mx.okdeb.com -d mx.okdeb.com,smtp.okdeb.com,imap.okdeb.com,autodiscover.okdeb.com

List certificates
certbot certficates

Revoke and delete certificates
certbot revoke --cert-name <certname>
certbot delete --cert-name <certname>

Make sure the auto renew script is being run by cron
cat /etc/cron.d/certbot

Setup SSL for website and mail website

nano /etc/apache2/sites-available/ssl-okdeb.conf
<VirtualHost _default_:443> ServerName okdeb.com ServerAlias www.okdeb.com ServerAlias 15.204.113.148 ServerAlias [2604:2dc0:202:300::3645] ServerAdmin postmaster@okdeb.com CustomLog ${APACHE_LOG_DIR}/access.log vhost_combined ErrorLog ${APACHE_LOG_DIR}/error.log DirectoryIndex index.php index.html SSLEngine on SSLCertificateFile /etc/letsencrypt/live/okdeb.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/okdeb.com/privkey.pem DocumentRoot /var/www/okdeb/html/ <Directory /var/www/okdeb/html/> Options FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch "\.(?:cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> # If you want cgi ScriptAlias /cgi-bin/ "/var/www/okdeb/cgi-bin/" <Directory "/var/www/okdeb/cgi-bin"> # AllowOverride All AllowOverride None Options +ExecCGI -Indexes -MultiViews +SymLinksIfOwnerMatch Require all granted </Directory> </VirtualHost>

nano /etc/apache2/sites-available/ssl-mx.okdeb.conf
<VirtualHost *:443> ServerName mx.okdeb.com ServerAlias mail.okdeb.com ServerAlias autoconfig.okdeb.com ServerAlias autodiscover.okdeb.com ServerAdmin postmaster@okdeb.com CustomLog ${APACHE_LOG_DIR}/access.log vhost_combined ErrorLog ${APACHE_LOG_DIR}/error.log DirectoryIndex index.php index.html SSLEngine on SSLCertificateFile /etc/letsencrypt/live/mx.okdeb.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/mx.okdeb.com/privkey.pem DocumentRoot /var/www/okdeb/mx/ <Directory /var/www/okdeb/mx/> Options FollowSymLinks AllowOverride All Require all granted </Directory> Alias /mail "/var/www/okdeb/mail" Alias "/Autodiscover/Autodiscover.xml" "/var/www/okdeb/mail/Autodiscover.xml/index.php" <Directory /var/www/okdeb/mail/> DirectorySlash Off Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch "\.(?:cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /var/www/okdeb/cgi-bin> SSLOptions +StdEnvVars </Directory> </VirtualHost>

Enable https hosts and restart apache

cd /var/apache2/site-enabled
ln -s ../sites-available/ssl-okdeb.conf
ln -s ../sites-available/ssl-mx.okdeb.conf
apachectl restart

Test - we test https://mx.okdeb.com/mail to confirm php is enabled for Autodiscover.xml/index.php
https://okdeb.com/test.php
https://okdeb.com/info.php
https://mx.okdeb.com/test.phyp
https://mx.okdeb.com/mail/test.php

For security remove the php script

rm /var/www/okdeb/html/info.php

It is a good idea to remove the other php scripts as well before going live.

/var/www/okdeb/html/test.php
/var/www/okdeb/mail/test.php
/var/www/okdeb/mx/test.php

Optionally create and test cgi

nano /var/www/okdeb/cgi-bin/test.cgi
#!/usr/bin/perl print "Content-type: text/html\n\n"; print "Hello this a cgi script that can't parse variables.\n";

chmod 755 /var/www/okdeb/cgi-bin/test.cgi

Test your cgi if using perl cgi's
http://okdeb.com/cgi-bin/test.cgi

Remove it for security
rm /var/www/okdeb/cgi-bin/test.cgi

Postfix is a popular smtp server and we'll continue with postfix setup on the next page...

01 Server Setup <- Intro -> 03 Postfix SMTPD