Mail servers are technical beasts. Even the experts recommend you employ professional mail services over attempting to do it yourself. Sure, its easy to get postfix and dovecot to do basic mail sending, yet include a database, mail management, online mail reader, insecure and secure ports all working, sending and receiving mail correctly? Think again.

You could pay for cPanel, Plesk or such commercial software which works out of the box as an all-in-one mail / web server. Look closer though at those solutions, they too do not employ correct mail protocols, nor do they use the best or latest web server technologies. They pass the basics test to send and receive email, and launch websites. <b>My problem</b>… they don’t work with standalone NGINX.

Enter iRedMail – a free, expert solution with a paid pro optional version. A complete free email solution that caters NGINX as the sole web server whilst using the latest technologies. Don’t get me wrong, iRedMail out of the box needs minor tweaking. The web server essentials are easily upgraded, and that’s what makes it so much better than most solutions.

iRedMail requires a fresh server to install upon. If you plan on using iRedMail anti-spam / virus scanning components, you require a server with a minimum of 2GB RAM. I turn all that off, as that’s what anti-virus does at our computer, and is often better. Blocking spam and email misuse is easily achieved with SPF, DMARC and DKIM, which we can include without using RAM hungry software.

When you disable the noise, a cheap single core VPS (Linode or Digital Ocean) with 1Gb RAM and SSD will suffice to run 2,000 daily users, email, NGINX and the latest database and PHP versions.


DNS requires you to set an A and MX record for your FQDN. These need to be set well in advance to allow time to propagate. You need to set SPF for your FQDN and each email domain you add to improve mail deliverability. Your FQDN requires DMARC too (follow the link). Both will dramatically increase delivery to inboxes. You can DNS set an approval for Gmail via Google Postmaster (i.e. set your FQDN which is the mailing server).

MX:, value =

Set an SPF for each hosting domain to use the IP’s of the FQDN:

TXT, VALUE = v=spf1 ip4:123.456.789.0 ip6:2404:5874:3:0:576:3eff:hge5:ba47 -all

To reduce your risk of email abuse, including the www version, if you don’t use it to mail from, add an SPF for each non-mailing domain version to ensure any mail sent is rejected as fraudulent spam.

TXT: www, value = v=spf1 -all

Add a HELO SPF for the FQDN:

TXT VALUE v=spf1 a -all

Add an MX SPF for your qualified domain:

TXT, VALUE = v=spf1 mx -all

DMARC is somewhat different, yet easy. Read the previous link. Start by sending reports to your email using p=none; to identify any issues. If all is well, work towards p=reject; as the default setting to stop all hijack attempts using your mailing addresses.

Reverse DNS typically takes the longest and is a MUST have for a mail server. Your mail server won’t work without reverse DNS having propagated fully. I would honestly leave the following until 24hrs after you have all DNS in place, especially reverse DNS, otherwise you will install this and mail won’t work, you will wonder why, screw with things that aren’t broken, as it all comes back to DNS for a mail server.




You must set a Fully Qualified Domain Name (FQDN), so whatever you choose, add it to your etc/hosts and set the hostname too. Your hostname is a unique name, and for mail, is what you use as the FQDN too. So if you call your computer “my” we make the FQDN sub-domain as “my” too. The order is: IP FQDN hostname.

nano /etc/hosts
123.456.789.123 my

Set hostname

hostnamectl set-hostname my

Check that both are set correctly:

hostname -f

Dependant upon your Centos install, SELinux must be disabled. Check status first:


If returned as “disabled” then do nothing. If enabled, you need to disable it. Change “enforcing” to either “permissive” or “disabled” to ensure compatibility:

nano /etc/sysconfig/selinux

Configure our system time and synchronisation:

timedatectl set-timezone America/Los_Angeles
yum install -y chrony
systemctl enable chronyd
systemctl start chronyd

Before we install our primary programs, we update and reboot:

yum -y update && reboot

Web / Email Server

iRedMail will not install correctly with third party repositories, which means we leave those until last and update everything else before or in preparation for iRedMail to use YUM to install.

We prepare both NGINX and MariaDB for iRedMail to install. I use the latest stable, you can use mainline if you choose. Grab the relevant code from NGINX and MariaDB sites respectively.

nano /etc/yum.repos.d/nginx.repo
name=nginx repo



