Hostwinds Dedicated LAMP Server Tutorial

Mark Malkasian
July 2020

This tutorial is intended to provide step-by-step instructions to setting up a dedicated server with a basic LAMP installation. There are plenty of tutorials out there that fit the same description. What sets this tutorial apart is the attention to detail and the intended audience.

If you wish to offer your suggestions or comments, you're welcome to contact me. You're also welcome to submit like-minded tutorials on related topics for publication on the site.

Early LAMP stackYou may be wondering why the focus is on Hostwinds. I came to Hostwinds as an exile from Dream Hosts. Although the server farm at Dream Hosts had a solid performance record, their business model revolves around herding customers toward managed server and VPS (virtual private server) packages, supplemented by selling add-on services. Eventually, the constraints and limitations imposed by Dream Hosts took all the fun out of running a dedicated server.

With Hostwinds, I was back in the driver's seat. Of course, that means being more self-reliant, but that's the point. I found the Hostwinds support staff to be professional and courteous during the transition. (Most of my questions pertained to domain transfers.) Like technical support staffs everywhere, they are clearly overstretched.

Much more disappointing was Hostwinds' online documentation. The Knowledge Base is littered with broken links and outdated articles, and little of it addresses the needs of dedicated server admins. My original intent was to post my tutorial in the Knowledge Base. I contacted Hostwinds twice to offer my tutorial at no charge. Unfortunately, I never received even so much as a boilerplate rejection. That leads me to question whether I made the right choice in signing up with Hostwinds. So far, knock on wood, the server itself is running smoothly.

My fear is that the business model at Hostwinds isn't much different than that of Dream Hosts (and seemingly the vast majority of web hosting providers). I'm in business myself, so I can appreciate the bottom-line forces driving the market. There's not much money to be made from the hands-on, do-it-yourself, command-line types these days. However, if you're proud to fit in that category, Hostwinds hasn't completely slammed the door in your face. I'm hoping that this tutorial helps fill the gap in their online documentation. By the time you finish, you should be well on your way to hosting multiple web sites on your new server. Let's jump in.

Admin Warm-Up

Create a Sudo User Account

Once your Hostwinds dedicated server order is complete, you'll receive an email with a password that will enable you to log in as the "root" user with your SSH client (I use PuTTY). One of your first tasks should be to change the root password and create a new user with "sudo" privileges. Conventional wisdom says that you shouldn't log in as root. Instead, create a separate user with "sudo" (i.e. "superuser do") privileges equal to the root user.

Change the root password from the default...

% passwd root

Create a new account...

% adduser myusername

Give the new user "sudo" privileges...

% usermod -aG sudo myusername

After you log in as "myusername", you give yourself sudo privileges by entering the command below, followed by a password prompt...

% sudo su

The above command means you'll have sudo privileges throughout your session. You'll be able to proceed through the rest of the server setup without prefacing each command with "sudo".

Firewall Setup

Ubuntu includes a built-in firewall, known as UFW (or "uncomplicated firewall"). The firewall is disabled by default. Before enabling UFW, you'll need to make sure that UFW doesn't block your SSH client from connecting to your server. First, check the list of UFW applications...

% ufw app list

The output should be similar to the following: Available applications: Apache Apache Full Apache Secure OpenSSH

To prevent UFW from blocking your SSH client, permit SSH connections with the following command...

% ufw allow OpenSSH

You'll also want to permit connections to the Apache web server on ports 80 (http) and 443 (https) with the following command...

% ufw allow "Apache Full"

Now it's time to enable the firewall with the following command...

% ufw enable

Check to make sure that OpenSSH and Apache connections are allowed...

% ufw status

Making Friends with APT and RSYNC

Two command line utilities -- apt and rysnc -- will quickly become your new best friends during the server setup process. Both are simple to use and battled-tested. Let's get acquainted.


APT is an abbreviation for Advanced Package Tool and serves to interact with the dpkg (Debian packaging) system. Debian (or Debian GNU/Linux) is the open-source operating system launched in 1993 that is the basis for Ubuntu and a few other flavors of Linux. APT makes installing, upgrading, and deleting software quick and easy. Before installing software on your new server, execute the "update" and "upgrade" commands to ensure you have access to the latest applications...

% apt update
% apt upgrade

View the impressive list of applications available for installation through APT...

% apt list

The standard command for installing an application is...

% apt install package_name (e.g., phpmyadmin)

If you need to remove a program, use the following commands...

% apt remove package_name
% apt purge package_name


rsync (remote sync) quickly transfers and synchronizes files between two servers. If you're moving your web sites from another hosting provider to Hostwinds, you'll find rsync an indispensable tool. It's certainly quicker and more efficient than FTP. rsync can also be used to copy files on your server from one directory to another.

To move a directory from your old server to your Hostwinds server, issue the following command from your Hostwinds server...

% rsync -avz myusername@111.222.333.444:/old_server/storage/myfiles /new_server/storage/

To move a single file from your old server to your Hostwinds server, issue the following command from your Hostwinds server...

% rsync -avz myusername@111.222.333.444:/old_server/storage/mydatabase.sql /new_server/storage/mydatabase.sql

To establish a connection and initiate the transfer, you will be prompted to enter the password for "myusername" on your old server.

rsync has a wide range of options. Below is a guide to the options used in the commands above: a = archive mode, which is the equivalent of "-rlptgoD", meaning rsync will transfer files recursively and preserve almost everything. v = verbose reporting. z = compress file data during the transfer.

System Reboot

After you download application packages or update Ubuntu, you may see the following message when you log in to your shell account: *** System restart required ***

You can reboot from the command line or from the Client Area.

From the Client Area main menu, click the "Manage" button for your dedicated server, scroll down to the "Server Management" section, and click the "Reboot" link.

From the command line...

% reboot

Rebooting takes a few minutes, so it's an action you want to avoid on a live server.

Apache Installation

Tied down by a managed serverSoon after its release in 1995, Apache established itself as the world's most popular web server (although Nginx is not far behind). Apache was crafted with adaptability in mind. The application's core functionality is extended by a variety of compiled modules, such as mod_rewrite and mod_ssl. Other modules accommodate the use of server-side programming languages, such as PHP and Python. The Apache community also maintains a robust support network centered around Apache is easy to install and easy to customize. Let's start with the basic installation.

Install Apache (or more correctly the latest stable version of apache2)...

% apt install apache2

Verify the Apache version...

% apache2 -v

In my case, the output was the following: Server version: Apache/2.4.29 (Ubuntu)

By default, the Apache configuration files are installed in the /etc/apache2/ directory, with the main config file located at /etc/apache2/apache2.conf. The path to the Apache HTTP daemon is /usr/sbin/apache2.

Start Apache and check status...

% systemctl start apache2.service
% systemctl status apache2

Assuming the "status" command reports "Active: active (running)", you should be able to enter your primary IP address into the location bar of a web browser and see the default index.html page generated by Apache in /var/www/html.

Below are a few other useful commands to manage Apache...

% systemctl stop apache2.service
% systemctl restart apache2.service
% systemctl reload apache2.service
% apache2ctl configtest (identifies any syntax errors in your configuration files)

The standard repository for web site files is the /var/www/html/ directory. The standard repository for log files is the /var/log/apache2/ directory. As you'll see, you can customize both paths in the Apache configuration files.

Although it's tempting to tweak some of the directives (e.g., MaxKeepAliveRequests) in the main Apache configuration file located at /etc/apache2/apache2.conf, it's probably better to get your web sites up and running before fine tuning. If you can't resist the temptation, first make a back-up copy of the config file...

% cp -p /etc/apache2/apache2.conf /etc/apache2/apache2.conf_original_bak

Apache Modules

As noted above, adding compiled modules makes Apache even more powerful. Several of them will be enabled during installation of the Apache package. Let's review the inventory.

