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

01 Server Setup <- Intro -> 03 Postfix SMTPD

We believe in data independence, and support others who want data independence.
This tutorial is partially complete 2025-08-07

This is version 2 and everthing works up to and including Roundcube.

#######################
# FAMP & Posfix Setup #
#######################

FAMP and LAMP stands for FreeBSD Apache MySQL PHP or Linux ... and are used to 
provide dynamic website services with a database backend. They are required for
the mail stack and are used to run various suites like wordpress, phpmyadmin,
postfixadmin, and roundcube webmail. We'll set this up first before starting
on the actual mail stack installation.

pkg update
pkg upgrade

# I have got this working with MariaDB before but the package version of opendmarc
# will try to remove MariaDB and install MySQL, so use the system default for
# compatiblility and simplicity of maintenance.

pkg install mysql80-server mysql80-client ghostscript10 netpbm

sysrc mysql_enable="YES"
service mysql-server start
service mysql-server status
mysql is running as pid 6690.
mysql --version
mysql  Ver 8.0.42 for FreeBSD14.2 on amd64 (Source distribution)
mysql
> ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'secretpasswd';
> GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;
> flush privileges;
> CREATE USER 'admin'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'secretalsouse m';
> GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost';
> flush privileges;
> exit

# Tune mysql server for higher efficiency, 32G memory available with 28 free.

cd /usr/local/etc/mysql
cp my.cnf my.cnf.bak

nano my.cnf
# --- edit or add ---
[mysqld]
...
innodb_buffer_pool_size = 12G
innodb_log_file_size = 1G
max_connections = 500
tmp_table_size = 128M
max_heap_table_size = 128M
sort_buffer_size = 2M
slow_query_log = 1
long_query_time = 1
# skip-name-resolve
lower_case_table_names = 1
character-set-server     = utf8mb4
# collation-server      = utf8mb4_general_ci
collation_server        = utf8mb4_unicode_520_ci

service mysql-server restart
service mysql-server status

# If it doesn't restart check the logs, do not use skip-name-resolve.

tail -f /var/db/mysql/okbsd.com.err

##################
# INSTALL APACHE #
##################

pkg install apache24
sysrc apache24_enable="YES"
service apache24 start
service apache24 status

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

# Install perl PHP module
pkg install p5-DBD-mysql

# Install PHP and PHP modules
pkg install php83 mod_php83 php83-mysqli php83-curl php83-zip php83-gd php83-xml php83-mbstring
pkg install php83-bcmath php83-tokenizer php83-zlib php83-imap php83-bz2 
pkg install php83-pecl-imagick php83-ldap php83-intl php83-gmp php83-pecl-redis

# These values will affect max attachment size in Roundcube

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

nano /usr/local/etc/php.ini
# ---
max_execution_time = 600
max_input_time = 300
max_input_vars = 3000
memory_limit = 256M
post_max_size = 400M
upload_max_filesize = 200M
date.timezone = "America/Los_Angeles"
# ---

sysrc php_fpm_enable="YES"
service php_fpm start
service php_fpm status

apachectl restart
sockstat | grep 9000
www      php-fpm    29241 8   tcp4   127.0.0.1:9000        *:*
www      php-fpm    29240 8   tcp4   127.0.0.1:9000        *:*
root     php-fpm    29239 7   tcp4   127.0.0.1:9000        *:*

# Backup Apache configuation.

cd /usr/local/etc/apache24
cp /usr/local/etc/apache24/httpd.conf /usr/local/etc/apache24/httpd.conf.default
cp /usr/local/etc/apache24/httpd.conf /usr/local/etc/apache24/httpd.conf.bak

# Apache Configuration - choose built in php or php-fpm, you do not need both

# Two host definitions in separate locations might yield unexpected results.

# In FreeBSD the httpd.conf set ServerName to 127.0.0.1 and in ssl module options set the vitual host host to
# 127.0.0.1, so these configurations will not be used except for local connections. Or, remove the Virtual Host
# section in /usr/local/etc/apache24/extra/httpd-ssl.conf and define _default_ in an include file.

# Then define real virtual hosts in include files.

nano /usr/local/etc/apache24/httpd.conf
# ---
Listen 80

LoadModule socache_shmcb_module libexec/apache24/mod_socache_shmcb.so
LoadModule logio_module libexec/apache24/mod_logio.so
LoadModule proxy_module libexec/apache24/mod_proxy.so
LoadModule proxy_fcgi_module libexec/apache24/mod_proxy_fcgi.so
LoadModule ssl_module libexec/apache24/mod_ssl.so
<IfModule !mpm_prefork_module>
        LoadModule cgid_module libexec/apache24/mod_cgid.so