nano /etc/yum.repos.d/MariaDB.repo


# MariaDB 10.2 CentOS repository list - created 2017-07-22 09:57 UTC
name = MariaDB
baseurl =


Now we update OpenSSL to the latest version for HTTP2 compatibility and install some needed software:

yum -y install wget bzip2 yum-utils gcc git
cd /usr/src
tar -zxf openssl-1.0.2-latest.tar.gz
cd openssl-1.0.2l
make test
make install
mv /usr/bin/openssl /root/
ln -s /usr/local/ssl/bin/openssl /usr/bin/openssl


openssl version

Download iRedMail version (check iRedMail website). I have chosen the specific version below over the latest, as the developer has done some super weird stuff with NGINX file structure, which sucks. This requires an extra step prior to running the bash:

tar xjf iRedMail-0.9.6.tar.bz2
rm -rf iRedMail-0.9.6.tar.bz2
cd iRedMail-0.9.6
nano runtime/install.status
Add to and save: export status_check_new_iredmail=DONE

Follow through the full installation process at iRedMail Centos instructions. Ensure you choose NGINX and MariaDB.


You now have a fully functional email and web server using the latest NGINX and MariaDB, with PHP-FPM 5.x and default anti-virus / anti-spam setup.

All your important information can be found at:

cd iRedMail-0.9.6

Your most important first act is to change your iredadmin password to something more secure than it would allow during the installation process.


Go to: admins > edit account profile (cog icon) > password (top right) > change it to something secure.

Remove Anti-Virus / Anti-Spam

If you want to disable the RAM hungry scanning, it’s super simple and only requires two lines to be commented, then we shutdown and disable the services:

nano /etc/postfix/
content_filter = smtp-amavis:[]:10024

Save and Close

nano /etc/postfix/


-o content_filter=smtp-amavis:[]:10026

Save and Close

systemctl stop [email protected]
systemctl stop amavisd spamassassin
systemctl disable [email protected]
systemctl disable amavisd spamassassin


Update To PHP 7.1

PHP 7.1.x is not supported by official repositories, which is why we add it after the fact. Download and install Remi-repository:

yum -y install remi-release-7.rpm epel-release-latest-7.noarch.rpm bc

First we remove PHP 5.x and then install PHP 7.x. You can backup PHP if you wish, but I have not allocated for it here as I give you the new php-fpm .conf file to paste:

systemctl stop php-fpm
yum -y remove php-fpm php-cli php-common


yum-config-manager --enable remi-php71
yum -y --enablerepo=remi,remi-php71 install php-fpm php-common php-bcmath  php-cli php-dba php-devel php-embedded php-gd php-imap php-intl php-ldap php-mbstring php-mcrypt php-mysqlnd php-opcache php-pdo php-pear php-pdo_dblib php-pecl-apcu php-pecl-mongodb php-pecl-redis php-pgsql php-phpdbg php-process php-pspell php-recode php-snmp php-soap php-tidy php-xml php-xmlrpc

systemctl enable php-fpm

We must update our pool file to work with iRedAdmin. I included a full version to copy and paste straight into it as a blank version (delete existing content) or you work through it and make your own changes to your file:

nano /etc/php-fpm.d/www.conf
----- Paste Below -----

;prefix = /path/to/pools/$pool
user = nginx
group = nginx
listen = /var/run/php-fpm/php-fpm.socket
;listen.backlog = 511
listen.owner = nginx = nginx
listen.mode = 0660
;listen.acl_users = apache,nginx
;listen.acl_groups =
listen.allowed_clients =
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
;pm.process_idle_timeout = 10s
;pm.max_requests = 500
;pm.status_path = /status
;ping.path = /ping
;ping.response = pong
;access.log = log/$pool.access.log
;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
slowlog = /var/log/php-fpm/www-slow.log
;request_slowlog_timeout = 0
;request_terminate_timeout = 0
;rlimit_files = 1024
;rlimit_core = 0
;chroot =
;chdir = /var/www
;catch_workers_output = yes
;clear_env = no
;security.limit_extensions = .php .php3 .php4 .php5 .php7
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f [email protected]
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 128M
php_value[session.save_handler] = files
php_value[session.save_path]    = /var/lib/php/session
php_value[soap.wsdl_cache_dir]  = /var/lib/php/wsdlcache
;php_value[opcache.file_cache]  = /var/lib/php/opcache