Determine which modules have been enabled...

% a2query -m

If your web sites require additional modules, such as mod_rewrite, mod_ssl, etc., enable them with a2enmod and restart Apache...

% a2enmod rewrite expires headers include ssl suexec
% systemctl restart apache2.service

MySQL Installation

MySQLBy most measures, MySQL is the world's most popular database. First released in 1995, MySQL serves as the foundation for many of the web's most popular database-driven applications, such as WordPress, phpBB, and Drupal. It has also been adopted as a platform by many of the Internet's giants, including Facebook, Twitter, and YouTube. Although Oracle bought MySQL in 2010, it continues to provide an open-source version. (At the time of Oracle's acquisition, MariaDB was created by one of MySQL's founders to preserve the open-source MySQL project.) In addition, free, online support for MySQL remains as strong as ever.

Install MySQL...

% apt install mysql-server

The default settings in MySQL are not secure. Fortunately, there is a single configuration command that protects the database against the most common attacks. Secure MySQL...

% mysql_secure_installation

You will be prompted to enter a password and answer "y" (for "yes") for questions about security.

Log in to MySQL...

% mysql -u root -p

MySQL can be operated from the command line, but managing the database through a web-based application, such as phpMyAdmin, will make your life a lot easier. We'll get to that later.

Below are a few key commands to start and stop the database...

% service mysql stop
% service mysql start
% service mysql restart

Custom Configuration

As with Apache, it's probably best not to tweak MySQL configuration settings until your web sites are up and running. That doesn't mean, however, that you shouldn't familiarize yourself with your database's settings.

View the current MySQL settings...

% mysqladmin -u root -p variables

To see all MySQL settings...

% /usr/sbin/mysqld --help --verbose | more

Although MySQL has a my.cnf configuration file (actually a symlink that points to /etc/mysql/mysql.cnf), the key settings are found in /etc/mysql/mysql.conf.d/mysqld.cnf. It's possible to edit mysqld.cnf. However, the best practice for adjusting your database settings is to place a new file with a ".cnf" extension in the /etc/mysql/conf.d/ directory. For example, you might call your file "myDBoptions.cnf" so you'll remember that it's your own creation. (The ".cnf" extension tells MySQL to incorporate the settings when you restart the database.) Your custom file should follow the format of mysqld.cnf. For example, [mysqld] key_buffer_size = 16M max_allowed_packet = 16M thread_stack = 192K thread_cache_size = 8

After creating a custom configuration file, restart MySQL...

% service mysql restart

PHP Installation

Serious web applications demand a serious server-side programming language. Among the most popular at the moment are PHP, Python, and Ruby. I use PHP (Hypertext Preprocessor), so I'll guide you through the process of putting it to work on your server.

Install PHP

% apt install php libapache2-mod-php

Because I didn't specify a version of PHP (e.g., php7.3, php5.6, etc.), the default package of PHP on your server is installed.

Restart Apache...

% systemctl restart apache2.service

PHP Extensions

PHP includes a wide range of extensions that broaden PHP's functionality and enable it to interface with other programs, such as MySQL.

Install common PHP extensions...

% apt install php-bcmath php-bz2 php-cli php-common php-curl php-fpm php-gd php-gettext php-imap php-json php-mbstring php-mysql php-mysqli php-opcache php-pspell php-soap php-tokenizer php-xml php-xmlrpc

Restart Apache...

% systemctl restart apache2.service

Now that PHP is installed, you might want to explore the configuration by creating a simple file that executes the phpinfo() function. First, open your favorite text editor (e.g., nano, pico, vi, etc.) and enter the three lines below... phpinfo();

Save the file at /var/www/html/phpinfo.php.

Open your newly created file by entering 111.222.333.4/phpinfo.php in the location bar of a web browser (with 111.222.333.4 being your primary IP address). The resulting display should include all the vital details of your PHP configuration.

Legacy Versions

Tech supportIf you're like me, you have web sites written in older versions of PHP that you haven't updated (and may never get around to updating). Parsing an application written in version 5.6 with version 7.3 will result in syntax errors. Fortunately, the Ubuntu community has developed a method for accessing applications not included in Ubuntu's default installation. The solution comes in the form of a Personal Package Archive, or PPA. A PPA is a repository of applications, typically focusing on a single program. A PPA will usually include both older and newer versions of an application. The PPA for PHP you'll need to enable is maintained by Ondřej Surý. (Like so many other individuals in the open-source community, Ondřej Surý is not a tech billionaire with a personal brand to promote. He puts his time and effort into maintaining the PHP PPA out of the goodness of his heart and a commitment to the open-source ethos.)

Enable the Ondrej PPA...

% apt install software-properties-common
% add-apt-repository ppa:ondrej/php
% add-apt-repository ppa:ondrej/apache2

Install PHP version 5.6...

% apt install php5.6

Restart Apache...

% systemctl restart apache2.service

Install common PHP 5.6 extensions...

% apt install php5.6-bcmath php5.6-bz2 php5.6-common php5.6-cli php5.6-curl php5.6-fpm php5.6-gd php5.6-imap php5.6-json php5.6-mbstring php5.6-mcrypt php5.6-mysql php5.6-opcache php5.6-pspell php5.6-soap php5.6-xml php5.6-xmlrpc php5.6-xsl php5.6-zip

Restart Apache...

% systemctl restart apache2.service

Your PHP executables can be found at... /usr/bin/php5.6 /usr/bin/php7.2 /usr/bin/php7.3

Configure PHP with the Apache Module mod-fcgid and PHP Extension php-fpm

Over the years, the PHP community has continued to improve the integration between PHP and Apache. As of this writing, the consensus is to serve up PHP web pages with the help of the Apache mod-fcgid module and the PHP php-fpm extension.

The mod_fcgid module uses the FastCGI protocol to provide an interface between Apache and Common Gateway Interface (CGI) programs. It starts up several CGI program instances to handle concurrent requests to your web site.

The "fpm" in the php-fpm extension stands for FastCGI Process Manager, which runs as a daemon and interprets Fast/CGI requests. Each version of PHP requires installation of its own php-fpm extension. We'll use php7.3-fpm for the example below.

Enable the FastCGI module for Apache2...

% a2enmod proxy_fcgi setenvif

Restart Apache...

% systemctl restart apache2.service

Enable the configuration that instructs Apache to pass PHP requests to php-fpm for processing...

% a2enconf php7.3-fpm

Restart Apache...

% systemctl restart apache2.service

Start FastCGI Process Manager...

% systemctl start php7.3-fpm

Verify the status of FastCGI Process Manager...

% systemctl status php7.3-fpm

FastCGI Process Manager requires the following Apache2 modules: actions, fcgid, alias, proxy_fcgi. Make sure the modules are enabled...

% a2query -m

Enable modules as needed...

% a2enmod actions fcgid alias proxy_fcgi

Restart Apache...

% systemctl restart apache2.service

When you run your phpinfo() script, you should now see a new entry: Server API FPM/FastCGI

Enabling the FastCGI module created the configuration file below at /etc/apache2/mods-available/fcgid.conf, so there's no need to add a new directive in apache2.conf. FcgidConnectTimeout 20 AddHandler fcgid-script.fcgi

Just as there are version-specific php-fpm extensions, you'll find version-specific configuration files (e.g., php7.3-fpm.conf) for each version of php-fpm in the /etc/apache2/conf-available directory.

FastCGI Process Manager's security.limit_extension setting is used to limit the file extensions that will be parsed by PHP. If your web site has ".html" pages that require PHP parsing, limit_extension will block access and display an "Access denied" message with a 403 status error. To change or disable security.limit_extension, edit /etc/php5/fpm/pool.d/www.conf: security.limit_extensions =.php.html
or security.limit_extensions = (allows any extension to be executed)