</IfModule>
<IfModule mpm_prefork_module>
        LoadModule cgi_module libexec/apache24/mod_cgi.so
</IfModule>
LoadModule speling_module libexec/apache24/mod_speling.so
LoadModule rewrite_module libexec/apache24/mod_rewrite.so
# For builtin php
# LoadModule php_module         libexec/apache24/libphp.so

ServerAdmin postmaster@okbsd.com
ServerName 127.0.0.1

# change your paths to match your server file location
DocumentRoot "/usr/local/www/okbsd/html"
<Directory "/usr/local/www/okbsd/html">
  # Options -Indexes -MultiViews +FollowSymLinks
  Options -Indexes -MultiViews +SymLinksIfOwnerMatch

<IfModule dir_module>
    DirectoryIndex index.php index.html
</IfModule>

# Add a vhost_combined log format to view by virtual hosts %v
<IfModule log_config_module>
    ...
    # LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
    ...
    CustomLog "/var/log/httpd-access.log" vhost_combined
</IfModule>

# If you want to run cgi's enable the following 3 sections and the handler
<IfModule alias_module>
    ....
    # ScriptAlias /cgi-bin/ "/usr/local/www/apache24/cgi-bin/"
    ScriptAlias /cgi-bin/ "/var/www/okbsd/cgi-bin/"
</IfModule>
    
<Directory "/var/www/okbsd/cgi-bin">
        AllowOverride None
        # Options ExecCGI    
        Options +ExecCGI -Indexes -MultiViews +SymLinksIfOwnerMatch
        Require all granted
    </Directory>

# If you want cgi support, farther down in the file in mime_module
    AAddHandler cgi-script .cgi

# Optional Apache performance tuning, values here are experimental
# and depend on the resources available. These can go after
# ErrorDocument settings

# see mpm config file
# StartServers 40
# MinSpareServers 40
# MaxSpareServers 60

KeepAlive Off
MaxKeepAliveRequests 20
KeepAliveTimeout 5

ServerLimit 512
MaxClients 512
MaxRequestsPerChild 60

# Do enable the following, not so optional.

# Supplemental configuration
...
# Server-pool management (MPM specific)
Include etc/apache24/extra/httpd-mpm.conf

