AWS Archisoft Logo

Perfect7 LEMP Stack with Webmin & PHP7 on CentOS 7 (HVM)

Webmin / Virtualmin Interface Archisoft's new AMI (Amazon Machine Image) offers a turnkey (one-click) web server solution pre-configured and ready to deploy for DIY, small and medium size businesses. It is simple to deploy, yet configurable and also scalable via load balancers.

Perfect7 LEMP AMI comes in two variants.
1) Perfect7 LEMP AMI Base (without security)
2) Perfect7 LEMP AMI w/Security:
- Preconfigured with: Advanced Policy Firewall (APF), Brute Force Detection (BFD), (D)DoS Deflate (distributed denial-of-service attack mitigation) & Monit (server services monitor).

These are robust and highly available web servers, custom compiled with the latest open source technologies on a proven and stable enterprise class operating system (CentOS 7) with long term support, which is best suited for production environments. This AMI is a true one click solution. Once activated, follow the step by step instructions to setup your domain name, ftp, email user accounts with SPF & DKIM support. Various security and NGINX configuration examples are also provided to ease setup.

nginx http2 ALPN test

NGINX + HTTP2 w/ALPN support can serve large number of resources per webpage, over one TCP connection from client browser to your server, yet delivering the content several times faster than HTTP 1.1. This technology eliminates the need to optimize website performance with CDN's. Additionally, our benchmark tests shows PHP7 being 300-400% faster than PHP 5.x branch. When PHP7 is combined with NGINX, HTTP2 & ALPN support, the resulting server is much more available than ever before. You can actually use a smaller server to handle the same workload.

Wordpress Ready

This AMI is WordPress ready. See instructions below. To tighten security, it is possible to disable access to wp-admin & wp-login.php, while enabling access to specific IP's only. You can also install more than 1 copy of WordPress for development / testing, after which the new version can go live. See detailed step by step instructions below. test results

This AMI saves hundreds of hours of compilation and testing of various technologies. When you start using this AMI, you'll agree that it is named Perfect7 for reasons beyond the marriage of PHP7 & CentOS 7. Web server performance & stability can drastically vary, even if they are running the same technologies. However, when compiled based on benchmarking and compatibility testing, it produces a much higher level of server performance and stability. Test it for free (30 days trial) and you'll know that this AMI is production ready in all aspects.

Stay assured that this AMI was actually developed to host our own projects and projects of managed customers. We have made this available for public use via AWS marketplace for a minimal fee, starting at only $0.002/hr or $1.45/mth. Like AWS EC2 annual discount plans, annual subscribers will receive over 50% AMI discount to further reduce server cost.

Note: Detailed step by step instructions are provided covering even the basics like DNS setup to help DIY community.

Notes for WHM/cPanel Users

WHM/cPanel Users: Users of WHM/cPanel should understand that Webmin/Virtualmin/Usermin control panel is as or more powerful platform. Webmin/Virtualmin has lots to offer, it is a more reliable and stable platform, especially it will not bloat or break your site with automated updates & features. Also Webmin/Virtualmin is far ahead in delivering today's technology rather than waiting months and years. Additionally, you can use this open source control panel for free as long as you want. If at anytime support is required, upgrade to pro version for added features and support (plans start at only $5/mth).

Important: Do not expect Webmin/Virtualmin to look and or work in the same way/manner as WHM/cPanel. These two control panels take completely different approaches. If you expect one to work like the other, then disappointment is unavoidable. It is easy to hate something that is not properly understood. Tip #1: Forget all that you know about other control panels such as WHM/cPanel and come with an open mind to learn something different, yet powerful. Tip #2: Be prepared to do some research ask questions at the Virtualmin forums to help with the learning curve before you could form an opinion about its capabilities. Tip #3: Understand that Webmin/Virtualmin takes a unix like approach, which sometimes require learning. But it is all worthy to break free from expensive, proprietary, outdated & at times not so reliable platform.

Our team has prepared step by step instructions to help with the transition and to get you started quickly!

Perfect7 LEMP AMI Features:

Perfect7 LEMP AMI v1.2

CentOS 7 - Long Term Support Linux OS
NGINX v1.11.3 - Highly Available, Scalable & Configurable Web Server
MariaDB - High performance database - reverse compatible with MySQL
PHP 7 - Super fast code execution. 300%-400% faster than PHP 5.x
Control Panel - Pre-Configured with Webmin/Virtualmin, a powerful & open source alternative to cPanel (one-click upgrade to pro version with support for $5/mth)
HTTP2 with ALPN Support - NGINX is custom compiled with the latest version of OpenSSL for out of the box ALPN support
• Pre-Configured Email Encryption, DMARC, SPF & DKIM signing
Auto SSL Certificates by Let's Encrypt (lifetime free SSL certificates for all your domains & subdomains)
ProFTP with TSL/SSL support
phpMyAdmin, RoundCube, X2Engine etc. (One-click install via control panel)

Perfect7 LEMP AMI v1.2 w/Security

• Pre-Configured with APF Firewall with DShield
• Pre-Configured with Brute Force Defence (BFD)
• Pre-Configured with Customized (D)DoS Deflate script for Denial of Service/Distributed Denial of Service attack mitigation
Monit with custom configurations to monitor & restart server services
• PHP.INI security tweak

Free Step by Step Instruction from Perfect7 AMI Team - Topics:

Launch an instance using Perfect7 AMI, allocate static IP and configure DNS (with Google Cloud DNS)
Setting up AWS Security Group (firewall rules)
Initial login via SSH, Setup Root Password & Software Update
Setup APF, BFD, (D)DOS & Monit (w/Security)
First time login to Webmin/Virtualmin control panel
Setting up your first domain
Setting up AutoSSL with Let's Encrypt & Diffie-Hellman group
Setting up Postfix, SPF, DMARC & DKIM signing with DNS setup instructions
Create FTP & Email Users / Accounts
Install phpMyAdmin, RoundCube, Django, X2Engine etc.
NGINX Configuration with HTTP2 & ALPN support

Optional instruction topics:

Advanced NGINX Configuration
Setup External MySQL Server with Amazon RDS (optional)
Setting up Elastic File System (EFS) with auto-mounting (optional)
WordPress Installation & Nginx Configuration (optional)
Setting up your Pre-Configured AMI

Step by Step Instructions:

Launch Perfect7 AMI, Setup Static IP & DNS

Click here to see all Archisoft AMI's at AWS marketplace. Alternatively, you can also login to your account and find our AMI from AWS control panel as follows:

Signup for an AWS account from and login. Then click on "Services" from top left. Then click on EC2 & then click on "Launch Instance". Then click on "AWS Marketplace" from the left tabs. In the search box type "perfect7" to lookup for Perfect7 LEMP AMI's.

Static IP

AWS Cloud instances are provided with non static (ephemeral) IP address, which changes without notice. However, a web server (or load balancer instance) requires a static IP address to map your domain name to your server.

After launching your AMI, click on "Elastic IPs" from the left menu under NETWORK & SECURITY. Then click on "Allocate New Address" button. Once allocated, select the IP and click on "Actions" button to select "Associate Address" and select the instance that you just launched using Perfect7 AMI and allocate IP address.

Request to Remove Email Sending Limitations

By default AWS restricts outgoing email from newly obtained IP's. For production, it is important to have these limitations removed. Click here to fill out the form to have this limitations removed by AWS. In Reverse DNS Record, enter your primary domain name like this: www.yourdomain.tld or yourdomain.tld (whichever way you intend to use your domain).

Note: Only after this limitation is removed (you will receive an email confirmation), you can test outgoing emails from your web server as described in the steps below.

Setting Up DNS for your Domain Name

DNS (Domain Name System/Server) is internet's domain name to IP address lookup system. When someone clicks on your domain name, the browser first tries to resolve the domain "name" to IP address via a DNS server. It is possible to register your own name servers from your domain control panel like this: ns1.yourdomain.tld & ns2.yourdomain.tld and then configure this from the Virtualmin control panel. However, it is a less reliable practise. It is best to use an external DNS server which is extremely available and fast.

We recommend Google Cloud DNS (low cost) or Amazon Route 53 (for advanced latency based load balancing). Our instructions are based on Google Cloud DNS, but the concepts are the same.

Once you create an account with Google Cloud DNS, login > Networking > Cloud DNS and click on "CREATE ZONE". Zone Name: Give it a name for your reference inside Cloud DNS interface. DNS Name: Enter your domain name like this: yourdomain.tld Click on "Create" button.

