Virtualmin LAMP Server (Ubuntu)


Building on the basic Ubuntu Cloud Server (with Emerging Threats Protection) we will create an all-in-one internet hosting server using the Virtualmin web hosting control panel.

Add Support for TLSv1.3

The Apache package from Ubuntu 18.04, 16.04, repository isn’t built with OpenSSL 1.1.1. You can manually compile Apache with OpenSSL 1.1.1, but it takes extra time and you have to re-compile when a new version of Apache comes out. Luckily, we can install Apache from a PPA (personal package archive) by Ondřej Surý, who is a Debian developer and an important figure in the DNS community. He maintains many packages for Debian repository, including Apache, BIND, MariaDB, PHP etc. He is also one of the maintainers of the official certbot PPA. So I have trust in his PPA and use it on my servers.

add-apt-repository ppa:ondrej/apache2
apt update

Download & Install Virtualmin

cd /tmp
chmod +rx /tmp/

Before running the installation script, decide if you will locally host mail for end users (including IMAP/POP clients).

If you will not host email, then do a “minimal” install to exclude the full mail processing stack (SpamAssassin and ClamAV). This will save about 1GB of system resources.

/tmp/ --minimal

If you will be hosting email, then do the standard full install.


Verify the installation completed successfully.

Setup Firewall

If the UFW firewall was previously setup, uninstall it

apt -y purge ufw

Modify Blocklist Scripts for Firewalld

sed -i '/ipset create/D' /usr/local/bin/ 
sed -i '/ipset create/D' /usr/local/bin/

Modify firewall startup script

cat > /etc/rc.local <<EOF
#!/bin/sh -e
# rc.local
exit 0

Setup IP Blocklists

firewall-cmd --permanent --new-ipset=geo-block --type=hash:net
firewall-cmd --permanent --new-ipset=threat-ip --type=hash:ip
firewall-cmd --permanent --new-ipset=threat-net --type=hash:net
firewall-cmd --reload

Create Blocklist Rules

firewall-cmd --permanent --add-rich-rule='rule source ipset=geo-block log prefix="GEO BLOCK:" drop'
firewall-cmd --permanent --add-rich-rule='rule source ipset=threat-ip log prefix="THREAT BLOCK:" drop'
firewall-cmd --permanent --add-rich-rule='rule source ipset=threat-net log prefix="THREAT BLOCK:" drop'
firewall-cmd --reload
service firewalld restart

Move MySQL Data Directory

I like to locate the MySQL data under the /home directory so everything for Virtualmin is under a single path. This can be helpful if you later need to add disk space my moving /home to a separate drive.

Stop the MySQL service:

service mysql stop

Create new data directory:

mkdir /home/mysql
chown mysql:mysql /home/mysql

Define new data directory in MySQL config file:

sed -i '/datadir/c\datadir = /home/mysql' /etc/mysql/mysql.conf.d/mysqld.cnf

Update AppArmor:

echo "alias /var/lib/mysql/ -> /home/mysql/," >> /etc/apparmor.d/tunables/alias
service apparmor restart

Initialize New Data Directory (don’t worry, we will add a password during the Virtualmin setup later)

mysqld --initialize-insecure

Start MySQL:

service mysql start

Create SQL System Maintenance User. (This will include a new longer random password for the maintenance user)

RANDOM1=`< /dev/urandom tr -dc '[:alnum:]' | head -c${1:-64}`
cp /etc/mysql/debian.cnf /etc/mysql/debian.cnf.bak
cat > /etc/mysql/debian.cnf <<EOF
# Automatically generated for Debian scripts. DO NOT TOUCH!
host     = localhost
user     = debian-sys-maint
password = $RANDOM1
socket   = /var/run/mysqld/mysqld.sock
host     = localhost
user     = debian-sys-maint
password = $RANDOM1
socket   = /var/run/mysqld/mysqld.sock
basedir  = /usr
echo "GRANT ALL PRIVILEGES ON *.* TO 'debian-sys-maint'@'localhost' IDENTIFIED BY '$RANDOM1';" | mysql -u root
echo "GRANT PROXY ON ''@'' TO 'debian-sys-maint'@'localhost' WITH GRANT OPTION;" | mysql -u root

Apache Modifications

Enable mod_rewrite

a2enmod rewrite
service apache2 restart

Customization of HTTP request and response headers

cat > /etc/apache2/conf-enabled/security.conf <<EOF
ServerTokens Prod
ServerSignature Off
TraceEnable Off
Header unset ETag
FileETag None

Enable the mod_headers

a2enmod headers
service apache2 restart

Instruct browsers to allow cacheable content to be fetched from the browser’s cache for up to a week.

cat > /etc/apache2/mods-available/expires.conf <<EOF
<IfModule mod_expires.c>
    ExpiresActive On 
    ExpiresDefault "access plus 1 week"

Enable the mod_expires

a2enmod expires
service apache2 restart

Allow output from your server to be compressed before being sent to the browser.