Include etc/apache24/Includes/*.conf
# ---

# Optional Apache mpm fine tuning if more performance is needed, values here are
# experimental and depend on the resources available.

nano /usr/local/etc/apache24/extra/httpd-mpm.conf
# ---
<IfModule mpm_prefork_module>
    StartServers             20
    MinSpareServers          20
    MaxSpareServers          30
    MaxRequestWorkers       250
    MaxConnectionsPerChild  150
</IfModule>
# ---

# You will need either libphp module or php-fpm module.

nano /usr/local/etc/apache24/modules.d/200_mod-php.conf
# ---
<IfModule dir_module>
   DirectoryIndex index.php index.html
   <FilesMatch "\.php$">

	# libphp module
	# SetHandler application/x-httpd-php

	# php-fpm module
	SetHandler "proxy:fcgi://127.0.0.1:9000"

   </FilesMatch>
   <FilesMatch "\.phps$">
     SetHandler application/x-httpd-php-source
   </FilesMatch>
</IfModule>
# ---

# Optional fine tuning for php-fpm only, values here are experimental.

nano /usr/local/etc/php-fpm.d/www.conf
# ---
pm=static
pm.max_children=20
pm.max_requests = 1000
slowlog = /var/log/$pool.log.slow
request_slowlog_timeout = 30
request_terminate_timeout = 600
# ---

service php_fpm restart

# Create website directories and a test php

mkdir -p /usr/local/www/okbsd/html
mkdir -p /usr/local/www/okbsd/mail
echo '<?php print "<!DOCTYPE html lang=\"en\"><html><head><title>Title</title></head><body><h1>Hello World!</h1></body></html>"; ?>' > /usr/local/www/okbsd/html/index.php
echo '<?php phpinfo(); ?>' > /usr/local/www/okbsd/html/info.php

chown root:wheel /usr/local/www
chmod 755 /usr/local/www
chown -R root:www /usr/local/www/okbsd
find /usr/local/www/okbsd -type d -exec chmod 750 {} \;
find /usr/local/www/okbsd -type f -exec chmod 640 {} \;

apachectl configtest
apachectl restart
apachectl status

# Check that Apache and PHP are working.

http://okbsd.com/index.php
http://okbsd.com/info.php

# Create logs executable - an easy wasy to view apache errors

nano ~/bin/logs
# ---
#/bin/sh
clear; echo '' > /var/log/httpd-error.log; tail -f /var/log/httpd-error.log
# ---

chmod 750 ~/bin/logs

# To see errors use the 'logs' command you created in ~/bin,

logs
<refresh your webpage to generate new errors>
<view the errors>
<control-c to exit>

# Configure websites in Apache Configuration - In FreeBSD the default location for website files
# is /usr/local/www/apache24/data but to keep the path short and reduce typing I make a symbolic
# link and use /var/www/sitename/data.

nano /usr/local/etc/apache24/Includes/okbsd.conf
# ---
<VirtualHost _default_:80>
        ServerName okbsd.com
        ServerAlias www.okbsd.com
        ServerAlias 147.135.37.135
        ServerAlias [2604:2dc0:200:187::1]
        ServerAdmin postmaster@okbsd.com
        DirectoryIndex index.php index.html

        DocumentRoot /usr/local/www/okbsd/html/
        <Directory /usr/local/www/okbsd/html/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>

#        # If you want to run cgi's uncomment these and the handler
        ScriptAlias /cgi-bin/ "/usr/local/www/okbsd/cgi-bin/"
        <Directory "/usr/local/www/okbsd/cgi-bin">
                AllowOverride None
                Options +ExecCGI -Indexes -MultiViews +SymLinksIfOwnerMatch
                Require all granted
        </Directory>

</VirtualHost>
# ---

nano /usr/local/etc/apache24/Includes/mail.okbsd.conf
# ---
<VirtualHost *:80>
        ServerName mail.okbsd.com
        ServerAlias smtp.okbsd.com
        ServerAlias imap.okbsd.com
        ServerAlias autoconfig.okbsd.com
        #ServerAlias autodiscover.okbsd.com
        ServerAdmin postmaster@okbsd.com
        DirectoryIndex index.php index.html

        DocumentRoot /usr/local/www/okbsd/html/
        <Directory /usr/local/www/okbsd/html/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>

        Alias /mail "/usr/local/www/okbsd/mail/"
        <Directory /usr/local/www/okbsd/mail/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>

        #RewriteEngine on
        #RewriteCond %{SERVER_NAME} =mail.okbsd.com [OR]
        #RewriteCond %{SERVER_NAME} =mx.okbsd.com
        #RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
# ---

apachectl configtest
apachectl restart
apachectl statis

# Check that Apache and PHP are working.

http://okbsd.com/index.php
http://okbsd.com/info.php

# Remove the info.php script for security

rm /usr/local/www/okbsd/html/info.php

##########################
# Setup SSL with certbot #
##########################

# Certbot needs a virtual host setup or it won't be able to generate the certificates.

pkg install py311-certbot-apache

# When running certbot you may get a warning ... certs can still be created but
# cron could have problems renewing certs.

Unable to read ssl_module file; not disabling session tickets.

# Add the following symbolic link to hide the warning.

ln -s /usr/local/libexec/apache24 /usr/local/etc/apache24/libexec/apache24

# To simplify management create on certificate with all 5 hostnames. For granular control,
# security, and an insigificant performance increase create separate certs for each service.
# With a combined cert web users can view the alternate imap and smtp hostnames in the cert.
# If this is not desirable create a separate cert for these 2 hosts or 1 cert for each host
# (granualar). Obscurity is not security so balance service separation with easy of management
# with 2 certificates 1) web services# 2) mail and webmail services. 

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

# Create a certificate for mail services, and mail. web services.
certbot certonly --apache --agree-tos --redirect --hsts --staple-ocsp --email postmaster@okbsd.com --cert-name mail.okbsd.com -d mail.okbsd.com,smtp.okbsd.com,imap.okbsd.com

# Secure private key certificate files.

chown -R root:www /usr/local/etc/letsencrypt/live
find /usr/local/etc/letsencrypt/live -type d -exec chmod 755 {} \;
find /usr/local/etc/letsencrypt/live -type f -exec chmod 644 {} \;

chown -R root:www /usr/local/etc/letsencrypt/archive
find /usr/local/etc/letsencrypt/archive -type d -exec chmod 755 {} \;
find /usr/local/etc/letsencrypt/archive -type f -exec chmod 644 {} \;
chmod o-rwx /usr/local/etc/letsencrypt/archive/*/privkey*.pem

# Setup a crontab to run certbot and renew all your certs...

crontab -e
0 2 * * 1	/usr/local/bin/certbot renew --quiet && apachectl restart	

# To list certificates
certbot certificates

# To revoke and delete certificates
certbot revoke --cert-name <certname>
certbot delete --cert-name <certname>