Restart FastCGI Process Manager...

% systemctl restart php5.6-fpm

Using FastCGI Process Manager to serve your web pages requires editing Apache's VirtualHost directive, but we'll save the details for the section on Virtual Hosting.

Modify php.ini

If you've been programming with PHP long enough, you probably have some code running on a neglected web site that you're less than proud of. Bringing the code up to speed might be the right thing to do, but it's a lot easier to edit PHP's configuration settings to cut yourself some slack.

PHP's configuration files are called php.ini and can be found in the /etc/php/ directory. Each version of PHP has its own set of configuration files. Below are paths for version 7.3... /etc/php/7.3/apache2/php.ini /etc/php/7.3/cli/php.ini /etc/php/7.3/fpm/php.ini

If you've enabled the php-fpm module to serve web pages, the settings in /etc/php/7.3/fpm/php.ini are in effect. To confirm, run your phpinfo() script and check the value for "Loaded Configuration File".

The /etc/php/7.3/cli/php.ini file is used by the CLI version of PHP. The CLI version comes into play when you run PHP from the command line or use it in a cron script.

Confirm the path to the CLI version of PHP...

% php -i | grep php.ini

The output should be: Loaded Configuration File => /etc/php/7.3/cli/php.ini

The apache2/php.ini and fpm/php.ini files are identical and can be copied one to the other. The cli/php.ini file is slightly different.

After modifying fpm/php.ini, restart Apache and the php-fpm module...

% systemctl restart apache2.service
% systemctl restart php7.3-fpm

Securing Your Server

If you're a server admin for a top-secret government agency or a too-big-to-fail financial institution, securing the Internet's most sensitive data is a constant concern for you, and I sincerely hope you're not reading this tutorial. For the rest of us, we're more likely to be the target of a runaway bot or a bored teenager. Taking a few prudent steps will help minimize our vulnerability.

Employ the settings below in /etc/apache2/conf-available/security.conf

ServerSignature Off ServerTokens Prod TraceEnable off (already set as the default)

Install the Apache mod-evasive Module

The mod-evasive module is intended to thwart distributed denial-of-service (DDoS) attacks. The purpose of a DDoS attack is to block access to a web site by flooding it with page requests.

Install mod-evasive...

% apt install libapache2-mod-evasive

The installation interface asks how sending mail is configured. In most cases, you should select "Internet". You'll also be asked to enter a domain name for your SMTP server. Note that libapache2-mod-evasive includes the installation of Postfix (more on that later).

The configuration file for mod-evasive is found at /etc/apache2/mods-available/evasive.conf. By default, all settings are commented out.

Uncomment the following settings, leaving the default values in place: DOSHashTableSize 3097 DOSPageCount 2 DOSSiteCount 50 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 10

You should probably leave the following settings commented out unless you want to closely monitor your server: DOSEmailNotify DOSSystemCommand DOSLogDir.

Restart Apache...

% systemctl restart apache2.service

Install and Configure the Apache mod-security2 Module

The mod-security2 Apache module is a popular firewall application. Known as ModSecurity, or Modsec, the module helps protect your web server from several common attacks, including SQL injection, XSS, trojans, nasty bots, session capture/hijacking, and more.

Install mod-security2...

% apt install libapache2-mod-security2

By default, no security rules are configured at installation. You need to copy and edit /etc/modsecurity/modsecurity.conf-recommended to put ModSecurity to work.

Copy the configuration file to /etc/modsecurity/modsecurity.conf...

% cd /etc/modsecurity
% cp modsecurity.conf-recommended modsecurity.conf

Edit the "SecRuleEngine" setting in /etc/modsecurity/modsecurity.conf:
Change DetectionOnly
to On

Restart Apache...

% systemctl restart apache2.service

Set Up and Configure ModSecurity Core Rule Set (CRS)

ModSecurity can be configured to fine tune the level of security you wish to apply. You should start by setting up the OWASP ModSecurity Core Rule Set (CRS). The CRS is a set of generic attack detection rules that aim to protect web sites from the most common threats.

Replace the default CRS with an updated version...

% rm -rf /usr/share/modsecurity-crs
% git clone /usr/share/modsecurity-crs

Copy the file crs-setup.conf.example to crs-setup.conf...

% cd /usr/share/modsecurity-crs
% cp -p crs-setup.conf.example crs-setup.conf