cat > /etc/apache2/mods-available/deflate.conf <<EOF
<IfModule mod_deflate.c>
    <IfModule mod_filter.c>
         AddOutputFilterByType DEFLATE application/javascript
         AddOutputFilterByType DEFLATE application/rss+xml
         AddOutputFilterByType DEFLATE application/
         AddOutputFilterByType DEFLATE application/x-font
         AddOutputFilterByType DEFLATE application/x-font-opentype
         AddOutputFilterByType DEFLATE application/x-font-otf
         AddOutputFilterByType DEFLATE application/x-font-truetype
         AddOutputFilterByType DEFLATE application/x-font-ttf
         AddOutputFilterByType DEFLATE application/x-javascript
         AddOutputFilterByType DEFLATE application/xhtml+xml
         AddOutputFilterByType DEFLATE application/xml
         AddOutputFilterByType DEFLATE font/opentype
         AddOutputFilterByType DEFLATE font/otf
         AddOutputFilterByType DEFLATE font/ttf
         AddOutputFilterByType DEFLATE image/svg+xml
         AddOutputFilterByType DEFLATE image/x-icon
         AddOutputFilterByType DEFLATE text/css
         AddOutputFilterByType DEFLATE text/html
         AddOutputFilterByType DEFLATE text/javascript
         AddOutputFilterByType DEFLATE text/plain
         AddOutputFilterByType DEFLATE text/xml

Enable mod_deflate

a2enmod deflate
service apache2 restart

Download and install Google’s Pagespeed Module

cd /tmp
dpkg -i mod-pagespeed-stable_current_amd64.deb
apt-get -f install

Configure Pagespeed

cat > /etc/apache2/mods-available/pagespeed.conf <<EOF
<IfModule pagespeed_module>
    ModPagespeed on
    AddOutputFilterByType MOD_PAGESPEED_OUTPUT_FILTER text/html
    ModPagespeedFileCachePath "/home/.cache/mod_pagespeed/"
    ModPagespeedLogDir "/var/log/pagespeed"
    ModPagespeedSslCertDirectory "/etc/ssl/certs"
    ModPagespeedEnableFilters rewrite_javascript,rewrite_css
    ModPagespeedEnableFilters collapse_whitespace,elide_attributes
    ModPagespeedFileCacheInodeLimit 500000
    ModPagespeedEnableCachePurge on
    ModPagespeedPurgeMethod PURGE
    <Location /pagespeed_admin>
        Order allow,deny
        Allow from localhost
        Allow from
        SetHandler pagespeed_admin
    <Location /pagespeed_global_admin>
        Order allow,deny
        Allow from localhost
        Allow from
        SetHandler pagespeed_global_admin
    ModPagespeedStatisticsLogging on
    ModPagespeedMessageBufferSize 100000

Enable Pagespeed Module

a2enmod pagespeed
service apache2 restart

Install additional PHP Packages

apt -y install php-curl php-zip php-mcrypt php-mbstring php-gettext php-zip php-bz2 php-gd

Enable new PHP modules

phpenmod mcrypt mbstring gettext zip bz2 gd

Remove Default Apache Server.

rm /etc/apache2/sites-enabled/000-default.conf
service apache2 restart

Virtualmin Post-Installation Wizard

From a web browser, log in to the Virtualmin console at port 10000, using the root user credentials, and complete the Post-Installation Wizard. (https://myserver:10000)

Select System Settings on the left menu, then select the Re-check and refresh configuration button (this may fail if the system was not rebooted after installation of Virtualmin).

Disable Unnecessary Services

If you plan to host DNS elsewhere, disable Bind DNS.

systemctl mask bind9

If you plan to host email elsewhere, disable Dovecot Mail Server.

systemctl mask dovecot

I strongly encourage the use of ssh-based sftp instead of ftp/ftps. Disable Proftp server.

systemctl mask proftpd

Restrict Mail protocols

If you are not using the Virtualmin’s mail services, then let’s lock down the Postfix SMTP server so it cannot be an attack target. We cannot disable it completely as it will be needed to send outbound email from your server. We configure it so connections are only accepted from the server itself.

From the Virtualmin Console:

  • Select Webmin on the left menu bar
  • Expand the Servers menu and select Postfix Mail Server
  • Select General Options.
  • For the setting Network Interfaces for receiving mail select the second radio button and enter in the box.
  • Select Save and Apply button at the bottom of the page.

SSL Security

The default SSL/TLS configuration for Apache lacks good security. These changes will apply Best Industry Practices and pass compliance checking for PCI, HIPAA, and NIST standards.

Create Diffie-Hellman Key Pairs

openssl dhparam -out /etc/ssl/dhparam.pem 2048

Create New Apache SSL Config File

cp /etc/apache2/mods-available/ssl.conf /etc/apache2/mods-available/
cat > /etc/apache2/mods-available/ssl.conf <<EOF
<IfModule mod_ssl.c>
SSLRandomSeed startup builtin
SSLRandomSeed startup file:/dev/urandom 512
SSLRandomSeed connect builtin
SSLRandomSeed connect file:/dev/urandom 512
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl
SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase
SSLSessionCache         shmcb:${APACHE_RUN_DIR}/ssl_scache(512000)
SSLSessionCacheTimeout  300
SSLProtocol -All +TLSv1.2 +TLSv1.3
SSLCipherSuite TLSv1.3 TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
SSLHonorCipherOrder On
SSLOpenSSLConfCmd DHParameters "/etc/ssl/dhparam.pem"
SSLUseStapling on
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

Modify Apache2 Config File

sed -i '/SSLProtocol/D' /etc/apache2/apache2.conf
sed -i '/SSLCipherSuite/D' /etc/apache2/apache2.conf

Restart Apache

service apache2 restart

Finished – Reboot


After adding a new SSL enabled virtual server in Virtualmin, execute these commands to make sure the new server stays compliant and doesn’t override the SSL Protocol and SSL Ciphersuite that is defined by Apache SSL Config File.

sed -i '/SSLProtocol/D' /etc/apache2/sites-available/*
sed -i '/SSLCipherSuite/D' /etc/apache2/sites-available/*
service apache2 restart