After creating your zone, the first step is to add your Static IP that you assigned Perfect7 AMI web server instance (AWS EC2 interface).

Google Cloud DNS A Record

From Google Cloud DNS interface, create an "A" record for your server's IP address. Click on "+ ADD RECORD SET" from the DNS zone that you just created. From this interface paste your IP address into "IPv4 Address" field and click create button. No need to edit any other field (Resource Record Type: A | TTL 5).

Google Cloud DNS CNAME Record

Create 5 more records with type "CNAME". First record: Click on "+ ADD RECORD SET". In "DNS Name" field, type "www". In "Resource Record Type" select CNAME. In "Canonical name" field, type yourdomain.tld. This CNAME record enables www.yourdomain.tld resolve to yourdomain.tld.

Repeat this step to create CNAME records for subdomains: "mail", "ftp", "host" & "ns". If you require to activate more subdomains, you can create more CNAME records. If your project require lots of subdomains, you can create a wildcard "A" record (use "*" for "DNS Name") and enter your static IP in the "IPv4 Address" field. This will forward all subdomain traffic to your server (note: if you use wildcard A record, still, above mentioned CNAME records are required).

Setting Up Name Server for "Your Domain Name"

Login to your domain registrar control panel (like: godaddy / Go to your domain name settings and look for "Nameservers" and add all the nameservers one by one as provided by Google Cloud DNS for your domain name. Google DNS will look like this:

	# example Google Cloud Name Servers #

Note: After adding & saving nameserver information from the domain control panel, allow ample time (it can take few hours to few days) for your domain name to begin resolving to the IP address. Subsequent edits like changing IP address or creating new DNS records from Google Cloud DNS will resolve almost instantly, but it takes time during initial name server setup at the domain control panel.

Setup AWS Security Group

Now its time to access your server via terminal and the browser. AWS Security Group acts as the primary firewall, therefore, appropriate "allow rules" must be defined in order to access your instance.

From Services > EC2 screen, click on "Security Groups" from the left menu. Select the security group to edit and then click on "Actions" button and select "Edit Inbound Rules" and add the following rules by clicking on "Add" button.

	Type: HTTP  | Protocol: TCP | Port Range:  80 | Source: Anywhere (
Type: HTTPS | Protocol: TCP | Port Range: 443 | Source: Anywhere (
Type: SMTP | Protocol: TCP | Port Range: 25 | Source: Anywhere ( >> to send & receive emails
Type: All traffic | Protocol: All | Port Range: 0 - 65535 | Source: My IP (your ip)

Initial SSH login, Setup Root Password & Software Update

From Services > EC2 Page, click on instances from the left menu, then select the Perfect7 instance, click on "Actions" button and then click on "Connect" for connections methods and instructions.

Once connected to your instance, follow the instructions below to be executed from the command prompt.

Create Root Password for Webmin/Virtualmin Control Panel

Webmin/Virtualmin Control Panel uses your root login credentials. The username is root, to create a password, use the following command.

$ sudo passwd root
New password: 
Update Server Software

Periodic system updates is recommended to upkeep with software security patches and versions. Run the following command to update your server software.

$ sudo yum -y update

Webmin/Virtualmin is pre-configured to daily update security patches. You can change this settings from Webmin/Virtualmin control panel from Webmin Tab > System > Software Package Updates (see below under "Scheduled checking options").

Setup APF, BFD, (D)DoS & Monit (w/Security)

NOTE: Users of w/Security AMI have reported lockouts. If this happens disable APF from terminal using the command below. Also uninstalling and reinstalling APF has been found to resolve this issue.

$ sudo /usr/local/sbin/apf -f

Instructions for Perfect7 AMI "w/Security"

Whitelist all IP's that will interact with your server. Failure to do so, might get you locked out. To obtain your IP address, visit this page: Replace with your IP address in the following commands. Repeat steps to add more IP's.

APF Firewall & Allow List

Advanced Policy Firewall (APF) is an iptables (netfilter) based firewall system. APF extends Linux firewall with additional features and easy interaction by you the server administrator as well as other applications such as (D)DoS Deflate & Brute Force Detection (BFD)

In APF there are allow & deny rules. As an web administrator you can manually set these rules. (D)DoS Deflate & BFD automatically sets deny rules based on your settings. It is highly recommended to set "allow" rules for all of your IP addresses (including EC2 elastic IP) so that you will not lock yourself accidentally.

To Create an Allow Rule, run the command below.

$ sudo /usr/local/sbin/apf -a

APF deny rules are created with option "-d" followed by the IP (instead of "-a" to allow). Option "-u" is used to remove an IP rule. To see all APF options, run this command sudo /usr/local/sbin/apf.

APF allow and deny files are located at cd /etc/apf/. Use "sudo su" command (super user) to browse this directory. Use "exit" command to get back to local user.

APF config file can be edited with this command: sudo nano /etc/apf/conf.apf. Note: APF is preconfigured with DShield & SPAMHAUS block rules.

With APF you can maintain a Global Allow & Deny ruleset. We recommend this, as you can allow or block traffic to IP's (or networks like without having to use terminal commands. Every 10 minutes APF will automatically download and apply the Global rules.

To enable Global Trust, edit APF config file as follows: sudo nano /etc/apf/conf.apf

# Global Trust
# This is an implementation of the trust rules (allow/deny_hosts) but
# on a global perspective. You can define below remote addresses from
# which the glob_allow/deny.rules files should be downloaded from on
# a daily basis. The files can be maintained in a static fashion by
# leaving USE_RGT=0, ideal for a host serving the files.




Brute Force Defence (BFD) & Ignore List

BFD is a modular shell script for parsing application logs and checking for authentication failures. It does this using a rules system where application specific options are stored including regular expressions for each unique auth format. IP's that exceed login failures as defined in the config file are automatically blocked with APF deny command. (you will receive an email for each block incident)

BFD config file can be edited with this command: sudo nano /usr/local/bfd/conf.bfd.

Add your IP's (one per line) to ignore list by editing this file:

$ sudo nano /usr/local/bfd/ignore.hosts

CTRL + x to exit nano editor.

Use the following command to start BFD. Use option -a to list addresses that have attacked this host (BFD is configured to start automatically).

$ sudo /usr/local/sbin/bfd -s

(D)DoS Deflate

(D)DoS Deflate is a lightweight bash shell script designed to assist in the process of blocking a denial of service attack. This script periodically runs this command "netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr" to calculate and sort network connections. IP addresses with over a pre-configured number of connections are automatically blocked in the server's firewall, with an APF deny command. You will receive emails for each IP block incident.

(D)DoS Upgrade

Since the launch of this AMI, we found some bugs in DDoS, therefore an upgrade is required. From SSH terminal, run the following command to upgrade DDoS to the latest version.

cd ~ && sudo wget && sudo chmod 0700 && sudo ./

Note: The script will prompt the following question: Autoinstall dependencies by 'yum'? (n to exit) type y and press return key to continue.

Customizing (D)DoS

You can edit the config file with this command: sudo nano /etc/ddos/ddos.conf

"NO_OF_CONNECTIONS=150" this setting bans all IP's that exceed 150 simultaneous connections. 150 is relatively large number. Edit this to a lower number in the range of 30, because HTTP2 + ALPN web server requires only 1 connection per browser session, even if the page has 5000 images & other resources! In our opinion, any IP's with more than 30 simultaneous connection is a rogue bot, unless, you have a situation where building full of people with the same IP is accessing your server all at the same time.

"BAN_PERIOD=600" this setting locks out an offending IP for 600 seconds (10 minutes). Adjust this number as required. To block a IP for 1 day, enter 86400 instead of 600. When you see the same IP's blocked frequently, it is recommended to add repeat offenders to APF global or local deny rules.

HOST_IP="" Replace with your EC2 static IP address. Note: If this instance will be assigned random IP address, leave this settings to its default value of

Edit Ignore List: Add all your IP's to ignore list to prevent accidental lockout. ALSO ADD EC2 INSTANCE ELASTIC IP. The file should contain Recommended to add (aws metadata ip). If you are using Google DNS - add their ip's as shown. Replace with your IP.

$ sudo nano /etc/ddos/ignore.ip.list

CTRL + x to exit nano editor. Banned IP list is located at: sudo nano /var/lib/ddos/bans.list

Restart DDoS for settings to take effect: sudo /etc/init.d/ddos restart

Monit Configuration

Monit has been preconfigured to monitor most vital services in your web server. If a server service should go down, Monit will automatically try to restart it (you will receive email notification for each such incident).

You can edit Monit config file with the following command. Add your domain to the mail-format & uncomment the 5th line, as shown:

$ sudo nano /etc/monit.d/monitrc

set mail-format { from: monit@yourdomain.tld }

CTRL + x to exit nano editor. Run the following command to restart Monit service.

$ sudo systemctl restart monit

Additionally use this command to reload all commands in Monit: sudo monit reload. To start all services use sudo monit start all command.

Monit Server Configuration
Monit dashboard

By default Monit webserver is pre-configured to run in port 2812 (accept connection from localhost). Default username: admin password: monit. Monit Server configuration file is located at: sudo nano /etc/monitrc


Refer to NGINX advanced setup instructions below (under .GlobalHttp Settings) to see how Monit and various other services such as phpMyAdmin is configured to be accessed securely via port 10001. (https://yourdomain.tld:10001/monit/)

Login to Webmin/Virtualmin Control Panel

You can access your Webmin/Virtualmin Control Panel via the static IP that you assigned in the first step. For this example, lets assume as your instance IP. To login to your Webmin/Virtualmin Control Panel use the following url pattern: Make sure that the protocol is "https" and the port is 10000 (ten thousand). Use root for username and the password that was setup earlier.

Re Run Virtualmin Wizard
1) Re-Run Install Wizard & Set MySQL Password

Click on Virtualmin Tab > System Settings > Re-Run Install Wizard. This will walk you through the basic options. You can come back to this and change any of these options at anytime.

During this setup, you can create MySQL password by selecting "Yes" to this question "Run MySQL database server?" (required to follow other steps below).

In "Primary nameserver" field enter a name server like this: ns.yourdomain.tld (as setup in DNS CNAME record above).

Note: It is recommended to leave Preload Virtualmin libraries? to "Yes (more RAM used, faster Virtualmin UI - approximately 40M)"

Re-Check Configuration
3) Re-Check Configuration & Update IP address

Virtualmin Tab > System Settings > Re-Check Configuration. You should not see any errors in the result page except "Update Incorrect IP Addresses" message. Now, click on "Update Incorrect IP Addresses" button and then click on "Change Now" button on the next page.

Create Site / Create Virtual Server

Create Virtual Server

Now you can create the first site in your shiny new server!

From Virtualmin tab click on the very first option "Create Virtual Server" (Virtualmin Tab > Create Virtual Server) and enter your domain name and a password and then click on "Create Server" button.

Update Hostname and DNS for your Server
webmin Hostname DNS settings

Go to Webmin Tab > Networking > Network Configuration > Hostname and DNS Client
Edit Hostname: host.yourdomain.tld
Search domains: yourdomain.tld (refer screen shot)
Click on "Save" button
Click on "Apply Configuration"

Setup AutoSSL with Let's Encrypt & Diffie-Hellman group

Let's Encrypt SSL certificate

No more paying or renewing SSL certificates. Now its free and automated!

Click on Virtualmin Tab > yourdomain.tld > Server Configuration > Manage SSL Certificate > click on Let's Encrypt Tab. You can add more subdomains or other domains into the "Request certificate for" field. However, if those domains are not setup properly, Let's Encrypt authentication will fail altogether. For this initial setup it is recommended to leave this field blank and keep the default option: "Domains associated with this server"

Important: "Months between automatic renewal" option must be changed from "Only renew manually" to the next option and enter 2 in the field. See screenshot for reference.

Note: Your domain name must resolve to AWS Static IP before requesting SSL certificate form Let's Encrypt. Test from your browser by typing your domain name to make sure that it is reaching your Amazon Server.

Now click on "Request Certificate". If successful, you will see a message like this:

Requesting a certificate for yourdomain.tld, www.yourdomain.tld from Let's Encrypt ..
.. request was successful!
Configuring webserver to use new certificate and key ..
.. done

Applying Nginx configuration ..
.. done

If this process should fail, make sure that your domain name is properly resolving to your server IP address. In the steps above, you created a DNS record for your domain with Google Cloud DNS (or similar DNS service). After this step, you updated the nameserver information at your domain registrar's control panel. It can take several hours to couple of days for the new settings to take effect. To test if your domain is resolving to your AWS AMI's Static IP, open a browser and type: yourdomain.tld and you should see a 403 page like this: "403 Forbidden ---- nginx/1.11.3". You should also be able to access this page directly by typing the Static IP address instead of your domain name. Another way to test domain to ip resolution is from terminal / command prompt > type "ping yourdomain.tld" and enter; the result should show your AMI's Static IP address.

If your domain resolves to your new server and still Let's Encrypt fails, it is possible for Let's Encrypt system to have some temporary issues. It is not advisable to try more than 2-3 times, as your IP might get banned. Just skip this step for now and try again in a day or two.

Add certificate to all services
Add SSL Certificate to Server Services

From the same mange SSL interface: Virtualmin Tab > yourdomain.tld > Server Configuration > Manage SSL Certificate > click on each of the buttons such as: "Copy to Webmin", "Copy to ProFTPD" etc... to use this auto renewing SSL certificate and also enable TLS/SSL encryption to server services such as ProFTP etc.

Note: After clicking on "Copy to Webmin", the control panel interface may become unresponsive. This is normal because the SSL certificate that your browser is connected is replaced with a new one. Just log back again via the same url (example: to continue with other buttons or click on "Copy to Webmin" button last.

Diffie-Hellman Group

Generate a strong Diffie-Hellman group as an additional layer of security & provide Perfect Forward Secrecy with ephemeral form of the Diffie-Hellman key exchange. Login to your server via SSH and run these commands. Note: this step will take a while to complete.

$ sudo mkdir /etc/ssl/certs/dhparam	
$ sudo openssl dhparam -out /etc/ssl/certs/dhparam/dhparam.pem 4096

Email Setup: Postfix, SPF, DMARC & DKIM

Perfect Email Delivery
Root email signing

A perfect email system is an absolute necessity, whether, receiving a server notification from root or an important email to be delivered (from a PHP shopping cart script) to your customer. Perfect7 AMI allows you to configure the most perfect email server configuration for your business. When the step by step instructions are followed to set up SPF, DKIM and DMARC (Domain-based Message Authentication, Reporting & Conformance) all your outgoing emails will be encrypted & authenticated for successful email delivery to inbox. Also your domain name cannot be used by spammers to send emails from their servers. When DMARC is set to reject, email servers (like gmail) will reject all emails that do not pass SPF & DKIM. In other words, only your server is authorized to send emails from @yourdomain.tld email address. Spoofed emails sent from another IP address or domain will be discarded (rejected), and will not reach junk / spam folders.

Let's get started: Note: Port 25 must be opened in AWS firewall to test email features. Email sending limitations must also be removed for your IP address (as described in the first step)
Route ROOT Email's

Most "system critical" alerts and notifications are emailed to the user "root". In this step, let's get root emails routed to your favourite email address such as ""

go to Webmin Tab > Servers > Postfix Mail Server > Mail Aliases > click on root (last item in the page) > Enabled?: "Yes" & Alias to: (see third screenshot below). Click on "Save" button to complete/

Postfix Mail Aliases Postfix Mail Aliases root Postfix Mail Aliases root Edit Alias
Add your Domain to Postfix Config File

Webmin Tab > Servers > Postfix Mail Server > Edit Config Files > Scroll to
Line 75: Uncomment & Edit this line like this: myhostname = host.yourdomain.tld
Line 83: Uncomment & Edit this line like this: mydomain = yourdomain.tld (refer screen shot)
After editing, click on "Save and Close" button and then click on "Reload Configuration" button in the next page (bottom left) to restart Postfix Server.

Postfix Mail Server Edit Conf Postfix Mail Server Edit Config File
DNS Options SPF DMARC Setup

Virtualmin Tab > yourdomain.tld > Server Configuration > DNS Options:
SPF record enabled?: "Yes"
Allowed sender mail domains: yourdomain.tld & host.yourdomain.tld
Allowed sender IPv4 addresses: enter your Static IP address
Action for other senders: "Disallow" (or according to your preference)
DMARC record enabled?: "Yes"
DMARC policy for emails that fail SPF or DKIM: "Reject email" (best option for productions server. Select "Take no action" for testing, if emails are not being delivered)
Percentage of messages to apply policy: "100%" (or according to your preference)
Click on "Save" button

Virtualmin DKIM setup
Setup DKIM

Virtualmin Tab > Email Messages > DomainKeys Identified Mail:
Signing of outgoing mail enabled?: "Yes"
Force generation of new private key?: "Yes"
Additional domains to sign for: "yourdomain.tld" & "host.yourdomain.tld" (one domain per line)
Click on "Save" button

Setup External DNS Records
DNS Records for

For speed, availability and performance, we recommend the use of external DNS servers instead of the inbuilt DNS server. Therefore, after setting up SPF, DMARC & DKIM, all of the resulting DNS records must be copied over to the external DNS server.

Let's take a look at your current DNS records. Go to Virtualmin Tab > yourdomain.tld > Server Configuration > DNS Records: Your screen should closely match the screen shot from one of our production server for domain "" with IP "".

Now click on "Manually Edit Records" to view the entire records. The last 4 records are MX, SPF, DMARC & DKIM (domainkey), these DNS records must be recreated at your external DNS provider such as Google Cloud DNS.

Let's now copy DKIM record to Google Cloud DNS. From Google Cloud DNS, click on "+ ADD RECORD SET"; In the "DNS Name" field enter: default._domainkey "Resource Record Type" select "TXT". In the "TXT data" field, copy and paste DKIM records from your Virtualmin DNS Records screen. Take special care to copy the data block as shown in the screen shot including the quotes but excluding the brackets. After pasting, click on "Create" button.

Repeat this step to create DKIM record for host.yourdomain.tld. In the "DNS Name" field enter: This step will ensure root emails from your server will be signed.

Google Cloud DNS DKIM TXT Record DKIM Records for

Let's now repeat the same procedure to setup a TXT record for DMARC. In the "DNS Name" field enter: _dmarc

Google Cloud DNS DKIM TXT Record DMARC Records for

Let's now create another DNS record with type "TXT" (Google Cloud DNS has "SPF" option, but use "TXT" option). Leave the "DNS Name" field blank. Select "TXT" for type. Copy and paste SPF records from your Virtualmin DNS records for SPF as shown in the screen shot.

Google Cloud DNS SPF Record SPF Records for

Also create another record with type "MX". Leave the "DNS Name" field blank. In the "Preference and mail server" field copy & paste or type in this format: 5 mail.yourdomain.tld.

Google Cloud DNS Records Final

Once the above mentioned DNS entries are added, your DNS record should look like the screenshot on the right. Of course this is the starting point configuration, you may have to create more DNS records as required, especially if you are going to use Amazon Simple Email Service (SES) - recommended for both incoming and outgoing emails as your production email server. Because it can reduce your server loads and help close down port 25 to further secure your server.

Gmail header

However, do not skip email & DNS configuration as it is required for proper email delivery of outgoing emails sent by PHP scripts and root emails from your server. See screenshot of an email from this AMI as received by Gmail. You'll see that the email server is properly configured to show mailed-by & signed-by information as well as the email is encrypted.

Create FTP & Email Users / Accounts

Let's create a FTP account and upload some files to your brand new website. Go to Virtualmin Tab > yourdomain.tld > Edit Users > click on "Add a website FTP access user" button (top right). On the next screen create an user as shown in the screenshot

Mail FTP Users Create FTP User coda FTP settings
Setup FTP Client

Now you should be able to FTP into your site's root folder using a compatible FTP client and upload your index.php file. See screenshot for FTP configuration. This screenshot is from "Coda", a code editor with built in FTP client. Observe how the FTP username is entered. Also, notice that "FTP with TLS/SSL" & passive mode is used as the FTP protocol option.

!!! FTP Access Issues: Set Permission for FTP User !!! Try this only IF you could not connect via FTP
Webmin Edit User

If you should encounter FTP access issues, try this: Go to Webmin Tab > System > Users and Groups > Scroll down and you'll see the FTP user that you just created. Click on the user and change "Shell" to "/bin/sh" and then scroll to bottom and click on "Save" button, Now try again to connect via FTP.

Create an Email Account

Go to Virtualmin Tab > yourdomain.tld > Edit Users > click on "Add a user to this server" button located towards the top left. Creating a user account from this page provides with email access for that user. You can also choose to grant FTP access. This FTP is not website root access, but acts as a storage container where the user can store files. You can also manage the user's MySQL access from this screen.

round cube login screen

You can now install an open source email system like RoundCube from Virtualmin Tab > yourdomain.tld > Install Scripts. Email & FTP usernames always ends with your site name. Refer screenshot. You can also configure your favourite desktop email client to send and receive emails.

Install phpMyAdmin, RoundCube, Django, X2Engine etc.

Go to Virtualmin Tab > yourdomain.tld > Install Scripts > click on "Available Scripts" tab to see all available scripts. Free version includes about 7 scripts. Upgrade to Pro version for over 100 scripts including WordPress.

Note: Installing open source scripts to your website like yourdomain.tld/phpmyadmin/ can be a major security risk. Refer to NGINX advanced setup instructions below (under .GlobalHttp Settings) to see how phpMyadmin and other directories can be accessed securely via port 10001. (https://yourdomain.tld:10001/phpmyadmin/)

NGINX Configuration with HTTP2 & ALPN support

webmin nginx config http2

Assuming that a valid SSL certificate has been installed, lets now enable HTTP2 over SSL.

Go to Webmin Tab > Servers > Nginx Webserver > Edit Config File. Scroll all the way down and you'll see a line like this:
listen default ssl; change it to:
listen 443 ssl http2;

Also scroll slightly up and replace the line which looks like this
listen; change it to:
listen 80;.

Click on "Save and Close" button; on the next page click on "Apply Nginx Configuration" button to restart Nginx server. Alternatively, Nginx service can be restarted from SSH with this command: sudo systemctl restart nginx (much faster).

Notes: After completing all of your configuration, it is recommended to take a snapshot, from which you can launch new instances at anytime. When you do so, new instance will be assigned a different NAT IP. Therefore, setting "listen" directive to a port number ensures proper functioning when new instances are launched from this AMI snapshot.

nginx http2 ALPN test
Testing HTTP2 & ALPN Support

Visit and enter yourdomain.tld to test your server configuration. You should see: "supports HTTP/2.0" & "ALPN supported".

Advanced Configuration:

NGINX Configuration

NGINX offers very large number of configuration options, yet the same configuration file is responsible for the entire NGINX server settings. There are no directory level configuration files like .htaccess. This could be challenging for those coming from Apache and especially those who rely on rewrite rules - but do not fret, we'll show you how to migrating from .htaccess

NGINX can be configured many different ways. One approach may be better than the other, but what works for you might be different, depending on how you are going to use the server and the desired level of performance & security. In this step-by-step instructions, we'll show how we setup a NGINX server for a DIY / small business server administrator environment. Your comments and feedback is welcome.

Understanding NGINX Configuration File

NGINX configuration file looks like a JSON file containing blocks of data. Most of the configurations reside inside http { ... } block. When a site is created from the control panel, Virtualmin creates a server { ... } block inside http { ... } block.

Content inside http { ... } block (but outside the server { ... } block) are global settings, which is applied to all sites/servers. Settings inside server { ... } block only applies to that particular site

http { 

# All global settings

  server {
    server_name yourdomain.tld www.yourdomain.tld;
    # yourdomain related settings including rewrite rules
    location ~ \.php$ {
        # this "location" block holds PHP related settings for yourdomain
    # ... more settings

  server {
    server_name yourdomain2.tld www.yourdomain2.tld;
    # yourdomain2 related settings including rewrite rules


*** NOTE: NGINX server must be restarted for changes in NGINX configuration file to take effect ***
NGINX configurations shown here are examples only, use that is applicable for your project. You may need more fine tuning above and beyond these examples, therefore, this is only a start. Please feel free to send us your feedback and configuration ideas to make these examples better.

NGINX Configuration File Setup

Update NGINX Basic Configuration

To access your NGINX configuration file; go to Webmin Tab > Servers > Nginx Webserver > Edit Configuration Files.
a) Change the 2nd line worker_processes 1; to worker_processes auto;. This allows NGINX to automatically add worker processes based on CPU cores (1 worker per CPU core).
b) Logging: After testing, you may want to reduce logging, you can comment error_log & access_log lines to stop logging.
c) worker_connections 1024; can remain same.
Theoretically, 1 CPU core server with 1 NGINX worker, will be able to serve 1024 simultaneous connections. For production, we recommend 4 core server such as Compute Optimized - c4.xlarge.
d) Comment these items from http block as they are included in the example configuration below. NGINX will not restart if options are repeated.

    #sendfile       on;
    #tcp_nopush     on;
    #keepalive_timeout  65;
    #gzip  on;
Configuring NGINX with Include Files

If we start adding content to the original configuration file, quickly it'll become large and messy. It is best to add include files in strategic places inside the original configuration file and then begin customization via include files.

Insert a include file called ".GlobalHttp" inside http block. Inside yoursite's "server" block include a file named ".VhostConfig" (& ".VhostURLrewrite" if you have lots of rewrite rules), include another file named ".VhostPHPConfig" inside "location ~ \.php$" block.

Include files can be located anywhere convenient to manage. We find it easy to place these files inside website root to give the webmaster control over the server configurations. Of course global http include file must be managed by server administrator. For this example, we'll put all include files in the same location.

FTP to your website root & create a directory called ".nginx" and create files with the names as described above. You can name them anyway you want. This is only an example. We use filenames starting with "." as they can be secured with a rule to prevent public access. In some operating system these files can become hidden and needs to be made visible by setting to "show hidden files".

After uploading ".nginx" directory along with the include files, insert these include lines as shown below (without altering any existing lines). Replace "yourdomain" with your site directory name.

http { 

# All global settings
include /home/yourdomain/public_html/.nginx/.GlobalHttp;

  server {
    server_name yourdomain.tld www.yourdomain.tld;
    # yourdomain related settings
    include /home/yourdomain/public_html/.nginx/.VhostURLrewrite;
    include /home/yourdomain/public_html/.nginx/.VhostConfig;
    location ~ \.php$ {
        # this "location" block holds PHP related settings for yourdomain
        include /home/yourdomain/public_html/.nginx/.VhostPHPConfig;
    # ... more settings


.GlobalHttp Settings

This configuration example enables NGINX caching and rate limit settings. It is possible to exclude set of IP addresses used by developers and testers. These IP's, cache & rate limit zones are declared in the Global HTTP block to be utilized inside your server/domain blocks. So these declaration may not mean much until we use them inside server block.

# NGINX must be restarted after edit: sudo systemctl restart nginx	
# Or restart from control panel: Webmin Tab > Servers > Nginx Webserver > Click "Apply Nginx Configuration" button

# turn off nginx version number in error pages and Server header
server_tokens off;

# do not allow the browser to render the page inside an frame or iframe
# and avoid clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
add_header X-Frame-Options SAMEORIGIN;

# disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;

# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter if it was disabled by the user.
add_header X-XSS-Protection "1; mode=block";

# uncomment this to turn off access_log during production
#access_log off;

### Variable settings that can be used in .VhostConfig ###

# add your ips, "$my_ips" variable can be used in if statements inside ".VhostConfig".
geo $my_ips {
  default 0; 1; 1;

# Limit MAX connections per IP or per Server - second layer DDoS mitigation
limit_conn_zone $binary_remote_addr zone=per_ip:10m;
limit_conn_zone $server_name zone=per_server:10m;

# Rate limit request based on IP - DDoS mitigation

# zone: static: Limit 20 requests per second per IP (edit as required)
# this zone is used in ".VhostConfig" file 
limit_req_zone $binary_remote_addr zone=static:10m rate=20r/s;

# zone: php: Limit 30 PHP-FPM requests per minute per IP (edit as required)
# this zone is used in ".VhostPHPConfig" file
limit_req_zone $binary_remote_addr zone=php:10m rate=30r/m;

# Global FastCGI cache settings
fastcgi_cache_path /tmp/nginx/ levels=1:2 keys_zone=php_zone:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache php_zone;

# PHP-FPM cache time: increase cache time to decrease PHP-FPM load on very busy sites
# 3m = 3 minutes; to declare in seconds: 30s = 30 seconds
fastcgi_cache_valid any 3m;
fastcgi_cache_use_stale updating error timeout invalid_header http_500;

fastcgi_pass_header Set-Cookie;
fastcgi_pass_header Cookie;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

# Global Cache Settings 
proxy_cache_path /tmp/nginx/static/ levels=1:2 keys_zone=static_zone:10m inactive=60m;
proxy_cache_key "$host$request_uri";

proxy_cache static_zone;
proxy_connect_timeout 10s;
proxy_force_ranges on;
proxy_ignore_client_abort on;
proxy_intercept_errors on;

# serve cached file while updating
proxy_cache_use_stale updating error timeout invalid_header http_500;
proxy_cache_lock on;
proxy_cache_lock_age 10s;
proxy_cache_lock_timeout 5s;
proxy_cache_min_uses 1000;

# enable caching to limit requests during DDoS attacks
proxy_cache_valid 200 302  1h;
proxy_cache_valid 404      20m;
proxy_cache_valid any      25m;

# Buffers
client_body_buffer_size 128K;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
output_buffers 1 32k;
postpone_output 1460;

# Set max upload size
client_max_body_size 32m;

# Timeouts
client_body_timeout 8;
client_header_timeout 8;
keepalive_requests 1000; #default is 100
keepalive_timeout 10;
send_timeout 7;

sendfile    on;
tcp_nopush  on;
tcp_nodelay on;

# enable compression
gzip             on;
gzip_disable "MSIE [1-6].(?!.*SV1)";
gzip_vary on;
gzip_comp_level  5;
gzip_proxied any;
gzip_min_length  1000;
gzip_buffers 16 8k;
gzip_types text/plain text/html text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml image/svg+xml;

# Global SSL / Diffie-Hellman Params
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
resolver valid=300s;
resolver_timeout 5s;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security max-age=15768000;
ssl_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt;

# Setup Secure Access to your website via port 10001 
# We recommend blocking public access (from .VhostConfig) to backend applications like
# phpMyAdmin, RoundCube, WP... while you may access via port 10001

server {
	# replace yourdomain.tld with your domain
	server_name yourdomain.tld www.yourdomain.tld;

	listen 10001 default ssl http2;
	# disable cache
	set $no_cache 1;
	# replace yourdomain with appropriate directory name	
	ssl_certificate /home/yourdomain/ssl.cert;
	ssl_certificate_key /home/yourdomain/ssl.key;

	# these parameters are copied from the server block created by Virtualmin for your domain	
	root /home/yourdomain/public_html;
	index index.html index.htm index.php;
	fastcgi_param GATEWAY_INTERFACE CGI/1.1;
	fastcgi_param SERVER_SOFTWARE nginx;
	fastcgi_param QUERY_STRING $query_string;
	fastcgi_param REQUEST_METHOD $request_method;
	fastcgi_param CONTENT_TYPE $content_type;
	fastcgi_param CONTENT_LENGTH $content_length;
	# replace "yourdomain" with appropriate directory name
	fastcgi_param SCRIPT_FILENAME /home/yourdomain/public_html$fastcgi_script_name;
	fastcgi_param DOCUMENT_ROOT /home/yourdomain/public_html;
	fastcgi_param SCRIPT_NAME $fastcgi_script_name;
	fastcgi_param REQUEST_URI $request_uri;
	fastcgi_param DOCUMENT_URI $document_uri;
	fastcgi_param SERVER_PROTOCOL $server_protocol;
	fastcgi_param REMOTE_ADDR $remote_addr;
	fastcgi_param REMOTE_PORT $remote_port;
	fastcgi_param SERVER_ADDR $server_addr;
	fastcgi_param SERVER_PORT $server_port;
	fastcgi_param SERVER_NAME $server_name;
	fastcgi_param HTTPS $https;
    # for AMI w/Security
	# monit can be accessed via: yourdomain.tld:10001/monit/
	# default username: admin Password: monit
  	location /monit/ {
  		proxy_pass http://localhost:2812/;
  		proxy_set_header Host $host;
	location ~ \.php$ {
		try_files $uri =404;
		fastcgi_cache_bypass $no_cache;  # Don't pull from cache
		fastcgi_no_cache $no_cache; # Don't save to cache
		#replace "1234567890" with the socket number found inside your server block as created by Virtualmin for your domain
		fastcgi_pass unix:/var/php-nginx/1234567890.sock/socket;
	fastcgi_read_timeout 60;

.VhostURLrewrite Settings

# NGINX must be restarted after edit: sudo systemctl restart nginx 
# Or restart from control panel: Webmin Tab > Servers > Nginx Webserver > Click "Apply Nginx Configuration" button

### You can convert all .htaccess rewrite rules to nginx and add it to this file
### Online converter: 

# Rewrite Example 1

# rewrite all traffic to index.php except those defined directories & files

#location / {
#    if ( $uri !~ ^/(img|css|images|core|uploads|js|robots\.txt|favicon\.ico) ) {
#        rewrite ^ /index.php?q=$request_uri? last;
#    }

# Rewrite Example 2
# request to: yourdomain.tld/nice-path/some-request/ will be sent to: /some-script.php?b=some-request

#rewrite ^/nice-path/ /some-script.php?b=$request_uri?;

.VhostConfig Settings

# NGINX must be restarted after edit: sudo systemctl restart nginx 
# Or restart from control panel: Webmin Tab > Servers > Nginx Webserver > Click "Apply Nginx Configuration" button

### Site level settings based on rules defined in .GlobalHttp ###

# Limit MAX concurrent connections per IP - (D)DoS Deflate is configured to allow up to 15 connections
# change this according to your server needs
limit_conn per_ip 15;

# limit MAX concurrent connections per server (your site)
# uncomment & set limit to protect your server from crashing due to load 
#limit_conn per_server 1000; 

# static content access: limit to 20 requests per second per ip; 
# burst=N nodelay; will immediately allow additional N requests above average limit.  
# burst=N; will queue N requests above average limit
# uncomment to limit 20 static file requests per IP per second.
#limit_req zone=static burst=10;

# disable cache for developer IP's 
set $no_cache '';

if ($my_ips) { 
    # disable cache
	set $no_cache 1;

# Block access to private dir / apps. You can access them via port 10001 as defined in .GlobalHttp file
location ~* (roundcube|squirrelmail|phpmyadmin|webdav|smtp|http\:|soap|w00tw00t) {
       	return 444;

# Set static file cache expiry & custom error handling
location ~* .(jpg|jpeg|png|gif|ico|css|js|svg)$ {
	# uncomment to handle errors with custom php file (like error.php) instead of regular 404 page 
	#try_files $uri $uri/ /error.php?r=$uri;
	expires 365d;

# allow Lets Encrypt authentication directory
location ~ /\.well-known {
	allow all;

# Protect all files/directories starting with "." and known private files
location ~ /(\.|readme\.html|readme\.txt|license\.txt|license\.commercial\.txt|schema\.txt|password\.txt|passwords\.txt) { 
        deny all;
# Protect ~ files
location ~ ~$ { 
        access_log off; 
        log_not_found off; 
        deny all; 
# Protect .git files
location ~ /\.git { 
        access_log off; 
        log_not_found off; 
        deny all; 
# Protect Perl/CGI/etc files
location ~* \.(pl|cgi|py|sh|lua)\$ {
        return 444;

# Protect other sensitive files
location ~* \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|\.php_{
        return 444;
# Block execution of PHP files in uploads folders
location ~* /(?:uploads|files)/.*\.php$ {
        deny all;

.VhostPHPConfig Settings

# NGINX must be restarted after edit: sudo systemctl restart nginx 
# Or restart from control panel: Webmin Tab > Servers > Nginx Webserver > Click "Apply Nginx Configuration" button

# limit PHP-FPM requests to 30 requests per minute per IP (as defined in .GlobalHttp)
# this should be sufficient considering 3 minute cache for php script output
limit_req zone=php burst=3 nodelay;

# Defines regular expression that captures a value for the $fastcgi_path_info variable
fastcgi_split_path_info ^(.+?\.php)(/.*)$;

# Don't pull from cache for $my_ips
fastcgi_cache_bypass $no_cache;

# Don't save to cache for $my_ips
fastcgi_no_cache $no_cache; 

Setup External MySQL Server with Amazon RDS (optional)

Why Amazon RDS?

Generally remote & dedicated database servers are employed to handle large workloads as the database doesn't have to share resources with the main web server.

However, we recommend Amazon RDS along with Elastic File System (EFS) to create a web server which is resilient to failures and future server upgrades. Since all of the site data reside remotely in Amazon RDS and EFS, switching to a new server is as easy as launching a new instance with your pre-configured AMI, once it is up and running, just switch the static IP from the old instance to the new. The site will switch over to the new instance without any downtime or the end users knowing it. Autoscaling also becomes easy as there is no need for versioning, any new instances that join the group will have the current data as the data is always served from one common / remote location. Additionally Amazon handles RDS backups and upgrades for you. If things should go wrong, you can get back up without much down time.

Which type of RDS is right for me?

For small businesses or sites that are currently using MySQL or MariaDB, we recommend using MySQL with Amazon RDS. Even though, there is a slight performance gain with MariaDB, MySQL has the advantage of being able to directly convert into Amazon's Aurora DB. If your site should grow in size and traffic, MySQL will keep the upgrade choice open without adding complexity to the transition. Amazon Aurora is a good choice for both performance and cost. Aurora RDS instances are built with Multi-AZ technology (by default), if you compare server cost for a similar spec (with Multi-AZ) instance, it will generally cost almost twice with other RDS types. With Aurora, there is a direct savings in instance cost, while achieving enterprise class performance and features.

If this sounds worthwhile, follow this tutorial to setup your external RDS.

Setting up Amazon RDS

From your AWS console, goto Services & click on RDS, for here launch a new instance. For Dev/Testing select the free tier MySQL option, click next to select instance type and fill out RDS name (identifier), username and password. Click next to "Configure Advanced Settings"

Configure Advanced Settings

Set "Publicly Accessible" to "No". If this is set to "Yes", you will then have to open incoming traffic IP by IP for the instances to communicate with RDS. If you select "No" and make this a Private RDS, then you can open incoming traffic by entering your instances "Security Group ID" to allow incoming traffic from all your instances that uses this security group. This helps when you setup autoscaling, as your instances IP's will change all the time.

Select a VPC Security Group(s) or create a new one, in the next step, you can edit this group to allow traffic from all your instances. Enter database name and launch the instance.

Now go to EC2 dashboard, from the left menu click on "Security Groups" under "Network & Security". If you have opted to create a new security group while launching RDS instance, you'll see it with group name "rds-launch-wizard". Select the RDS security group, click action button and click on "Edit inbound rules". In the window, select MYSQL/Aurora for type. Protocol is TCP. Port range is 3306. Source is custom, in the input field, start typing "sg" and it'll populate available security groups from which, select the security group used by your web server instances. This allows inbound traffic from all instance that uses the selected security group.

Connecting to your Amazon RDS

To make your PHP applications like WordPress to use Amazon RDS, replace "localhost" to the "Writer Endpoint" which looks like this url: To obtain this url, go to RDS console, select your instance, click on "Instance Action" and then click on "See Details" to obtain "Writer Endpoint" url.

Update phpMyAdmin Config File

SSH into your instance and follow these commands.

$ sudo su
$ cd /home/yourdomain/public_html/phpmyadmin

# backup config file before editing
$ cp

$ nano

Editing phpMyAdmin Config File:

# scroll to the bottom of config file and add these lines (replace RDS endpoint with yours)

// Amazon RDS Integration
$cfg['Servers'][$i]['host']          = '';
$cfg['Servers'][$i]['port']          = '3306';
$cfg['Servers'][$i]['socket']        = '';
$cfg['Servers'][$i]['connect_type']  = 'tcp';
$cfg['Servers'][$i]['extension']     = 'mysql';
$cfg['Servers'][$i]['compress']      = FALSE;
$cfg['Servers'][$i]['auth_type']     = 'cookie';

Save and exit. Now when you log into phpMyAdmin, you'll be able to log into your localhost or to Amazon RDS.

Add your RDS IP to APF & DDOS (for AMI's w/Security)

When your website begins using Amazon RDS, the server will make lots of connections to these remote resources. DDoS may ban RDS IP, if the number of connections exceed max allowed as configured in your DDoS settings. Therefore it is important to whitelist your RDS IP. To obtain your RDS IP, login to your instance via SSH terminal and run this command:
ping (replace with your RDS host address). This command will output RDS IP in this format:
PING ( 56(84) bytes of data.
Press "CTRL + C" to exit ping.

Copy the resulting IP and add it to DDOS ignore list and APF allow rules.

# from terminal, edit DDOS file with this command & add IP in a new line
$ sudo nano /usr/local/ddos/ignore.ip.list

# use this command to add to APF allow rules (replace with your RDS IP)
$ sudo /usr/local/sbin/apf -a

This completes Amazon RDS setup tutorial. Contact us if you should have any questions (include your Amazon customer ID)

Setting up Elastic File System (EFS) with auto-mounting (optional)

NOTE: This is experimental. We have received reports related to reliability issues. Do not use this instruction in production servers. This information is for educational purposes only.

Why Amazon EFS?

Amazon Elastic File System (EFS) along with Amazon (RDS) makes your "site data" independent of the instance or web server storage making your data resilient to server failures or server migration. Basically you can add or change your web server like changing socks! This is true cloud computing!

Setting up EFS

Go to EFS control panel from AWS > Services > Elastic File System. Click on Create file system to get started. In the next screen you'll see default security group selected. You can create a new security from EC2 dashboard or you can edit the default security group to add inbound access from your instances. Click next to complete EFS creation.

Once EFS is created, go to EC2 dashboard and click on Security Group and select the security group which is used for the EFS. Then click "Action" button to Edit inbound rules. Then add the following rule: Type: NFS | Protocol: TCP | Port: 2049 | Source: Custom | in the text field begin typing "sg" and you'll see all the security groups, select the security group(s) used for your instances and save it.

Mounting EFS to your Instance

From EFS dashboard, select the file system and you'll see "EC2 mount instructions" under "File system access". When you click on this option you'll see mount command with EFS DNS name. However, we have encountered "Failed to resolve server" error messages while trying to connect using DNS names. Therefore, this tutorial will cover mounting instructions using EFS IP address instead of DNS names.

EFS IP's and Auto Mounting

Amazon EFS is replicated across all available zones. From the EFS dashboard select your EFS and you'll see all the zones along with their IP addresses. You can connect to your EFS using any one of the IP address, however to achieve best latency, it is important to connect to same zone as your instance. Additionally, to use EFS to house your site data, EFS must be mounted during boot time to be recognized by the web server & control panel (NGINX/Apache/Webmin/Virtualmin). The best way to achieve this is to mount it with a init.d script. Note: Auto mount cannot be done via /etc/fstab, since fstab is unable to run script's or commands that is required to mount EFS upon the instance's network connection is established.

Commands & Script to Mount EFS

SSH into your instance and run these commands

$ sudo mkdir /efs

# test mount EFS to your instance (replace IP with your EFS IP)
$ sudo mount -t nfs4 -o nfsvers=4.1 /efs

# create a file in your EFS
$ sudo nano /efs/mount.status

# add this content to this file, save and exit to create file mount.status
!!! Do Not Delete !!!
!!! Monit probes this file to check mount status !!!

# test if file exists
$ sudo ls -asl /efs

# if file exists, now un-mount EFS
$ sudo umount /efs 

# ls again and now you should not see the file mount.status
$ sudo ls -asl /efs

If you did not encounter errors during test mounting your EFS, rest of the steps should go without issues. If you should have errors while test mounting, then the problem could be with your security group. Read the instructions above and make sure to configure the security group and grant access for your instance to connect to your EFS.

Automate with INIT.D Script

# create INIT.D script file and set permissions #
$ sudo touch /etc/init.d/efsmount
$ sudo chmod 755 /etc/init.d/efsmount
$ sudo chown root:root /etc/init.d/efsmount

# Edit init.d Script
sudo nano /etc/init.d/efsmount

INIT.D Script Instructions

In the init.d script below, replace zones (us-east-1a) and IP's ( with your EFS zones and IP's. The script below has 4 zones. If you have fewer than 4 zones, delete the if ... fi code as shown below. If you have more availability zones, just add more if ... fi code blocks (as shown below) right after the first four zones.

if [ "$CURR_ZONE" == "us-east-1a" ];then
		/usr/bin/mount -t nfs4 -o nfsvers=4.1 /efs

THE INIT.D File Contents


# chkconfig: 235 01 01
# description: efsmount

### Usage ###
# /etc/init.d/efsmount start
# /etc/init.d/efsmount stop
# /etc/init.d/efsmount restart
# /etc/init.d/efsmount status

### Log Script Output - Uncomment for Debugging ###
#exec 3>&1 4>&2
#trap 'exec 2>&4 1>&3' 0 1 2 3
#exec 1>>/home/centos/efsmount.log 2>&1

# functions library
. /etc/init.d/functions

function mountefs {

CURR_ZONE=$(/usr/bin/curl -s


if [ "$CURR_ZONE" == "us-east-1a" ];then
		/usr/bin/mount -t nfs4 -o nfsvers=4.1 /efs

if [ "$CURR_ZONE" == "us-east-1b" ];then
		/usr/bin/mount -t nfs4 -o nfsvers=4.1 /efs

if [ "$CURR_ZONE" == "us-east-1c" ];then
		/usr/bin/mount -t nfs4 -o nfsvers=4.1 /efs

if [ "$CURR_ZONE" == "us-east-1d" ];then
		/usr/bin/mount -t nfs4 -o nfsvers=4.1 /efs


function loopmnt {
while :;
if grep -qs "/efs" /proc/mounts; then
	echo "It's mounted."
	echo "It's not mounted."
sleep 1

start() {

stop() {
	 /usr/bin/umount /efs

restart() {
	sleep 1

status() {
	echo $(/usr/bin/grep  "/efs" /proc/mounts)

case "$1" in
	start &
	echo $"Usage: $0 {start|stop|status|restart}"
	exit 1

After saving the above init.d script, run the following commands

#  reload
$ sudo systemctl daemon-reload

# start service on boot
$ sudo chkconfig --add efsmount

# set chkconfig level
$ sudo chkconfig --level 2345 efsmount on

# start script and test if it works
# if you see [FAILED] error, most likely it will get resolved after reboot
$ sudo /etc/init.d/efsmount start

# now /efs must be mounted, you should be able to see mount.status
$ ls -asl /efs

If you see the file mount.status after running the "efsmount start" command, then you have configured the auto mount script correctly. Now reboot the instance: from SSH terminal run: sudo reboot Upon reboot, run this command ls -asl /efs and if you see "mount.status" file, then the auto mount init.d script is correctly mounting your EFS during boot.

Whitelist IP in APF & DDoS (for AMI's w/Security)

Depending on the site traffic, your server can make 100's of simultaneous connections to EFS. This may trigger DDoS to block EFS IP via APF. To prevent this, whitelist your EFS IP's. Also whitelist Amazon Meta Data IP address.

Login via SSH Terminal and perform the following commands

APF Whitelist

Using the format below, add each of your EFS IP's to APF allow list at /etc/apf/allow_hosts.rules

# replace with your EFS IP 
$ sudo /usr/local/sbin/apf -a

# Amazon Meta Data IP
$ sudo /usr/local/sbin/apf -a
DDoS Edit ignore.ip.list

$ sudo nano /usr/local/ddos/ignore.ip.list

# add lines as follows for all EFS IP's (replace with your EFS IP)

# Amazon Meta Data IP

CTRL + x to exit nano editor

Enable Monit Monitoring (for AMI's w/Security)

Edit Monit config file and uncomment EFS monitoring codes to enable Monit to keep an eye on the EFS mount status.

$ sudo nano /etc/monit.d/monitrc

# uncomment these lines
 check file efsmount with path /efs/mount.status
  group system
   start program = "/etc/init.d/efsmount start"
   stop  program = "/etc/init.d/efsmount stop"
   if 5 restarts within 5 cycles then timeout
# save & exit nano; then restart monit   
$ sudo systemctl restart monit

Move Sites to EFS

Move /home/yourdomain to /efs/www/yourdomain; Also backup before moving.

$ sudo su
$ cd /

$ cp -rp /home/yourdomain/. /home/yourdomain-backup

# check if hidden files are included
$ ls -asl /home/yourdomain-backup
$ ls -asl /home/yourdomain

Copy Site Directory to EFS (takes a long time)

Depending on your site directory size and the allocated network speed, allow ample time for the copy command to complete.

$ mkdir /efs/www
$ cp -rp /home/yourdomain/. /efs/www/yourdomain

# see if all files are copied correctly
$ ls -asl /efs/www/yourdomain

# see size of EFS dir (will be slightly larger than original directory)
$ du -sh /efs/www/*

# see original dir size
$ du -sh /home/*

# if all is well, delete original directory and symlink EFS in its place
$ rm -rf /home/yourdomain
$ ln -s /efs/www/yourdomain /home/yourdomain

If you encounter errors while deleting site directory... such as "rm: cannot remove ‘/home/yoursite/cgi-bin/some-file.cgi’: Operation not permitted"; Use chattr for those directories and then try to delete them.

$ chattr -i -a /home/yoursite/cgi-bin/*
$ rm -rf /home/yoursite/cgi-bin

$ rm -rf /home/yoursite
$ ln -s /efs/www/yourdomain /home/yourdomain

Now your site is driven from EFS. Reboot your instance and see if the site will work as usual from EFS. If all is well, delete the backup directory that you created earlier. sudo rm -rf /home/yourdomain-backup

WordPress Installation & Nginx Configuration (optional)

WordPress is the most used CMS platform today, yet there are security vulnerabilities and are a target for hackers and DDOS attackers. Archisoft takes precautionary methods to offset some of these threats by blocking direct access to vulnerable files and by implementing cache and limiting PHP requests/minute as described in the advanced NGINX configuration.

Multiple Copies of WordPress

It is recommended to install WordPress in its own directory like: public_html/wp1/ instead of the root directory. Another copy of WordPress (for testing) can be installed in public_html/wp2/. Going forward with this tutorial, we'll assume that you are installing the main copy of your WordPress in "wp1" directory.

Create a new MySQL User

Create a new MySQL User with limited privileges. Assuming phpMyAdmin has been installed, log into it with Username: root and password that was created during Webmin/Virtualmin Install Wizard (if you are using Amazon RDS use appropriate username and password).

Click on phpMyAdmin logo > Click "User accounts" tab > Add user account. Select a new user name like wp1 & password.

Check: Create database with same name and grant all privileges.
Check: Grant all privileges on wildcard name (username\_%).

Select these Privileges (or as required):

Click GO to create the new user with limited privileges.

NGINX Configuration for WordPress

Change website root directory to wp1

From your control panel Click on Webmin Tab > Servers > Nginx Webserver > click on "Edit Configuration Files" and edit your website path in three places.

# assuming your WordPress is installed in directory wp1, edit these 3 lines

server {
	server_name yourdomain.tld www.yourdomain.tld;
	# find and edit these 3 lines
	root /home/yourdomain/public_html/wp1;

	fastcgi_param SCRIPT_FILENAME /home/yourdomain/public_html/wp1$fastcgi_script_name;

	fastcgi_param DOCUMENT_ROOT /home/yourdomain/public_html/wp1;


WordPress URL rewrite

Open your .VhostURLrewrite or .VhostConfig include file and add this code for WordPress to work

# WP URL rewrite
location / {
        try_files $uri $uri/ /index.php?$args ;

Securing WordPress by Denying Access

The following examples will help you configure NGINX server to deny direct URL access to all WP admin scripts and most other WP php files while allowing full access to your IP's listed in .GlobalHttp file under "geo $my_ips".

Open your .VhostConfig include file and add/modify these codes to secure WordPress.

set $no_cache '';
set $block '';

if ($my_ips) {
   # disable cache
   set $no_cache 1;
   # allow access to blocked locations
   set $block "NO";

# add this code to your file to:
# deny direct url access to /wp-admin/, files starting in /wp-whatever.php and 
# deny access to PHP files inside wp-includes & wp-content directories
# Edit this line to allow or deny files based on your needs. This is just an example of what can be done.
if ( $request_uri ~* (wp-admin|wp-.*\.php|xmlrpc\.php|wp-includes\/.*\.php|wp-content\/.*\.php)) {
   set $block "${block}YES";

# this blocks traffic to all except to those IP's listed in .GlobalHttp - "geo $my_ips"
if ( $block = "YES" ) {
   return 444;
Continued access to directories

Note: Since the root directory has been moved, ".well-known" directory in the old root will no longer be accessible, this will break Lets Encrypt's automatic SSL certificate renewal. To make this work again, login to SSH terminal and run the following command to create a symlink from old location to the new root directory (update yourdomain and wp1 to appropriate directory names in the below command).

$ sudo ln -s /home/yourdomain/public_html/.well-known /home/yourdomain/public_html/wp1/.well-known

Similarly, you can create more symlinks to other directory that needs access, such as, if you are using global rules for APF and have created a directory at public_html/apf, now this will no longer be accessible since the root has been moved. So create another symlink for this.

$ sudo ln -s /home/yourdomain/public_html/apf /home/yourdomain/public_html/wp1/apf

Restart NGINX Server

For NGINX configuration to take effect, NGINX server must be restarted. This can be done from Webmin Tab > Servers > Nginx Webserver > Click on "Apply Nginx Configuration" button (bottom left). Or run this command from the terminal systemctl restart nginx

2 Ways to Install WordPress

Installing WordPress via SSH Terminal

It is much faster to install WordPress via Terminal, as it eliminates the need to upload thousands of files to the server from your computer via FTP. Follow the steps below, change "yourdomain" to appropriate directory name where your site resides, as well as change "wp1" to the appropriate directory name where you want to install WordPress

$ sudo su
$ cd /home/yourdomain/public_html
$ mkdir wp1
$ cd wp1
$ wget
$ tar xfz latest.tar.gz
$ mv wordpress/* ./
$ rmdir ./wordpress/
$ rm -f latest.tar.gz
$ cd ../
$ chown -R yourdomain:yourdomain wp1/

Installing WordPress via FTP

Download the latest copy of WordPress form Unzip it and upload its content to your server's WordPress directory using FTP.

After downloading/uploading of WP files is completed, access your website from the browser to complete WordPress installation. Use the newly created MySQL user name and password credentials for this WordPress installation.

To install large WordPress themes and plugins, increase PHP upload limits by creating a file with name ".user.ini" at wp1/wp-admin/.user.ini with the following configurations.

max_file_uploads = 32M
upload_max_filesize = 32M

Setting up your Pre-Configured AMI

If you manage a conventional dedicated server to operate your sites, things can get complicated if something should go wrong with the server. Downtime is unavoidable if redundancy servers are not in place (expensive options).

When Amazon instance is configured with EFS & RDS, your sites are not dependant on the server state. If the server should get corrupted for some reason, you can just launch a new instance using your pre-configured AMI and switch the IP address to the new instance and the site should resume normal operation. If you use autoscaling this will be done automatically the moment an instance should go unresponsive.

Pre-Configured AMI

You started with the Prefect7 AMI and then created your site and configured it to your preferences. Upon completion of all your server configurations, when you think the server is ready for production, take a snapshot of the instance, which becomes your "pre-configured AMI". When you launch new instances with this AMI, all your configuration will be intact. Every time you make changes to the server configuration take another snapshot and date it or give it a version number.

Creating Your Pre-Configured AMI

From SSH login to your instance, shut down the instance with the following command: sudo shutdown now. From your Amazon EC2 dashboard, you'll see that the instance state will become "stopped". Now select the instance and click on Action button, under Image click on "Create Image". Provide appropriate name for your image and create it. Once the creation is completed, you'll see the image from the left menu under IMAGES > AMI and under ELASTIC BLOCK STORE > Snapshots.

Now you can launch a new AMI anytime using this image. While launching new instances, select "My AMI" tab to see all your saved AMI's. When new instances are launched, and if you want to move your sites static IP address from one instance to another, from left menu click on Elastic IPs under NETWORK & SECURITY. Select the IP that needs switching. Then click on Action and then click on "Associate Address". Click on the instance field and you'll see all your instances populated, select the instance and check on "Reassociation" check box and click on "Associate" button.

Congratulations on your new web server on Amazon cloud!

Fine Print


 Disclaimer of Warranty. This AMI is provided on an "AS IS" BASIS,


      implied, including, without limitation, any warranties or conditions


      PARTICULAR PURPOSE. You are solely responsible for determining the

      appropriateness of using the AMI and assume any risks.
      Users are encouraged to do their due diligence to test all aspects 
      of this AMI for stability, performance and security. Every configuration 
      & server services might require further customization, tweaking and or 
      patching for production.

Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.

Bring business to life with modern web design solutions. Building custom web applications that fits like a glove, at an affordable price! Archisoft stands as an outsourced IT department enabling small & medium class businesses.

All logos are trademarks of their respective companies.
© 2024 BeccaTrade Inc., All Rights Reserved
Privacy Policy