In /etc/apache2/mods-available/security2.conf, add the following lines after "IncludeOptional /etc/modsecurity/*.conf": IncludeOptional "/usr/share/modsecurity-crs/*.conf IncludeOptional "/usr/share/modsecurity-crs/rules/*.conf

In the same file, make sure the following line has been entered above "": IncludeOptional /usr/share/modsecurity-crs/owasp-crs.load

Enable the Apache mod_headers module (if not already be enabled)...

% a2enmod headers

Restart Apache...

% systemctl restart apache2.service

Not surprisingly, experts in Internet security are particularly worried about cyberthreats. The developers of ModSecurity are no exception (the settings for the CRS are ranked by "paranoia level"). You'll almost certainly want to dial down the fear factor. ModSecurity's default installation comes in blocking mode and with an "anomaly threshold" of 5 for page requests. I found that setting triggered too many "false positives". To address the issue, I uncommented the section below in /usr/share/modsecurity-crs/crs-setup.conf and raised the defaults of inbound_anomaly_score_threshold and outbound_anomaly_score_threshold to 10: SecAction "id:900110, phase:1, nolog, pass, t:none, setvar:tx.inbound_anomaly_score_threshold=10, setvar:tx.outbound_anomaly_score_threshold=10"

ModSecurity provides more fine-tuning tools to exclude URLs from being subjected to specific rules. If you post content with HTML or JavaScript tags through a content management system, you'll probably want to take advantage of them. For example, I created /usr/share/modsecurity-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf by copying the ".example" file and added the rule exclusion below to disable all SQLi and XSS rules for my "content_management.html" page: SecRule REQUEST_URI "@beginsWith /content_management.html" "id:1000, phase:1, pass, nolog, ctl:ruleRemoveById=941000-942999"

Note that each rule exclusion must have a unique "id" and Apache must be restarted after each configuration edit.

To disable specific rules with one-line exceptions (e.g., SecRuleRemoveById 942100), edit /usr/share/modsecurity-crs/rules/REQUEST-999-EXCLUSION-RULES-AFTER-CRS.conf.

Install and Configure Fail2Ban

Fail2Ban is a software package designed to protect Apache servers from a range of attacks. Fail2Ban blocks specific IP addresses by monitoring your log files to identify suspicous activity, such as rapid-fire login attempts, brute force denial-of-service (DDOS) assaults, requests for sketchy URLs, probes for executables, etc. The offending IPs are blocked from accessing your server for a time period you define.

Install Fail2Ban...

% apt install fail2ban

Fail2Ban files reside in the /etc/fail2ban directory. The primary configuration file, jail.conf, should not be edited. Instead, you should create a separate file (e.g., jail.local) in the /etc/fail2ban directory to define customized filters. In Fail2Ban parlance, each filter (or set of rules) is known as a "jail". The text between the brackets (e.g., "apache", "http-get-dos") defines a distinct jail that holds malevolent IP addresses. Several parameters enable you to customize the filters.

logpath is the file path monitored by Fail2Ban.
maxretry defines the maximum number of failed login attempts allowed before an IP address is blocked.
findtime specifies the time period in seconds used by "maxretry".
bantime specifies the number of seconds that an offending IP address will be blocked (i.e., held in Fail2Ban jail).
ignoreip lists IP addresses that should not be blocked by Fail2Ban. Each address should be separated by a space.

Below is a basic working example of jail.local:
# Blocks failed login attempts. [apache] enabled = true port = http,https filter = apache-auth logpath = /var/log/apache2/*error.log maxretry = 15 bantime = 600 ignoreip = 111.222.333.4 # Blocks failed login attempts to the SSH server. [ssh] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 5 bantime = 600 ignoreip = 111.222.333.4 # Blocks requests for suspicious URLs. [apache-overflows] enabled = true port = http,https filter = apache-overflows logpath = /var/log/apache2/*error.log maxretry = 10 bantime = 600 ignoreip = 111.222.333.4 # Blocks requests for executable scripts. [apache-noscript] enabled = true port = http,https filter = apache-noscript logpath = /var/log/apache2/*error.log maxretry = 10 bantime = 600 ignoreip = 111.222.333.4 # Blocks requests for malicious bots. [apache-badbots] enabled = true port = http,https filter = apache-badbots logpath = /var/log/apache2/*error.log maxretry = 3 bantime = 600 ignoreip = 111.222.333.4 # Blocks brute force denial-of-service (DDOS). Each file in logfile should be separated by a newline character. [http-get-dos] enabled = true port = http,https filter = http-get-dos logpath = /var/log/apache2/access.log /var/log/apache2/access_log_mydomain /var/log/apache2/access_log_yourdomain maxretry = 400 findtime = 400 bantime = 200 ignoreip = 111.222.333.4 action = iptables[name=HTTP, port=http, protocol=tcp] # Block attempts to exploit PHP for malicious purposes. [php-url-fopen] enabled = true port = http,https filter = php-url-fopen logpath = /var/log/apache2/*access.log ignoreip = 111.222.333.4

Create jail.local...

% nano /etc/fail2ban/jail.local

Next, a filter file needs to be placed in the /etc/fail2ban/filter.d/http-get-dos.conf directory with the content below: # Fail2Ban configuration file [Definition] # Option: failregex # Note: This regex will match any GET entry in your logs, so basically all valid and not valid entries are a match. # You should set up in the jail.conf file, the maxretry and findtime carefully in order to avoid false positives. failregex = ^ -.*"(GET|POST).* # Option: ignoreregex ignoreregex =

Create http-get-dos.conf...

% nano /etc/fail2ban/filter.d/http-get-dos.conf

Start Fail2Ban and check its status...

% systemctl restart fail2ban
% fail2ban-client status

Starting Fail2Ban should generate and fail2ban.sock files in the /var/run/fail2ban directory. If the status command reports that fail2ban is not running, there's probably a syntax error in your jail.local file.

View the rules added by Fail2Ban (you should find a few incarcerated IP addresses)...

% iptables -L

phpMyAdmin Installation

As I mentioned in the section about MySQL, you could manage your database through the command line, but using a web-based interface is so much easier. I've relied on phpMyAdmin for many years. It has a long track record and is written in PHP. Before installing phpMyAdmin, Apache, MySQL, and PHP must first be in place. You may also want to create a new user in MySQL to serve as the username for your installation of phpMyAdmin. As you'll see during the installation, the default username is "phpmyadmin". For security reasons, you should stay away from defaults.

Install phpMyAdmin...

% apt install phpmyadmin

The installation of phpMyAdmin launches a GUI that poses a few questions. You can choose the default settings in most cases, although there are some exceptions. For example, when it comes to indicating your web server, you have to press the space bar to select "apache2", tab to "Ok", and press "". You'll also be prompted to enter your "MySQL username for phpmyadmin" (the default is "phpmyadmin").

Restart Apache...

% systemctl restart apache2.service

phpMyAdmin files are located at: /usr/share/phpmyadmin -- web site files. /etc/phpmyadmin and /etc/dbconfig-common -- config files. /etc/phpmyadmin/ -- main configuration file.

phpMyAdmin requires the PHP mbstring extension to function (it may already be installed). Install and enable php-mbstring...

% apt install php-mbstring
% phpenmod mbstring

You may also need to uncomment the line below in php.ini: extension=mbstring

Restart Apache...

% systemctl restart apache2.service

By default, phpMyAdmin is served out of the /var/www/html directory. If the FastCGI Process Manager handles PHP requests to your server, add the following to 000-default.conf: SetHandler "proxy:unix:/run/php/php7.3-fpm.sock|fcgi://localhost"

You can now access phpMyAdmin by visiting your server's IP address followed by "/phpmyadmin": http://111.222.333.4/phpmyadmin

Access phpMyAdmin from a Secure Domain URL (e.g.,

Because phpMyAdmin is popular, it's a frequent target of malevolent hackers. A few precautions will reduce your vulnerability.

By default, the initial setup of phpMyAdmin allows access through the IP address of your server (e.g., http://111.222.333.4/phpmyadmin). To enable access through a domain, edit /etc/phpmyadmin/apache.conf.

Wrap the config file in a VirtualHost directive that assigns a domain you control to the ServerName and ServerAlias: ServerName ServerAlias Alias /phpmyadmin /usr/share/phpmyadmin ....

Add Basic Authentication by inserting the following below the "Alias" line: AuthType Basic AuthName "Admin Access" AuthUserFile /var/mypasswords/phpmyadmin Require valid-user

Insert "AllowOverride All" under the "" directive: Options SymLinksIfOwnerMatch DirectoryIndex index.php AllowOverride All ....

Create a symlink to /etc/phpmyadmin/apache.conf and enable phpmyadmin.conf...

% cd /etc/apache2/sites-available
% ln -s /etc/phpmyadmin/apache.conf phpmyadmin.conf
% a2ensite phpmyadmin.conf

Restart Apache...

% systemctl restart apache2.service

Now that you've assigned a domain to phpMyAdmin, the next step is to add encryption. I won't go into detail here about encrypted URLs. Instead, you'll want to follow the instructions below about generating a Let's Encrypt certificate for the domain serving phpMyAdmin ( and revising the apache.conf file.

DNS (Domain Name System)

Web hosting consolidationDNS is often compared to a phonebook. It maps IP addresses to domain names. This means that visitors to your web site can find you by entering instead of 111.222.333.4.

Managing DNS records is complicated. Fortunately, Hostwinds provides a convenient web interface to manage DNS. The first step involves pointing your domains to Hostwinds' nameservers.

Registration and Nameservers

If you are registering new domain names, the process is simple. From the home page of the Client Area, go to Domains > Register a New Domain, and purchase an available domain.

If you are transferring a domain from another registrar to Hostwinds, go to Domains > Transfer Domains to Us. In addition to entering the domain name to be transferred, you'll need to enter an authorization code from the existing domain registrar (e.g., to initiate the transfer. Also, be sure to "unlock" the domain name with the existing domain registrar. Be patient. The transfer process can take up to a week.

If Hostwinds is acting as your registrar, you can monitor the status in the Client Area at Domains > My Domains. If the status is "Active" (as opposed to "Pending"), you can select "Manage Nameservers" in the pulldown menu to the right of the domain name. Make sure the "Use custom nameservers (enter below)" radio button is highlighted. Enter nameserver records for the "Nameserver 1" (e.g., and "Nameserver 2" (e.g., fields, and click the "Change Nameservers" button.

If you are happy with your current registrar (e.g.,, you need to repoint the domain's nameservers to Hostwinds' nameservers (e.g., and and change the IP address of the "A" records to the primary IP of your Hostwinds server.

Next, you'll need to associate your domains with your server. In the Client Area, go to Domains > Manage DNS. (As you'll see, this takes you to the Cloud Control interface.) Click the "Create" button, select "Domain (Add a domain name)", enter the domain (e.g., you want to host on your server, and click "Add Domain". The nameservers associated with your account will be your defaults.

Manage DNS Records with Hostwinds

If a domain is registered with Hostwinds and its status is "Active", you have the ability to add, edit, and delete DNS records in the Hostwinds interface. In the Client Area, go to Domains > Manage DNS (this takes you back to the Cloud Control interface). You should see a list of domains associated with your account. Assuming the status of the domain is "Active", clicking the "Actions" link will open a link to "Records".

The interface for adding records has four fields: the first field is the record "type" (e.g., A, TXT, CNAME, etc.); the second field is the "name" or "host"; the third field is the "value" or "points to"; and the fourth field is the "TTL" (time to live) or time in seconds until the record begins propagating.

A (address) Records
The most basic web hosting DNS record is the A record, which maps a domain name to an IP address. You'll need two of them. The default for the record type in the first field is "A". In the second field, enter "@" (which signifies the core domain name). In the third field, enter your primary IP address. The default TTL is 3600 seconds (one hour). Your second A record should match the first, except in the second field you'll enter "www" instead of "@". This record will point to "".

NS (nameserver) Records
Likewise, you'll need two NS records for your two nameservers. In the pulldown menu of the first field, select "NS". In the second field, enter "@". In the third field, enter your first nameserver (e.g., ""). Repeat the procedure to add a second NS entry for your second nameserver (e.g., "").

CNAME (canonical name) Records
If you need to create a subdomain, such as "", you should select "CNAME" in the first field. In the second field, enter "blogs". In the third field, enter "".

MX (mail exchange) Records
If you plan on sending email from your domain, three records are required.
Record 1. In the first field, select "CNAME". In the second field, enter "mail". In the third field, enter "". This creates a subdomain -- "".
Record 2. In the first field, select "MX". In the second field, enter "@". In the third field, enter "10". The number indicates the priority of the record (the lower the number, the higher the priority).
Record 3. As a backup, repeat the procedure above to create another MX record. In the third field, enter "20". The other fields should match the first MX record.

PTR (pointer) Records
If you plan to send email on behalf of the domain name (i.e., entering "" in the "From" field), you'll want to create a "pointer", or PTR, record, that maps an IP address to a domain. The PTR is used for reverse DNS (rDNS) lookup. Your Hostwinds account comes with a single primary IP address and several "assigned" IP addresses. The assigned IPs come in handy when PTR records need to be generated. Unlike other DNS records, the PTR is not created through the Cloud Control interface. Instead, you must return to the Client Area and go to Domains > Manage rDNS. There you'll see a list of your assigned IPs. Click on the edit icon of an IP address to map the IP address to a domain. (There's no need to create a separate PTR record in the DNS interface.)

Confirm that the PTR has been set...

% host

SPF (Sender Policy Framework) Records
SPF records are intended to validate the email sent from your domain. Although the Sender Policy Framework is a complex subject, entering a basic SPF record is simple. Start by selecting "TXT" in the first field. In the second field, enter "@". In the third field, enter something like "v=spf1 mx ip4:111.222.333.4 ~all" (in this case, you're allowing email to be sent from your IP address and from your Gmail account). You can check the syntax of your SPF record by plugging it into an SPF Validator (such as

DKIM (DomainKeys Identified Mail) Records
Unlike SPF, creating a DKIM record is not simple. In fact, there's a separate section below titled "Create DKIM Keys". You can deal with that later.

DMARC (Domain-based Message Authentication, Reporting and Conformance) Records
DMARC is an email authentication protocol with the best of intentions. It gives owners of a domain name the ability to defend their domain against spoofers. A DMARC record instructs receiving email servers how an email from your domain should be authenticated based on your SPF and DKIM records. If you send email from your domain only from your server, you can construct a strict DMARC policy. On the other hand, if you send email with your domain in the "From" field from your Gmail, Yahoo, Hotmail (you name it) account, a strict DMARC policy will result in a lot of rejected email. In that case, you should create a lax DMARC record (i.e., something is better than nothing).

In the first field, select "TXT". In the second field, enter "_dmarc". In the third field, enter "v=DMARC1; p=none; pct=100;".

If you have a domain name that does not send emails, you should created a record with a "p=reject" policy (e.g., "v=DMARC1; p=reject; pct=100;").

One final word about DNS: don't worry if you make a mistake. You can delete the record and replace it with a new one. The new record may take some to propagate across the Internet, but you haven't done any lasting damage to your domain.

Virtual Hosting with Apache2

At this stage, you should have a functioning web server. Of course, the point of a functioning web server is to host a functioning web site. In this section, we'll get to the heart of the matter. You'll create a user, transfer files, and configure Apache to serve up your web site.

I'm assuming that you signed up for a dedicated server because you wanted the flexibility of hosting multiple domains on a single server. That's where Virtual Hosting comes in to play. For each virtually hosted domain, you'll repeat the process below.

Create a User and Transfer Files

Add a user for your domain...

% adduser mydomainuser

The "adduser" command will prompt you to make several entries.
The "User Name" should match the core username of the account (e.g., mydomainuser).
If you are the sole admin on your server, you can enter your name for "Full Name" and accept the empty defaults for the other identifiers ("Room Number", "Work Phone", etc.).
The results of "adduser" will be the creation of a new directory for a virtually hosted account in the /home/ directory (e.g., /home/mydomainuser/).

The standard path for web files is the /var/www/html/ directory. In the example below, however, I'll use the /home/ directory to house my web files.

Create a "www" directory in the /home/mydomainuser/ directory...

% mkdir /home/mydomainuser/www

From your Hostwinds server, use rsync to transfer files from your old server...

% rsync -avz myusername@111.222.333.444:/home/oldmydomainuser/www/ /home/mydomainuser/www/

For convenience, add a symlink from / (the root directory) to the web content directory.

% cd /
% ln -s /home/mydomainuser/www/ mydomainuser

If you have a configuration file with passwords, email addresses, and other sensitive information, you should place it in a more remote, protected area of your server.

By default, "mydomainuser" is the owner of the directory created by running "adduser". That's fine if you edit your web files with one of Ubuntu's built-in text editors (such as nano or vm) and don't use SFTP to upload files. In my case, however, I prefer to edit web files with my favorite desktop text editor (UltraEdit), which connects to my server by SFTP. I also use WS_FTP to upload files on occasion. Because I rely on the SFTP protocol, I want to give my SFTP user easy access to my web files by changing the ownership of the files in the newly created /home/mydomainuser/ directory.

Change ownership...

% chown -R mysftpuser /home/mydomainuser

Create a New Database (I'm assuming your web site is powered by MySQL)

Log in to phpMyAdmin.

To create and set up a new database, click the "New" link in the phpMyAdmin sidebar and enter a database name (e.g., "MyDatabase"). Accept the defaults and click "Create".

To grant privileges to a new user, update the "mysql" database that was automatically created when MySQL was installed. (To repeat, all of these steps could be accomplished by running MySQL from the command line. Using phpMyAdmin just makes them easier.)

Go to the "mysql" database and add a new user to the "user" table with the following SQL statement...

INSERT INTO user SET Host = 'localhost', User = 'mydomainuser', ssl_cipher = '', x509_issuer = '', x509_subject = '', authentication_string = PASSWORD('mydbpassword');

The fields for the newly created user "mydomainuser" in the "user" table will be set to "no" by default.

Next, set the privileges for the user "mydomainuser" in the "db" table...

INSERT INTO db SET Host = 'localhost', Db = 'MyDatabase', User = 'mydomainuser', Select_priv = 'Y', Insert_priv = 'Y', Update_priv = 'Y', Delete_priv = 'Y', Create_priv = 'Y', Drop_priv = 'Y', References_priv = 'Y', Index_priv = 'Y', Alter_priv = 'Y', Create_tmp_table_priv = 'Y', Lock_tables_priv = 'Y', Create_view_priv = 'Y', Show_view_priv = 'Y', Create_routine_priv = 'Y', Alter_routine_priv = 'Y', Execute_priv = 'Y', Event_priv = 'Y', Trigger_priv = 'Y';

Reload the grant tables...


Dump Database on Your Old Server and Rebuild It on Your New Server

Dump database on your old server (the path to mysqldump may need to be changed)...

% /usr/bin/mysqldump --host=localhost --user=myolddbuser --password MyOldDatabase > /home/myolddomainuser/olddb.sql

From your Hostwinds server, transfer the database dump and rebuild the database...

% rsync -avz myusername@111.222.333.444:/home/myolddomainuser/olddb.sql /home/mydomainuser/newdb.sql
% mysql -u root -p MyDatabase < /home/mydomainuser/newdb.sql
% rm /home/mydomainuser/newdb.sql

Execute check, repair, optimize, and analyze the tables of your MyDatabase.

Configure Virtual Hosting

In past versions of Apache, you probably put all of your VirtualHost directives into a single configuration file. In Apache2.4, standard practice is to create a separate VirtualHost configuration file for each of your domains. The VirtualHost config files reside in the /etc/apache2/sites-available directory. A default file -- 000-default.conf -- serves as an example. (You'll want to make a back-up copy of the default file.)

Copy the default file...

% cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/mydomainuser.conf

The "mydomainuser" in "mydomainuser.conf" must match the username you created above with the "adduser" command.

Now it's time to open mydomainuser.conf in the text editor of your choice and plop in your VirtualHost directive. VirtualHost directives can be as simple or complex as you desire. In the example below, we'll keep things fairly simple, but with a nod to the use of PHP and Apache's mod_rewrite module: ServerName ServerAlias DocumentRoot /home/mydomainuser/www/ ServerAdmin ErrorLog /error.log CustomLog /access_log_mydomain combined SetHandler "proxy:unix:/run/php/php7.3-fpm.sock|fcgi://localhost" RewriteEngine on RewriteCond %{HTTP_HOST} !^www [NC] RewriteRule ^/(.*)$1 [L,R=permanent] RewriteEngine on RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f RewriteRule ^.*$ /traffic_director.php [PT]

You'll notice that the VirtualHost directive uses a wildcard in place of an IP address (). The wildcard points the domain to your primary IP address. If the default doesn't work for you, you'll need to specify the IP address in the VirtualHost directive (e.g., ).

If the domain features a subdomain (e.g.,, you'll need to include a separate VirtualHost directive in the config file for the subdomain. The ServerName and ServerAlias must reflect the subdomain. Place VirtualHost directives for subdomains above the VirtualHost directive of the core domain (e.g., Subdomains must be associated with a CNAME DNS record.

The example above assumes you're using FastCGI Process Manager to serve requests for PHP pages. The three lines of the directive instruct Apache to process files ending with a ".php" extension with the php7.3 version of the php-fpm extension: SetHandler "proxy:unix:/run/php/php7.3-fpm.sock|fcgi://localhost"

Below that are two rewrite rules. The first rewrites URL requests without "www" (e.g., to The second redirects all requests that are not for actual files to my traffic_director.php script, which, as the filename suggests, routes page requests to their proper destination.

Enable and Restart

Once you've finished editing mydomainuser.conf, you need to enable the configuration file. The a2ensite command creates a symlink in the /etc/apache2/sites-enabled directory...

% a2ensite mydomainuser.conf

Before restarting Apache, check the syntax of the new VirtualHost directive...

% apache2ctl configtest

Restart Apache...

% systemctl restart apache2.service

Once 000-default.conf is no longer needed, you should disable the default config file...

% a2dissite 000-default.conf

Let's Encrypt Certificates

Creative powersEncryption is quickly becoming the standard on the web, with Google favoring sites that use the https protocol over http. Fortunately, there's Let's Encrypt -- a non-profit certificate authority that provides SSL certificates at no cost. To generate a Let's Encrypt certificate, you can use Certbot -- yet another free, open-source software tool.

To begin the process, go to

Select your web server (e.g., Apache) and operating system (e.g., Ubuntu 18.04 LTS (bionic)). Certbot's web site presents command-line instructions for you to follow to install the most up-to-date version of certbot from the Certbot PPA. (Note that Python will be installed along with certbot if it's not already on your server.)

Once certbot is installed, you are in a position to generate Let's Encrypt certificates on your server. (Certificates can be created only if a domain's nameserver and A records resolve to your server.)

Create a certificate for

% certbot certonly -d -d

The certificate generated above will encrypt requests for both and

You will find several certificate-related files in the /etc/letsencrypt/ directory.

Edit Apache Config Files

Next, we need to reconfigure the Apache configuration file for your domain. You'll notice in the VirtualHost example above, the directive was instructed to listen to port 80. We need to change that to port 443:

Within the same VirtualHost directive, add the SSLCertificate section just below the ServerAdmin parameter. This tells Apache where to find the certificates you generated: SSLEngine on SSLCertificateFile /etc/letsencrypt/live/ SSLCertificateKeyFile /etc/letsencrypt/live/ SSLCertificateChainFile /etc/letsencrypt/live/

You also need to include a VirtualHost directive listening to port 80 that redirects requests from to Insert the port 80 directive above the port 443 directive: ServerName ServerAlias DocumentRoot /home/mydomainuser/www/ ServerAdmin SSLEngine off RewriteEngine on RewriteRule ^/(.*)$1 [R=permanent,L]

WARNING: Do not enable a domain that is listening to port 443 until the Let's Encrypt certificate has been generated and the SSLCertificate paths defined. Otherwise, Apache will throw a syntax error when you restart and ALL sites on your server listening to port 443 will fail to connect and instead trigger errors.

Let's Encrypt certificates expire in 90 days. Fortunately, all of your certificates can be renewed with a single command...

% certbot renew

Even easier is to renew your certificates through a cron job, which is our next subject.

Cron Jobs

Cron is a utility included with Ubuntu and other Unix-based operating systems. The software is nothing more than a task scheduler. The power of cron derives from the scripts you choose to schedule.

Cron scripts can be scheduled to run as often as you like -- from every minute to once a year. They can execute single shell commands or lengthy scripts written in PHP or other programming languages. I use cron jobs to automate a variety of chores, such as renewing Let's Encrypt certificates, cleaning up temp files, optimizing my databases, sending emails to customers, settling credit card transactions, generating XML sitemaps, etc. You get the picture.

Cron is already installed on your server but you do need to enable it...

% systemctl enable cron

Cron responds to instructions in the crontab (short for cron table) file. You could edit crontab directly. However, for convenience I suggest you create a separate file that contains a schedule of your cron jobs and can be easily edited at /var/spool/cron/mycronjobs.

If you use PHP to write cron scripts, the top of the file needs a "shebang" line pointing to the CLI version of PHP required. #!/usr/bin/php7.3 -q

After editing mycronjobs, refresh crontab...

% crontab /var/spool/cron/mycronjobs

Check crontab to make sure your changes have been recorded...

% crontab -l

Logrotate Configuration

Logrotate is installed and activated when you order your server. As the name suggests, it rotates the many types of log files found in the /var/log/ directory. The default configuration file is located at /etc/logrotate.conf. In addition, there are application-specific config files in the /etc/logrotate.d/ directory.

If you find that too many log backup files are being saved, you can edit the files in the /etc/logrotate.d/ directory. Lowering the value of the "rotate" parameter reduces the number of backup files saved in /var/log/.

By default, logrotate runs on a daily basis by executing the shell script /etc/cron.daily/logrotate.

Postfix Installation

Email server complexitiesPostfix is an open-source email server (or mail transfer agent). It was developed by Wietse Venema at IBM and debuted in 1998. Venema and others in the Postfix community continue to support its improvement. Postfix is the default email server in Ubuntu.

Postfix serves to both send and receive email. Personally, I'm very wary of hosting email accounts on my server. Past experience has taught me that email hosting brings non-stop headaches and few rewards. Instead, you should focus on making sure that email sent from your server reaches as many inboxes as possible.

Installing and configuring Postfix is a relatively lengthy process, with many optional detours. I opted for a fairly simple installation. It largely follows the instructions written by Xiao Guoan (aka, the Linux Babe). If you're looking for more detail, please visit (In general, Xiao's site is an outstanding resource for anyone working on an Ubuntu server, or other Debian-based systems.)

Install and Configure Postfix

Before you delve into Postfix, you need to set up a FQDN (fully qualified domain name) for your email server, such as Revisit the "MX (mail exchange) Records" heading in the DNS section to define the FQDN for your mail server.

Assign the domain as your FQDN...

% hostnamectl set-hostname

If you installed the Apache mod-evasive module, Postfix was installed as well. Execute the command below to determine if a version of Postfix resides on your server...

% postconf mail_version

If Postfix is not found or the version needs to be updated, install it...

% apt install postfix

Below are the key paths to managing Postfix on your server: /etc/postfix -- Postfix configuration files. /usr/sbin -- Postfix executables. /var/log/mail.log -- log file. /var/mail -- inboxes for each user.

Postfix uses port 25. Make sure it's not blocked by the UFW firewall...

% ufw allow 25/tcp

To use an email client, such as Thunderbird, for email, you'll need to unblock additional ports from the firewall...

% ufw allow 110,995/tcp

By default, the /etc/aliases file contains only one line -- postmaster: root. Generally, root is not used as an email address. Instead, the postmaster should use a login name alias to access emails. root: myemailuser

Implement changes to /etc/aliases...

% newaliases

Most of the configuration parameters you may want to adjust can be found in /etc/postfix/ As you'll see, Postfix gives you a lot of flexibility. For now, we'll begin with a few basic edits.

Change the "myhostname" parameter to your FQDN (e.g.,

Edit the "TLS parameters" as follows (I'm assuming you've already created a Let's Encrypt certificate for smtpd_tls_cert_file=/etc/letsencrypt/live/ smtpd_tls_key_file=/etc/letsencrypt/live/ smtpd_tls_security_level=may smtpd_tls_loglevel = 1 smtpd_tls_session_cache_database = btree:/smtpd_scache smtp_tls_security_level = may smtp_tls_loglevel = 1 smtp_tls_session_cache_database = btree:/smtp_scache smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

By default, the /etc/postfix/ file contains the following line to prevent your email server from acting as an open relay: smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination

To send email from an email client, such as Thunderbird, uncomment the "submission inet" section in /etc/postfix/

Reload Postfix...

% systemctl reload postfix

Install and Configure Dovecot

Dovecot works hand-in-hand with Postfix. It mainly acts as a mail storage server employing the IMAP and POP3 protocols. Notwithstanding my reluctance to host email accounts, you might as well install Dovecot in conjunction with Postfix (let's hope you can avoid becoming a serious Dovecot admin).

Install the IMAP and POP3 versions of Dovecot...

% apt install dovecot-core dovecot-imapd
% apt install dovecot-pop3d

Edit the main Dovecot config file at /etc/dovecot/dovecot.conf by adding the following at the end of the file: protocols = imap protocols = imap pop3

Open /etc/dovecot/conf.d/10-mail.conf to check if the "mail_location" and "mail_privileged_group" parameters are properly set.
By default, "mail_location" is already set to /var/mail (mail_location = mbox:~/mail:INBOX=/var/mail/%u), which is fine.
The other important line -- mail_privileged_group = mail -- is also present by default.

Add dovecot to the mail group so that Dovecot can read the INBOX...

% adduser dovecot mail

Edit the authentication file at /etc/dovecot/conf.d/10-auth.conf.
Uncomment the following line: disable_plaintext_auth = yes
Add the following line to require a full email address ( to log in: auth_username_format = %n

Make the following changes in the SSL/TLS config file at /etc/dovecot/conf.d/10-ssl.conf: ssl = required (from "yes") ssl_cert =
Disable SSLv3, TLSv1 and TLSv1.1 by adding the following line: ssl_protocols = !SSLv3 !TLSv1 !TLSv1.1

In /etc/dovecot/conf.d/10-master.conf, change the "service auth" section to the following: unix_listener /var/spool/postfix/private/auth { mode = 0660 user = postfix group = postfix }

In /etc/dovecot/conf.d/15-mailboxes.conf, find the "Drafts", "Junk", "Trash" and "Sent" mailboxes. Below the opening bracket of each mailbox, add "auto = create".
The result should look like the following: mailbox Trash { auto = create special_use = Trash }

Restart Dovecot and confirm that it's running...

% systemctl restart dovecot
% systemctl status dovecot

Restart Postfix...

% systemctl restart postfix

If you get sucked into hosting email accounts, you'll need to set up a cron job to delete email from the "Junk" and "Trash" folders of your email users. Below are examples of commands that delete emails in the "Junk" and "Trash" folders at least two weeks (2w) old...

% doveadm expunge -A mailbox Junk savedbefore 2w
% doveadm expunge -A mailbox Trash savedbefore 2w

Install and Configure PostfixAdmin

PostfixAdmin is a web-based application for managing Postfix. It's written in PHP and requires integration with MySQL. The PostfixAdmin interface is a little clunky and dated, but it gets the job done.

Because PostfixAdmin relies on MySQL, you first need to prevent the installation from reconfiguring your database...

% apt install dbconfig-no-thanks

Install PostfixAdmin...

% apt install postfixadmin

Remove dbconfig-no-thanks...

% apt remove dbconfig-no-thanks

Run the PostfixAdmin Configuration Wizard...

% dpkg-reconfigure postfixadmin

The wizard will prompt you to enter a few responses: Reinstall database: yes MySQL database connection: Unix socket Database name: postfixadmin (default) MySQL username for postfixadmin database: postfixadmin Database password: *********** Database admin user: debian-sys-maint

The wizard will install two new databases: "information_schema" and "postfixadmin".

Edit /etc/dbconfig-common/postfixadmin.conf to integrate MySQL: dbc_dbtype='mysqli'

Edit /etc/postfixadmin/ to integrate MySQL: ='mysqli'

The web files are installed under /usr/share/postfixadmin/ directory, which is owned by root. You need to give the "www-data" user read, write, and execute permissions on the Smarty template compile directory. Grant permissions...

% setfacl -R -m u:www-data:rwx /usr/share/postfixadmin/templates_c/

In the Client Area, go to Domains > Manage DNS and create a new CNAME record for your domain:

Create a new VirtualHost directive at /etc/apache2/sites-available/postfixadmin.conf: ServerName DocumentRoot /usr/share/postfixadmin/ ErrorLog /postfixadmin_error.log CustomLog /postfixadmin_access.log combined Options FollowSymLinks AllowOverride All Options FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all

Enable the config file...

% cd /etc/apache2/sites-available
% a2ensite postfixadmin.conf

Restart Apache...

% systemctl restart apache2.service

You should be able to access the PostfixAdmin web-based install wizard at

The set-up process populates the postfixadmin database. The three most important tables are: domain -- contains information on the domains that are using your mail server to send and receive email. mailbox -- contains information on every email address, including hashed passwords and the location of mail files. alias -- contains the alias of each email address.

Configure Postfix and Dovecot to Use the MySQL Database

By default, Postfix delivers emails only to users with a local account on your server. To make it deliver emails to virtual users whose information is stored in the database, you need to configure Postfix to use virtual mailbox domains. At this point, the postfix-mysql module should have been already installed.

Add the following to the end of /etc/postfix/ virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/ virtual_mailbox_maps = proxy:mysql:/etc/postfix/sql/, proxy:mysql:/etc/postfix/sql/ virtual_alias_maps = proxy:mysql:/etc/postfix/sql/, proxy:mysql:/etc/postfix/sql/, proxy:mysql:/etc/postfix/sql/

virtual_mailbox_domains points to a file that will tell Postfix how to look up domain information from the database.
virtual_mailbox_maps points to files that will tell Postfix how to look up email addresses from the database.
virtual_alias_maps points to files that will tell Postfix how to look up aliases from the database.

You'll need to create several config files in the /etc/postfix/sql/ directory. Start by creating the directory...

% mkdir /etc/postfix/sql/

Create the files below in the /etc/postfix/sql/ directory by following the instructions at

Restrict access to /etc/postfix/sql...

% chmod 0640 /etc/postfix/sql/*
% setfacl -R -m u:postfix:rx /etc/postfix/sql/

Edit /etc/postfix/

Since we are using virtual mailboxes, we need to remove the core domain name ( from the "mydestination" parameter.

Add the following lines to the end of the file: virtual_mailbox_base = /var/vmail virtual_minimum_uid = 2000 virtual_uid_maps = static:2000 virtual_gid_maps = static:2000

The first line defines the base location of mail files. The remaining three lines define which user ID and group ID Postfix will use when delivering incoming emails to the mailbox. We use the user ID 2000 and group ID 2000.

Restart Postfix...

% systemctl restart postfix

Create a user named "vmail" with ID 2000 and a group with ID 2000...

% adduser vmail --uid 2000 --disabled-login --disabled-password

Create the mail base location with vmail as the owner of the directory...

% mkdir /var/vmail/
% chown vmail:vmail /var/vmail/ -R

Configure Dovecot to use MySQL by installing the dovecot-mysql module...

% apt install dovecot-mysql

Edit /etc/dovecot/conf.d/10-mail.conf:
Change: mail_location = maildir:/var/vmail/%d/%n
Add: mail_home = /var/vmail/%d/%n

Edit /etc/dovecot/conf.d/10-auth.conf:
Change: auth_username_format = %u
Uncomment: !include auth-sql.conf.ext
Add: auth_debug = yes auth_debug_passwords = yes

Set the parameters below in /etc/dovecot/dovecot-sql.conf.ext: driver = mysql connect = host=localhost dbname=postfixadmin user=postfixadmin password=********* default_pass_scheme = ARGON2I password_query = SELECT username AS user,password FROM mailbox WHERE username = '%u' AND active='1' user_query = SELECT maildir, 2000 AS uid, 2000 AS gid FROM mailbox WHERE username = '%u' AND active='1' iterate_query = SELECT username AS user FROM mailbox

Restart Dovecot...

% systemctl restart dovecot


Command line concernsBefore we get completely lost in the weeds, lets remind ourselves that the main purpose of Postfix is to send email from your server into the inboxes of your recipients. To repeat, we want our email to land in the inbox. Not spam, not junk, not trash, and certainly not out in the ether. That's the reason we are once again addressing the issues of SPF (Sender Policy Framework) and DKIM (DomainKeys Identified Mail) in the Postfix section. For now, follow along. There is light at the end of the tunnel.

SPF Policy Agent

Install the SPF Policy Agent...

% apt install postfix-policyd-spf-python

Edit /etc/postfix/ by adding the following lines at the end of the file: policyd-spf unix - n n - 0 spawn user=policyd-spf argv=/usr/bin/policyd-spf

Edit /etc/postfix/ by adding the following lines at the end of the file: policyd-spf_time_limit = 3600 smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf

Restart Postfix...

% systemctl restart postfix


Install OpenDKIM...

% apt install opendkim opendkim-tools

Add postfix user to opendkim group...

% gpasswd -a postfix opendkim

Edit /etc/opendkim.conf by uncommenting the following lines, replacing "simple" with "relaxed/simple": Canonicalization relaxed/simple Mode sv SubDomains no

Add the following lines below "SubDomains no": AutoRestart yes AutoRestartRate 10/1M Background yes DNSTimeout 5 SignatureAlgorithm rsa-sha256

Add the following lines at the end of the file: # Map domains in From addresses to keys used to sign messages KeyTable refile:/etc/opendkim/key.table SigningTable refile:/etc/opendkim/signing.table # Hosts to ignore when verifying signatures ExternalIgnoreList /etc/opendkim/trusted.hosts # A set of internal hosts whose mail should be signed InternalHosts /etc/opendkim/trusted.hosts

Create a directory for opendkim keys...

% mkdir /etc/opendkim
% mkdir /etc/opendkim/keys

Change the owner and permissions for the newly created directory...

% chown -R opendkim:opendkim /etc/opendkim
% chmod go-rw /etc/opendkim/keys

Change the OpenDKIM Unix socket file so Postfix can communicate with it.

Create a directory to hold the OpenDKIM socket file and allow only "opendkim" user and "postfix" group to access it...

% mkdir /var/spool/postfix/opendkim
% chown opendkim:postfix /var/spool/postfix/opendkim

Edit /etc/opendkim.conf and replace: Socket local:/var/run/opendkim/opendkim.sock
with Socket local:/var/spool/postfix/opendkim/opendkim.sock

Edit /etc/default/opendkim and look for: SOCKET="local:/var/run/opendkim/opendkim.sock
or SOCKET=local:/opendkim.sock
Change it to: SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"

Add the following lines to the end of /etc/postfix/ # Milter configuration milter_default_action = accept milter_protocol = 6 smtpd_milters = local:opendkim/opendkim.sock non_smtpd_milters =

Restart opendkim and postfix service...

% systemctl restart opendkim postfix

Create DKIM Keys

Now that opendkim is installed and configured, we can finally generate DKIM records for your domains.

Create/edit the signing table at /etc/opendkim/signing.table and add entries for each domain that requires a DKIM key: * *

Create/edit /etc/opendkim/key.table and add entries for each domain that requires a DKIM key:

Create/edit /etc/opendkim/trusted.hosts and add the following: localhost * *

For each domain, you will generate a private key for signing and a public key for remote verification. The public key will be published in your DNS records.

Create a separate directory for each domain...

% mkdir /etc/opendkim/keys/
% mkdir /etc/opendkim/keys/

Generate keys for each domain using opendkim-genkey tool...

% opendkim-genkey -b 2048 -d -D /etc/opendkim/keys/ -s default -v
% opendkim-genkey -b 2048 -d -D /etc/opendkim/keys/ -s default -v

Change the owner of the private keys...

% chown opendkim:opendkim /etc/opendkim/keys/
% chown opendkim:opendkim /etc/opendkim/keys/

Display the public key for the domain...

% cat /etc/opendkim/keys/

Publish Your DKIM Record

In the Client Area, go to Domains > Manage DNS to create a DKIM record for each domain. In the first field, select "TXT". In the second field, enter "default._domainkey," which will default to For CNAME domains (e.g.,, type out the second field in full (e.g., In the third field, enter everything displayed by the above command that lies within the parentheses. This can be tricky, because you also have to remove all double quotes, line breaks, and tabs from the display.

Restart opendkim...

% systemctl restart opendkim

Test the key...

% opendkim-testkey -d -s default -vvv

The results should look like the following: opendkim-testkey: using default configfile /etc/opendkim.conf opendkim-testkey: checking key '' opendkim-testkey: key not secure opendkim-testkey: key OK

Don't worry about "key not secure". That means DNSSEC isn't enabled on your domain name.

Test your DKIM, SPF, and DMARC records at

To pass a DMARC check, an email needs to meet one of the following requirements:
1. Score an SPF "pass" with the Return-Path domain name being the same as the header.from domain.
2. Score a DKIM "pass" with the domain of the DKIM-Signature ( being the same as the header.from domain.