Start PHP-FPM:

systemctl start php-fpm

All done. You now have the latest stable PHP 7.1.x PHP-FPM.

Install Lets Encrypt

To avoid signed certificate problems when connecting mail clients, lets install, automate and use Lets Encrypt if you want a free SSL solution. Please read below notes, as Lets Encrypt is not considered a trusted certificate authority for signing email, which can cause bounces for legitimate email requiring trusted TLS connection.

git clone /opt/letsencrypt
cd /opt/letsencrypt


./letsencrypt-auto certonly --standalone

Your certificates needed will be located at:

  • /etc/letsencrypt/live/mydomain/fullchain.pem
  • /etc/letsencrypt/live/mydomain/privkey.pem
  • /etc/letsencrypt/live/mydomain/cert.pem
  • /etc/letsencrypt/live/mydomain/chain.pem

You need to change the old for the new in the following locations. Before restarting NGINX, you should always test it first, to remove public services downtime:

nano /etc/nginx/conf.d/00-default.conf
nano /etc/postfix/
nano /etc/dovecot/dovecot.conf


nginx -t

If OK, continue:

systemctl restart nginx
systemctl restart postfix
systemctl restart dovecot

To automate we create a cron job to run each month. The below is set to run at 4am on the first of each month:


nano /etc/



systemctl stop nginx
/root/.local/share/letsencrypt/bin/letsencrypt renew --agree-tos
systemctl start nginx


chmod +x /etc/
crontab -e
  0  4  1  *  *  root  /etc/


All done. Lets Encrypt will automatically check certificate currency on the first of each month, and if needed, will renew. There are many different ways to run cron, so whatever suits you. I like to do things at specific times and dates for management and control.

SSL Trust