# If you leave out 'certonly', certbot may modify your configuration files and add ssl
# configuration files based on the non-ssl configuration. Generally it is better to use
# 'certonly' and manage your own settings. Certbot without 'certonly' adds Rewrite rules
# to okbsd.conf to redirect all non ssl requests to ssl and adds includes to httpd.conf.

# Enable Apache SSL/HTTPS

nano /usr/local/etc/apache24/httpd.conf
# ---
# Secure (SSL/TLS) connections
Include etc/apache24/extra/httpd-ssl.conf
# ---

# make a backup
cd /usr/local/etc/apache24/extra
cp /usr/local/etc/apache24/extra/httpd-ssl.conf /usr/local/etc/apache24/extra/httpd-ssl.conf.bak

nano /usr/local/etc/apache24/extra/httpd-ssl.conf
# ---
SSLRandomSeed startup file:/dev/urandom 512
SSLRandomSeed connect file:/dev/urandom 512
...
<VirtualHost 127.0.0.1:443>
DocumentRoot "/usr/local/www/okbsd/html"
ServerName okbsd.com:443
ServerAdmin postmaster@okbsd.com
ErrorLog "/var/log/httpd-error.log"
TransferLog "/var/log/httpd-access.log"
SSLCertificateFile /usr/local/etc/letsencrypt/live/okbsd.com/fullchain.pem
SSLCertificateKeyFile /usr/local/etc/letsencrypt/live/okbsd.com/privkey.pem
...
CustomLog "/var/log/httpd-ssl_request.log" \
          "%v:%p %h %l %u %t %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
# ---

/usr/local/etc/apache24/Includes/ssl-okbsd.conf
# ---
<IfModule mod_ssl.c>
    SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
    <VirtualHost _default_:443>
        ServerName okbsd.com
        ServerAlias www.okbsd.com
        ServerAlias 147.135.37.135
        ServerAlias [2604:2dc0:200:187::1]
        ServerAdmin postmaster@okbsd.com

	TransferLog "/var/log/httpd-access.log"
        CustomLog "/var/log/httpd-ssl_request.log" \
          "%v:%p %h %l %u %t %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"

        DirectoryIndex index.php index.html

        DocumentRoot /usr/local/www/okbsd/html/
        <Directory /usr/local/www/okbsd/html/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>

        # If you want to run cgi's uncomment these and the handler
        ScriptAlias /cgi-bin/ "/usr/local/www/okbsd/cgi-bin/"
        <Directory "/usr/local/www/okbsd/cgi-bin">
                AllowOverride None
                Options +ExecCGI -Indexes -MultiViews +SymLinksIfOwnerMatch
                Require all granted
        </Directory>


	Include /usr/local/etc/letsencrypt/options-ssl-apache.conf
	SSLCertificateFile /usr/local/etc/letsencrypt/live/okbsd.com/fullchain.pem
	SSLCertificateKeyFile /usr/local/etc/letsencrypt/live/okbsd.com/privkey.pem
	Header always set Strict-Transport-Security "max-age=31536000"
   </VirtualHost>
</IfModule>

nano /usr/local/etc/apache24/Includes/ssl-mail.okbsd.conf
# ---
<IfModule mod_ssl.c>
    SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
    <VirtualHost *:443>
        ServerName mail.okbsd.com
        #ServerAlias smtp.okbsd.com
        #ServerAlias imap.okbsd.com
        #ServerAlias autoconfig.okbsd.com
        #ServerAlias autodiscover.okbsd.com
        ServerAdmin postmaster@okbsd.com
        DirectoryIndex index.php index.html

        DocumentRoot /usr/local/www/okbsd/html/
        <Directory /usr/local/www/okbsd/html/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>

        Alias /mail "/usr/local/www/okbsd/mail/"
        <Directory /usr/local/www/okbsd/mail/>
                Options FollowSymLinks
                AllowOverride All
                Require all granted
        </Directory>

        #RewriteEngine on
        #RewriteCond %{SERVER_NAME} =mail.okbsd.com [OR]
        #RewriteCond %{SERVER_NAME} =mx.okbsd.com
        #RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]

	Include /usr/local/etc/letsencrypt/options-ssl-apache.conf
	Header always set Strict-Transport-Security "max-age=31536000"
	SSLUseStapling on
	SSLCertificateFile /usr/local/etc/letsencrypt/live/mail.okbsd.com/fullchain.pem
	SSLCertificateKeyFile /usr/local/etc/letsencrypt/live/mail.okbsd.com/privkey.pem
    </VirtualHost>
</IfModule>

apachectl configtest
apachectl restart

# test it
https://okbsd.com
https://www.okbsd.com
https://mail.okbsd.com

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

01 Server Setup <- Intro -> 03 Postfix SMTPD