High Performance WordPress and Server – Part I – Server Setup

High Performance Wordpress and Server - Part I - Server Setup

Posted:  June 10, 2016

High Performance Wordpress and Server - Part I - Server Setup

I have successfully managed to get around a 1 second load time on my WordPress site, While getting 250 concurrent users over a 1 minute test period.  (Source: https://webpagetest.org/result/210128_Di26_15d11aa18013d1df9ebc867e1aa9c2f3/)

This was done with a combination of items, stemming from the server install up to WordPress theme development.  Here is how I did it, so maybe you can too.

Server Setup

Here we will start from the ground up.  Items you will need: VirtualBox, Ubuntu 16.04 64b Server ISO, Time

My virtual machine is setup with 4G of RAM, using 2 CPU’s, with 80G SSD, and a Bridged Networking adapter

Boot to the ISO, and start the installation process.  Everything can be setup how you wish, however, I custom partitioned, as well as, only installed the “standard system utilities”, and OpenSSH during the install process.

During paritioning (with the size above), make sure to select Manual, and setup the 4 Partitions I layout below

Since we are creating a tmp, cache, and swap partition make sure to reserve at least the same amount as you have in RAM, so with my 4G of RAM, I need to reserve at least 12G of disk, however, I am going to reserve 16G because I want my swap partition twice the amount of RAM

  • 1st Partition:
    • Mount Point: /
    • 69.9G (officially my drive was 85.9G)
    • Primary – Beginning
    • Mount Options: discard
    • Reserved blocks: 1%
    • Typical Usage: news
    • Bootable: on
  • 2nd Partition:
    • Mount Point: /tmp
    • 4G
    • Primary – Beginning
    • Mount Options: discard, noatime, nodiratime
    • Reserved blocks: 1%
    • Typical Usage: news
    • Bootable: on
  • 3rd Partition:
    • Mount Point: /cache (probably will have to create this manually)
    • 4G
    • Primary – Beginning
    • Mount Options: discard, noatime, nodiratime
    • Reserved blocks: 1%
    • Typical Usage: news
    • Bootable: on
  • 4th Partition:
    • Use as: swap
    • 8G

Now finish up your install process, and let the machine reboot.  Once it boots, login to the machine and drop into a sudo session using sudo -s and let the “fun” begin 🙂

We are going to configure our server to use bash only, setup the default system control, install our software, and configure it… so be prepared to have your time sucked up 😉

Use bash only: dpkg-reconfigure dash

Now, we’ll remove apparmor since we’ll be using ufw as our firewall

service apparmor stop
update-rc.d -f apparmor remove
apt-get remove apparmor apparmor-utils

Speaking of firewall, we can set that up now too

ufw allow http
ufw allow https
ufw allow ssh
ufw enable

This will allow only web and ssh connections to the server.   Feel free to allow anything else you deem necessary

Now we’ll modify our system controller to allow a ton of connections, allow a ton of files to be open, and mod our networking and swap configuration

First run rm -f /etc/sysctl.conf  then nano /etc/sysctl.conf  and paste in the following:

# for /etc/sysctl.conf
# Protection from SYN flood attack.
net.ipv4.tcp_syncookies = 1
# See evil packets in your logs.
net.ipv4.conf.all.log_martians = 1
# Discourage Linux from swapping idle server processes to disk (default = 60)
vm.swappiness = 45
# Increase number of incoming connections that can queue up before dropping
net.core.somaxconn = 50000
# Handle SYN floods and large numbers of valid HTTPS connections
net.ipv4.tcp_max_syn_backlog = 30000
# Increase the length of the network device input queue
net.core.netdev_max_backlog = 5000
# Increase system file descriptor limit so we will (probably) never run out under lots of concurrent requests. (Per-process limit is set in /etc/security/limits.conf)
fs.file-max = 100000
# Widen the port range used for outgoing connections
net.ipv4.ip_local_port_range = 10000 65000
# If your servers talk UDP, also up these limits
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192
# Disable source routing and redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
# Disable packet forwarding.
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# Disable TCP slow start on idle connections
net.ipv4.tcp_slow_start_after_idle = 0
# Increase Linux autotuning TCP buffer limits Set max to 16MB for 1GE and 32M (33554432) or 54M (56623104) for 10GE Don't set tcp_mem itself! Let the kernel scale it based on RAM.
net.core.rmem_max = 56623104
net.core.wmem_max = 56623104
net.core.rmem_default = 16777216
net.core.wmem_default = 16777216
net.core.optmem_max = 40960
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# Disconnect dead TCP connections after 1 minute
net.ipv4.tcp_keepalive_time = 60
# Wait a maximum of 5 * 2 = 10 seconds in the TIME_WAIT state after a FIN, to handle any remaining packets in the network. net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait = 5 Allow a high number of timewait sockets
net.ipv4.tcp_max_tw_buckets = 2000000
# Timeout broken connections faster (amount of time to wait for FIN)
net.ipv4.tcp_fin_timeout = 10
# Let the networking stack reuse TIME_WAIT connections when it thinks it's safe to do so
net.ipv4.tcp_tw_reuse = 1
# Determines the wait time between isAlive interval probes (reduce from 75 sec to 15)
net.ipv4.tcp_keepalive_intvl = 15
# Determines the number of probes before timing out (reduce from 9 sec to 5 sec)
net.ipv4.tcp_keepalive_probes = 5

After you save the file, run the following to load in the configuration: sysctl -p

Now add in the following line to your limits by: nano /etc/security/limits.conf

# add to bottom
* - nofile 16384
* hard nofile 500000
* soft nofile 500000
root hard nofile 500000
root soft nofile 500000

Now we’ll update the server: apt-get update && apt-get -y upgrade && apt-get -y dist-upgrade && apt-get autoclean && apt-get -y autoremove

Once again, you will need to reboot.

Once you have rebooted, lets forge ahead and install our web/database software:

nGinx & memcached (and a couple extra helpers)

apt-get -y install nginx-full memcached zip lzop


apt-get -y install php7.0-fpm php7.0-curl php7.0-gd php7.0-intl php7.0-mysql php7.0-json php7.0-sqlite3 php7.0-opcache php-memcached php-pear php7.0-mbstring php7.0-cli


apt-get -y install mysql-server

We are done installing! :D   Now the real fun begins… configuration.  Since I host a multitude of sites on my server, I setup a directory structure like the following for both site configurations and files.  But, you can do what you want…  just remember to change your paths in the config files I post here, otherwise it will not work for you.

  • /hosting
    • /hosting/DOMAINS
      • /hosting/DOMAINS/the.domain.com
        • /hosting/DOMAINS/the.domain.com/www
        • /hosting/DOMAINS/the.domain.com/fpm-pools
    • /hosting/nginx-config
    • /hosting/site-config

I remove the default nginx config otping for my own, so do a: rm -f /etc/nginx/nginx.conf && echo “include /hosting/nginx-config/nginx.conf;” > /etc/nginx/nginx.conf

Now download the following and unzip it to your /hosting/nginx-config/ directory: DOWNLOAD HERE

You are now ready to setup your first site.  Run this and change the domain to the domain of your need: mkdir -p /hosting/DOMAINS/example.com/www && echo “<h1>Hello World</h1>” > /hosting/DOMAINS/example.com/www/index.php

We also need to configure the running of your site through fpm and nginx, copy and paste the following to /hosting/site-config/example.com make sure to change the paths to fit your needs, as well as the domain.

upstream your-fpm-lb {
	# PHP-FPM - make sure the ports you decide to use are open
        # You should at least have 2 pools available to you.  But really no more than 4 is necessary

# Redirects
server {
	# what we want to redirect
	server_name www.example.com;
	# where we want to redirect to
	return 301 http://example.com$request_uri; 

server {
	# the document root of your site
	root /hosting/DOMAINS/example.com/www;
	# the default page for your site
	index index.php;
	# the main fqdn of the site
	server_name example.com;
	# your access log location.  I leave this commented for performance
	#access_log /logs/example.com-access.log;
	# your error log location, i only enable critical errors to log - performance
	error_log /logs/kevinpirnie.com-error.log crit;
	# let’s setup the php fpm processor
	location ~ [^/]\.php(/|$) {
		# let’s turn on the keep alive
		fastcgi_keep_conn on;
		# include our default fastcgi configuration: see file for details
		include /hosting/nginx-config/site-fastcgi-common.conf;
		# set to your fpm upstream above
		fastcgi_pass your-fpm-lb;
	# Configure memcached to be usable. See file for details 
	include /hosting/nginx-config/memcache-enabled.conf;
	# Configure caching. See file for details
	include /hosting/nginx-config/yes-cache.conf;
	# Configure no caching. See file for details
	#include /hosting/nginx-config/no-cache.conf;
	# Configure default site settings, Required.  See file for details
	include /hosting/nginx-config/all-sites.conf;
	# Configure gzipping of static resources.  See file for details
	include /hosting/nginx-config/gzip.conf;
	# Configure some extra security for WordPress sites.  See file for details.
	include /hosting/nginx-config/wp-security.conf;

One last bit of configuration.  We need to setup our fpm pools.  Since we configured 4 upstream connections in our site config, we need to configure 4 pools

Create a new user for these pools to run under

adduser -y example-user

Copy and paste the following into 4 files located in /hosting/DOMAINS/example.com/fpm-pools

; Start a new pool
; make sure to update the number
; what user should this pool run as
user = example-user
; keep this www-data so nginx can serve the site
group = www-data
; change this to reflect one of the ports in the upstream block of your site config
listen =
; We don’t need to have too high of a task priority
process.priority = 0

; fpm process management
pm = dynamic
pm.max_children = 200
pm.start_servers = 20
pm.min_spare_servers = 20
pm.max_spare_servers = 60
pm.max_requests = 500

We can now start our engines 🙂   Run this command to make sure you haven’t messed up anything 😉 nginx -t  then run the following to restart all of your services to start hosting your site.

/etc/init.d/memcached restart
/etc/init.d/php7.0-fpm restart
/etc/init.d/nginx restart

Now that we have the “basics” out of the way, let’s head into configuring MySQL to handle the loads we are going to place on it.    Copy/Paste the following into your mysqld.conf (usually located at /etc/mysql/mysql.conf.d)

[showhide type=”mysql” more_text=”Show mysqld.conf” less_text=”Hide mysqld.conf”]

# The MySQL database server configuration file.
# You can copy this to one of:
# - "/etc/mysql/my.cnf" to set global options,
# - "~/.my.cnf" to set user-specific options.
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

# This will be passed to all mysql clients
# It has been reported that passwords should be enclosed with ticks/quotes
# escpecially if they contain "#" chars...
# Remember to edit /etc/mysql/debian.cnf when changing the socket location.

# Here is entries for some specific programs
# The following values assume you have at least 32M ram

socket		= /var/run/mysqld/mysqld.sock
nice		= 0

# * Basic Settings
user		= mysql
pid-file	= /var/run/mysqld/mysqld.pid
socket		= /var/run/mysqld/mysqld.sock
port		= 3306
basedir		= /usr
datadir		= /var/lib/mysql
tmpdir		= /tmp
lc-messages-dir	= /usr/share/mysql
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address		=
# * Fine Tuning
key_buffer_size = 128M 
max_allowed_packet = 16M 
thread_stack = 128K 
thread_cache_size = 8 
table_open_cache = 8192 
max_heap_table_size = 256M 
innodb_buffer_pool_size = 4G
myisam-recover-options = BACKUP 
innodb_log_file_size = 512M

#table_cache            = 64
#thread_concurrency     = 10
# * Query Cache Configuration
query_cache_limit	= 4M
query_cache_size        = 1024M
# * Logging and Replication
# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# As of 5.1 you can enable the log at runtime!
#general_log_file        = /var/log/mysql/mysql.log
#general_log             = 1
# Error log - should be very few entries.
log_error = /var/log/mysql/error.log
# Here you can see queries with especially long duration
#log_slow_queries	= /var/log/mysql/mysql-slow.log
#long_query_time = 2
# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
#       other settings you may need to change.
#server-id		= 1
#log_bin			= /var/log/mysql/mysql-bin.log
expire_logs_days	= 10
max_binlog_size   = 100M
#binlog_do_db		= include_database_name
#binlog_ignore_db	= include_database_name
# * InnoDB
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
# Read the manual for more InnoDB related options. There are many!
# * Security Features
# Read the manual, too, if you want chroot!
# chroot = /var/lib/mysql/
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem


For the most part we are now done and ready to start serving up scalable, efficient, and fast loading WordPress websites.  Just the config above alone is not enough to ensure high availability though.  There is alot more work to be done, mostly with theme development.

Just like any other web application, proper development goes a long way.  Design as well, if your app’s design isn’t optimized, you will still create a heavy load on the server, that is un-necessary.  Always optimize your images, always set the dimensions when you call them as well.  Make sure you concatenate and minify your css and javascripts where you can, and load them appropriately in your html code.  (css in the head, keep as much javascript at the bottom of your documents as well).

That’s it for now, stay tuned for the rest in this series.  And as always…   Happy Coding 🙂

Kevin Pirnie

Kevin Pirnie

20+ Years of PC and server maintenance & over 15+ years of web development/design experience; you can rest assured that I take every measure possible to ensure your computers are running to their peak potentials. I treat them as if they were mine, and I am quite a stickler about keeping my machines up to date and optimized to run as well as they can.

Our Privacy Policy

Revised: June 8, 2021

Thank you for choosing to be part of my website at https://kevinpirnie.com (“Company”, “I”, “me”, “mine”). I am committed to protecting your personal information and your right to privacy. If you have any questions or concerns about this privacy notice, or my practices with regards to your personal information, please contact me at .

When you visit my website https://kevinpirnie.com (the “Website”), and more generally, use any of my services (the “Services”, which include the Website), I appreciate that you are trusting me with your personal information. I take your privacy very seriously. In this privacy notice, I seek to explain to you in the clearest way possible what information we collect, how I use it and what rights you have in relation to it. I hope you take some time to read through it carefully, as it is important. If there are any terms in this privacy notice that you do not agree with, please discontinue use of my Services immediately.

This privacy notice applies to all information collected through my Services (which, as described above, includes our Website), as well as, any related services, sales, marketing or events.

Please read this privacy notice carefully as it will help you understand what I do with the information that I collect.


Information automatically collected

In Short: Some information – such as your Internet Protocol (IP) address and/or browser and device characteristics – is collected automatically when you visit my Website.

I automatically collect certain information when you visit, use or navigate the Website. This information does not reveal your specific identity (like your name or contact information) but may include device and usage information, such as your IP address, browser and device characteristics, operating system, language preferences, referring URLs, device name, country, location, information about how and when you use our Website and other technical information. This information is primarily needed to maintain the security and operation of our Website, and for our internal analytics and reporting purposes.

Like many businesses, I also collect information through cookies and similar technologies.

The information I collect includes:
Log and Usage Data. Log and usage data is service-related, diagnostic, usage and performance information our servers automatically collect when you access or use our Website and which we record in log files. Depending on how you interact with us, this log data may include your IP address, device information, browser type and settings and information about your activity in the Website (such as the date/time stamps associated with your usage, pages and files viewed, searches and other actions you take such as which features you use), device event information (such as system activity, error reports (sometimes called ‘crash dumps’) and hardware settings).

Device Data. I collect device data such as information about your computer, phone, tablet or other device you use to access the Website. Depending on the device used, this device data may include information such as your IP address (or proxy server), device and application identification numbers, location, browser type, hardware model Internet service provider and/or mobile carrier, operating system and system configuration information.

Location Data. I collect location data such as information about your device’s location, which can be either precise or imprecise. How much information I collect depends on the type and settings of the device you use to access the Website. For example, I may use GPS and other technologies to collect geolocation data that tells me your current location (based on your IP address). You can opt out of allowing me to collect this information either by refusing access to the information or by disabling your Location setting on your device. Note however, if you choose to opt out, you may not be able to use certain aspects of the Services.


In Short: I process your information for purposes based on legitimate business interests, the fulfillment of my contract with you, compliance with my legal obligations, and/or your consent.

I use personal information collected via my Website for a variety of business purposes described below. I process your personal information for these purposes in reliance on my legitimate business interests, in order to enter into or perform a contract with you, with your consent, and/or for compliance with my legal obligations. I indicate the specific processing grounds I rely on next to each purpose listed below.

For other business purposes. I may use your information for other business purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve my Website, products, marketing and your experience. I may use and store this information in aggregated and anonymized form so that it is not associated with individual end users and does not include personal information. I will not use identifiable personal information without your consent.


In Short: I only share information with your consent, to comply with laws, to provide you with services, to protect your rights, or to fulfill business obligations.


In Short: I may use cookies and other tracking technologies to collect and store your information.

I may use cookies and similar tracking technologies (like web beacons and pixels) to access or store information. Specific information about how I use such technologies and how you can refuse certain cookies is set out in our Cookie Notice.


In Short: We may transfer, store, and process your information in countries other than your own.

My servers are located in the United States of America, unless otherwise requested by my clients. If you are accessing my Website from outside, please be aware that your information may be transferred to, stored, and processed by me in my facilities and by those third parties with whom I may share your personal information (see “WILL YOUR INFORMATION BE SHARED WITH ANYONE?” above), in and other countries.

If you are a resident in the European Economic Area, then these countries may not necessarily have data protection laws or other similar laws as comprehensive as those in your country. I will however take all necessary measures to protect your personal information in accordance with this privacy notice and applicable law.


In Short: I keep your information for as long as necessary to fulfill the purposes outlined in this privacy notice unless otherwise required by law.

I will only keep your personal information for as long as it is necessary for the purposes set out in this privacy notice, unless a longer retention period is required or permitted by law (such as tax, accounting or other legal requirements). No purpose in this notice will require me keeping your personal information for longer than 6 months.

When I have no ongoing legitimate business need to process your personal information, I will either delete or anonymize such information, or, if this is not possible (for example, because your personal information has been stored in backup archives), then I will securely store your personal information and isolate it from any further processing until deletion is possible.


In Short: I aim to protect your personal information through a system of organizational and technical security measures.

I have implemented appropriate technical and organizational security measures designed to protect the security of any personal information I process. However, despite our safeguards and efforts to secure your information, no electronic transmission over the Internet or information storage technology can be guaranteed to be 100% secure, so I cannot promise or guarantee that hackers, cybercriminals, or other unauthorized third parties will not be able to defeat my security, and improperly collect, access, steal, or modify your information. Although I will do my best to protect your personal information, transmission of personal information to and from my Website is at your own risk. You should only access the Website within a secure environment.


In Short: I do not knowingly collect data from or market to children under 18 years of age.

I do not knowingly solicit data from or market to children under 18 years of age. By using the Website, you represent that you are at least 18 or that you are the parent or guardian of such a minor and consent to such minor dependent’s use of the Website. If I learn that personal information from users less than 18 years of age has been collected, I will deactivate the account and take reasonable measures to promptly delete such data from my records. If you become aware of any data I may have collected from children under age 18, please contact me at .


In Short: You may review, change, or terminate your account at any time.

If you are a resident in the European Economic Area and you believe I am unlawfully processing your personal information, you also have the right to complain to your local data protection supervisory authority. You can find their contact details here: http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm.

If you are a resident in Switzerland, the contact details for the data protection authorities are available here: http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm.

Cookies and similar technologies: Most Web browsers are set to accept cookies by default. If you prefer, you can usually choose to set your browser to remove cookies and to reject cookies. If you choose to remove cookies or reject cookies, this could affect certain features or services of my Website.


Most web browsers and some mobile operating systems and mobile applications include a Do-Not-Track (“DNT”) feature or setting you can activate to signal your privacy preference not to have data about your online browsing activities monitored and collected. At this stage no uniform technology standard for recognizing and implementing DNT signals has been finalized. As such, I do not currently respond to DNT browser signals or any other mechanism that automatically communicates your choice not to be tracked online. If a standard for online tracking is adopted that I must follow in the future, I will inform you about that practice in a revised version of this privacy notice.


In Short: Yes, if you are a resident of California, you are granted specific rights regarding access to your personal information.

California Civil Code Section 1798.83, also known as the “Shine The Light” law, permits my users who are California residents to request and obtain from me, once a year and free of charge, information about categories of personal information (if any) I disclosed to third parties for direct marketing purposes and the names and addresses of all third parties with which I shared personal information in the immediately preceding calendar year. If you are a California resident and would like to make such a request, please submit your request in writing to me using the contact information provided below.

If you are under 18 years of age, reside in California, and have a registered account with the Website, you have the right to request removal of unwanted data that you publicly post on the Website. To request removal of such data, please contact us using the contact information provided below, and include the email address associated with your account and a statement that you reside in California. I will make sure the data is not publicly displayed on the Website, but please be aware that the data may not be completely or comprehensively removed from all my systems (e.g. backups, etc.).


In Short: Yes, I will update this notice as necessary to stay compliant with relevant laws.

I may update this privacy notice from time to time. The updated version will be indicated by an updated “Revised” date and the updated version will be effective as soon as it is accessible. If I make material changes to this privacy notice, I may notify you either by prominently posting a notice of such changes or by directly sending you a notification. We encourage you to review this privacy notice frequently to be informed of how I am protecting your information.


If you have questions or comments about this notice, you may email me at or by post to:

Kevin C. Pirnie

22 Orlando St.
Feeding Hills, MA 01030
United States of America


Based on the applicable laws of your country, you may have the right to request access to the personal information I collect from you, change that information, or delete it in some circumstances. To request to review, update, or delete your personal information, you may email me at or by post to:

Kevin C. Pirnie

22 Orlando St.
Feeding Hills, MA 01030
United States of America