Any email provider who requires a trusted TLS certificate in order to send email to your server, will fail when using Lets Encrypt. You will see “Untrusted TLS connection established to…” in your mail log. This will cause some legitimate email sent to you, to bounce. Lets Encrypt is not advised for professional services, instead purchase a Positive SSL certificate by Comodo as a minimum for your FQDN (US$10 per annum via, as you will require the CA bundle to become a trusted, secure, mail server.

After installing your new key, Positive SSL cert and ca-bundle onto the server, ensure you add the Positive SSL ca-bundle to and change NGINX:

nano /etc/dovecot/dovecot.conf
ssl_ca =
nano /etc/postfix/
smtpd_tls_CAfile = /etc/pki/tls...
nano /etc/nginx/conf.d/00-default.conf
server_name _;

iRedMail also adds all SSL locations to the my.cnf database file, so you can change that too with any new locations.

RESTART all three services.

If you don’t change the server_name, you will get a new mail log error attempting to connect to the top level domain.

This will not work using the Lets Encrypt ca-bundle. Again, not a trusted certificate authority.

When using Positive SSL with ca-bundle, a senders mail log will show: “Trusted TLS connection established to…” when sending mail to you.

DKIM Signing

As Amavis is so intertwined with Spamassassin and ClavAV (RAM hungry), it’s easier to disable them and install openDKIM for email signing instead. DKIM will allow you to get your email past the toughest spam filter of them all, Microsoft’s Outlook system.

Because I host many domains I’m going to use a domain folder to segregate keys.

yum -y install opendkim opendkim-tools
mkdir -p /etc/opendkim/keys/
opendkim-genkey -D /etc/opendkim/keys/ -d
chmod 640 /etc/opendkim/keys/
chmod 644 /etc/opendkim/keys/
chown -R opendkim:opendkim /etc/opendkim


nano /etc/opendkim.conf

Change: Mode sv

Change: Socket inet:[email protected]

Uncomment lines: KeyTable, SigningTable, ExternalIgnoreList and InternalHosts.

Comment lines: Domain and KeyFile.


Now we need to modify these files to tell them, per domain, where to look.

You can alternatively do these adjustments in a single line if you want, i.e. echo “” >> /etc/opendkim/KeyTable

nano /etc/opendkim/KeyTable



nano /etc/opendkim/SigningTable

If you use external servers to send email you will need to add them to the below trusted hosts file: (localhost is already available if using a single server, so disregard).

nano /etc/opendkim/TrustedHosts

Time to link OpenDKIM with Postfix.

nano /etc/postfix/


# Link OpenDKIM to Postfix
smtpd_milters = inet:
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
systemctl start opendkim
systemctl enable opendkim
systemctl restart postfix

Now grab your public key and enter it into your domains DNS as a TXT record. You must ensure you get the full key and enter it correctly for your DNS provider. Remember that DKIM is validated against your domain, so a valid A record is needed.

nano /etc/opendkim/keys/

Name = default._domainkey

Value = v=DKIM1; k=rsa; p=MIGfMA0….

A final task is to ensure your firewall allows TCP port 8891, as that is what openDKIM uses to sign outgoing mail. Mail will fail to send without it. For the default iredmail install using Firewalld:

firewall-cmd --zone=iredmail --permanent --add-port=8891/tcp
firewall-cmd --reload


Once you’re all done, use DKIM, SPF, and Spam Assassin Validator – to test your DKIM and SFP are working correctly.

Remember that when you add or update keys, make sure when done, before testing, you chown the keys to the opendkim user and group, otherwise DKIM will not work.

Alias Email Addresses

Inserting aliases without paying for the pro version of iRedMail is super simple via the MySQL database. Just follow their directions and insert the line yourself: Per-user alias address

Want phpMyAdmin?

systemctl stop nginx
yum -y --enablerepo=remi install phpmyadmin
ln -s /usr/share/phpMyAdmin /var/www/html
chown nginx:nginx /var/lib/php/session
chown -R nginx:nginx /etc/phpMyAdmin
systemctl start nginx
systemctl restart php-fpm
systemctl restart mysqld

All done. Go to your FQDN ( and access with your MySQL root login.

To secure your phpMyAdmin, you can use a custom location (change “mylocation”) by changing the symbolic link name.

mv /var/www/html/phpMyAdmin /var/www/html/mylocation

Now your phpMyAdmin login page will be at (

I highly recommend you lock down access to phpMyAdmin to your IP alone. If you change IP, you can change the nginx server file to access on the move.

nano /etc/nginx/conf.d/00-default.conf


location /mylocation {
 deny all;

Obviously change mylocation to the name you used.

DKIM failure usually occurs for two primary reasons: permission errors and DNS errors. The above is tried, tested and proven, and your outcome upon testing should look similar to the below. To prove a point, using default cPanel email I get the following positive spam score result, which whilst it claims not to be marked as spam, places email from my account into an Outlook spam-box:


SpamAssassin Score: 0.11
Message is NOT marked as spam
Points breakdown:
0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid
0.0 T_DKIM_INVALID DKIM-Signature header exists but is not valid

Using my version, you get a negative spam score which places mail into Outlook inbox, Microsoft being the toughest filter to get through:

SpamAssassin Score: -0.101
Message is NOT marked as spam
Points breakdown:
-0.0 SPF_HELO_PASS SPF: HELO matches SPF record
0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid
-0.1 DKIM_VALID Message has at least one valid DKIM or DK signature
-0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author’s domain

Enjoy. If you can improve it, please comment with your recommendations.

Just For Laughs — If You Want NGX Pagespeed Too

Assuming you run the default YUM NGINX install, whether Centos default or you added the .repo to get the latest stable or mainline package from NGINX, changing out NGINX rpm for manual build is super easy.

If you read the NGINX admins guide on building NGINX from source, you may believe that you should be building PCRE, ZLIB and OpenSSL from source. Centos already has these libraries installed. All you need is their devels which add the tools for each when building from source.

Saying that, if you are not running the latest OpenSSL stable 1.0.2 branch, you should be for HTTP2 compatibility. The current 1.0.2e YUM is not HTTP2 compatible. The 1.1 branch is too far, and not entirely Centos 7 compatible.

Both Zlib and PCRE devels will be installed automatically with ngx_pagespeed.

Install NGX_Pagespeed

bash <(curl -f -L -sS \ --nginx-version latest

When completed it will output the add module directive with location you installed, i.e. –add-module=/root/ngx_pagespeed-latest-stable. You will need to add that to your configure argument.

Move & Remove NGINX

nginx -V

Copy the configure arguments your current NGINX install is using and paste them within an editor.

Add your ngx_pagespeed module output to that argument. You will have something like:

--prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/ --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E' --add-module=/root/ngx_pagespeed-latest-stable

If you want to make the most of dynamic modules, adjust the above arguments as necessary, and remember you will need to implement those manually.

Now we move and remove (discard the errors shown during removal):

systemctl stop nginx
mv /etc/nginx /root/nginx
yum -y remove nginx

Now install our new version. Install whatever version makes you happy. In this example, I use the latest mainline at time of writing:

yum -y install openssl-devel
tar zxf nginx-1.11.13.tar.gz
cd nginx-1.11.13
./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx ... < your configure argument >
make install
cp -R /root/nginx /etc

Say yes (y) to each file overwrite. You can compare and amend if you wish, file by file. If you used the same version, then overwriting should not be a problem.

Because we no longer use an RPM, we need to manually implement our systemd. The below is a copy of the NGINX official version. Check for accuracy.

nano /lib/systemd/system/nginx.service


Description=The NGINX HTTP and reverse proxy server

ExecStartPre=/usr/sbin/nginx -t
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID


All done. Enable NGINX to start at boot, check syntax first (see known errors below) and if OK, start NGINX:

systemctl enable nginx
nginx -t
systemctl start nginx

Visit your domain and everything should be running as normal.

If you get this error due to NGINX not starting, when running nginx -t:

[emerg] mkdir() "/var/cache/nginx/client_temp" failed (2: No such file or directory)


mkdir /var/cache/nginx
$ touch /var/cache/nginx/client_temp

Implement NGX_Pagespeed into NGINX

Within your http configuration, add the basics (cache is set to one hour cleaning checks based on filling 100Mb or 500k inodes of cache entries) and remove the Pagespeed version:

pagespeed on;
pagespeed DisableRewriteOnNoTransform off;
pagespeed XHeaderValue "Powered By ngx_pagespeed";
pagespeed FileCachePath /var/ngx_pagespeed_cache;
pagespeed FileCacheSizeKb 100000;
pagespeed FileCacheCleanIntervalMs 3600000;
pagespeed FileCacheInodeLimit 500000;

Within each server config you want to run ngx_pagespeed, add:

location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {
add_header "" "";

location ~ "^/pagespeed_static/" { }

location ~ "^/ngx_pagespeed_beacon$" { }


Pagespeed is far from set and forget. The basics at ngx_pagespeed configuration will help you get a fundamental cache running, combined with an important basic component that is URL Restricting (what is and is not to be cached). The full docs outline far more configuration options, which I do not cover here as you need to set them based on what is relevant to your virtual host / server.

Be super careful with further settings beyond the basics, as settings can cause unintended complications depending on the software you use on your server, and work differently between browsers. Example: inlining Google Font CSS has no negative affect on WordPress, yet totally screws with Xenforo. Even preserve URL relativity, a basic setting, can break parts of your site between browsers. Be careful, roll out one thing at a time and get your users to feedback issues.

When you make a change, drop every cache from your server to browser. Even disable every cache beyond your server, so you test the effect of page speed settings. PageSpeed can very quickly render your server useless when you go in heavy adding options. Image options specifically can be very CPU intensive and just not worth it. Saying that, when you delve into the settings you have a lot of control to limit rendering your server useless with PageSpeed performing too many optimisations at once.

When you think longer term, and depending on the frequency your static content changes, you can implement quite the longevity cache that progressively improves until at the point of being up to date with only new content.

How Do I Know It’s Working?

Open a website on your server, right click and view page source, then search for pagespeed. You should find where pagespeed is being applied. If you view the network tab in inspector, reload the page, you should see a pagespeed header.


When using page speed with PHP-FPM sockets, you will likely get unintended consequences and stall PHP-FPM, thus shutting down all your sites. If using sockets, remove any keep alive components, disable them in both the http socket and server PHP config.

You will need to monitor your nginx, php-fpm and database logs, even mail logs and such, for sudden errors caused by a wrong setting somewhere. Trust me, page speed is amazing, but tedious to get setup and get right. Approach with caution and monitor for days, a week even, after changing a setting. I’ve found negative impact from page speed settings across most primary services, all of which have easy enough solutions, but will vary dependent upon your setup and settings used.

Start basic, monitor for a week and fix the basic issues that arise first. Use the time to read and understand potential settings you may want to implement, and work out viable solutions based on your server specs and software application.

Flush Pagespeed Cache

Just run the following to flush the ngx_pagespeed cache:

touch /var/ngx_pagespeed_cache/cache.flush