A verbose build guide for a modern, high-performance WordPress production VPS.
A verbose build guide for a modern, high-performance production WordPress VPS.
This project aims to provide a straightforward, albeit lengthy and all-inclusive, build guide for a low-budget, high-performance WordPress hosting solution. For as little as $5/mo., one can develop a cutting edge hosting stack for his or her projects. The instructions are verbose so that developers with little server administration experience can track.
Component | Solution | Notes |
---|---|---|
Development Client | macOS | |
Production Host | DigitalOcean | |
Server | Ubuntu LTS x64 | |
WordPress Management Tools | WP-CLI | |
Database | MariaDB | |
Object Cache Store | Redis | in-RAM (persists) |
PHP Compiler | HHVM | |
Web Server | NGINX | w/FastCGI Caching in-RAM (persists) |
Connection | Modern TLS Ciphers HTTP/2 ipv4/ipv6 |
This stack is designed to host one or multiple WordPress sites with light to medium loads. It will scale well, but it is not designed for an ultra-heavy use case that requires load balancing across multiple servers, etc. Server configurations are not one-size-fits-all, for sure, but hopefully this guide serves as a "good-enough-for-most" solution. While configuration recommendations provided are a good starting point, it is no substitution for ongoing optimization. Both speed and security have been key values during the development of this guide. The instructions to follow are scoped to only cover a single self-contained VPS. No load-balancing or CDN configuration is described, while these are highly recommended.
The best way to support this project is to submit issues and pull requests to assist in keeping the guide up-to-date. Clicking through the maintainer's DigitalOcean affiliate link when signing up is helpful as well, but by no means expected.
Feel free to use this guide to turbocharge projects! Please submit issues or pull requests for any problems discovered.
Please provide feedback. This guide should continue to receive ongoing optimizations and updates. In its current state, it will lead to a server that is higher-performing than most, but it is not perfect and the technologies powering it are constantly changing. Issues and pull requests are welcome.
This build guide is constructed from a compilation of sources from all over the web. Inline "via"s give credit to some of these authors, but apologies go out to any blogs that were forgotten. A special recognition goes out to Mark Jaquith and Carl Alexander whose talks played fundamental roles in this architecture.
In Terminal, sudo nano ~/.ssh/config
Host {myVpsName}
HostName {myVpsIP}
Port 22
User root
IdentityFile {myPK}
ssh {myVpsName}
adduser {myUser}
usermod -aG sudo {myUser}
mkdir /home/{myUser}/.ssh
cp ~/.ssh/authorized_keys /home/{myUser}/.ssh/
chown -R {myUser}:{myUser} /home/{myUser}/.ssh
chmod 700 /home/{myUser}/.ssh
chmod 600 /home/{myUser}/.ssh/authorized_keys
nano /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
service ssh restart
Do not close the Terminal window yet. In a new Terminal window, sudo nano ~/.ssh/config
Host {myVpsName}
HostName {myVpsIP}
Port 22
User {myUser}
IdentityFile {myPK}
Test ssh into the VPS as {myUser} before closing the root Terminal window.
ssh {myVPSName}
Type exit
in the root Terminal window and close it.
via DigitalOcean
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
sudo poweroff
sudo ufw allow OpenSSH
sudo ufw enable
sudo apt-get install fail2ban
sudo service fail2ban restart
sudo dpkg-reconfigure tzdata
sudo apt-get update
sudo apt-get install ntp
sudo fallocate -l {swapSizeInGb}G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo sh -c 'echo "/swapfile none swap sw 0 0" >> /etc/fstab'
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
"${distro_id}:${distro_codename}-updates";
sudo nano /etc/apt/apt.conf.d/10periodic
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
sudo apt-get install nginx
mysql_secure_installation
n
for do not change root password.sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0x5a16e7281be7a449
sudo add-apt-repository "deb http://dl.hhvm.com/ubuntu $(lsb_release -sc) main"
sudo apt-get update
sudo apt-get install hhvm
sudo update-rc.d hhvm defaults
sudo /usr/share/hhvm/install_fastcgi.sh
sudo mkdir /var/cache/hhvm
(do only if RAM < 512mb)sudo chown www-data:www-data /var/cache/hhvm/
(do only if RAM < 512mb)sudo nano /etc/hhvm/server.ini
hhvm.server.port = 9000
with hhvm.server.file_socket=/var/run/hhvm/hhvm.sock
hhvm.repo.central.path = /var/cache/hhvm/hhvm.hhbc
(do only if RAM < 512mb)sudo service hhvm restart
mysql -u root -p
CREATE DATABASE {myWPDB} DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
GRANT ALL ON {myWPDB}.* TO '{myWPDBUser}'@'localhost' IDENTIFIED BY '{myWPDBPassword}';
FLUSH PRIVILEGES;
exit
wget http://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
rm latest.tar.gz
cd ~/wordpress
cp wp-config-sample.php wp-config.php
sudo nano wp-config.php
define('DB_NAME', '{myWPDB}');
define('DB_USER', '{myWPDBUser}');
define('DB_PASSWORD', '{myWPDBPassword}');
{myWPSecurityKeys}
Generate {myWPSecurityKeys}
$table_prefix = '{myRandomPrefix}_';
(Generate {myRandomPrefix})define( 'WP_AUTO_UPDATE_CORE', true );
mkdir wp-content/uploads
sudo mkdir -p /var/www/{myWPSiteName}
sudo rsync -avP ~/wordpress/ /var/www/{myWPSiteName}/
rm -rf ~/wordpress/
sudo chown root:root /var/www/{myWPSiteName}/
sudo chown -R {myUser}:{myUser} /var/www/{myWPSiteName}/*
sudo chown {myUser}:www-data /var/www/{myWPSiteName}/wp-config.php
sudo find /var/www/{myWPSiteName}/ -type d -exec chmod 755 {} \;
sudo find /var/www/{myWPSiteName}/ -type f -exec chmod 644 {} \;
sudo chmod 440 /var/www/{myWPSiteName}/wp-config.php
sudo chown -R www-data:www-data /var/www/{myWPSiteName}/wp-content/uploads/
sudo chown -R www-data:www-data /var/www/{myWPSiteName}/wp-content/
sudo mkdir /mnt/ramdisk
sudo nano /etc/fstab
tmpfs /mnt/ramdisk tmpfs defaults,size=32M 0 0
sudo mount /mnt/ramdisk
sudo mkdir /var/ramdisk-backup
sudo wget https://raw.githubusercontent.com/collinbarrett/wp-vps-build-guide/master/ramdisk -O /etc/init.d/ramdisk
sudo chmod +x /etc/init.d/ramdisk
sudo /etc/init.d/ramdisk sync
sudo crontab -e
@reboot /etc/init.d/ramdisk start >> /dev/null 2>&1
2 * * * * /etc/init.d/ramdisk sync >> /dev/null 2>&1
sudo ufw allow 'Nginx Full'
sudo wget https://raw.githubusercontent.com/h5bp/server-configs-nginx/master/mime.types -O /etc/nginx/mime.types
sudo wget https://raw.githubusercontent.com/collinbarrett/wp-vps-build-guide/master/nginx.conf -O /etc/nginx/nginx.conf
sudo mkdir /etc/nginx/global
sudo wget https://raw.githubusercontent.com/collinbarrett/wp-vps-build-guide/master/global/common.conf -O /etc/nginx/global/common.conf
sudo wget https://raw.githubusercontent.com/collinbarrett/wp-vps-build-guide/master/global/wordpress.conf -O /etc/nginx/global/wordpress.conf
sudo wget https://raw.githubusercontent.com/collinbarrett/wp-vps-build-guide/master/global/hackrepair.conf -O /etc/nginx/global/hackrepair.conf
sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default
sudo wget https://raw.githubusercontent.com/collinbarrett/wp-vps-build-guide/master/sites-available/example -O /etc/nginx/sites-available/example
sudo mv /etc/nginx/sites-available/example /etc/nginx/sites-available/{myWPSiteName}
sudo nano /etc/nginx/sites-available/{myWPSiteName}
root /var/www/{myWPSiteName};
example.com
with {myWPSiteUrl}
default_server
are active.sudo ln -s /etc/nginx/sites-available/{myWPSiteName} /etc/nginx/sites-enabled/{myWPSiteName}
sudo mkdir /etc/nginx/cert
sudo chmod 700 /etc/nginx/cert
sudo openssl dhparam 2048 -out /etc/nginx/cert/dhparam.pem
sudo chmod 600 /etc/nginx/cert/dhparam.pem
/etc/nginx/cert/
.
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
sudo crontab -e
0 1 * * * /usr/local/bin/wp cli update --yes --allow-root
crontab -e
0 */6 * * * cd /var/www/{myWPSiteName}/ && /usr/local/bin/wp core update --quiet && /usr/local/bin/wp core update-db --quiet && /usr/local/bin/wp plugin update --all --quiet && /usr/local/bin/wp db optimize
sudo apt-get install redis-server
sudo nano /etc/redis/redis.conf
maxmemory 64mb
maxmemory-policy allkeys-lru
sudo nano /var/www/{myWPSiteName}/wp-config.php
define( 'WP_CACHE_KEY_SALT', '{myWPSiteName}_' );
$redis_server = array( 'host' => '127.0.0.1', 'port' => 6379, );
cd /var/www/{myWPSiteName}/
wp plugin install wp-redis
sudo ln -s /var/www/{myWPSiteName}/wp-content/plugins/wp-redis/object-cache.php /var/www/{myWPSiteName}/wp-content
Verify Redis is working by redis-cli monitor
and watching Terminal as you load {myWPSiteUrl} in a browser.
Repeat all but the first bullet for each WordPress site to be installed.
via Codeable
cd /var/www/{myWPSiteName}/
sudo nano wp-config.php
define('RT_WP_NGINX_HELPER_CACHE_PATH','/mnt/ramdisk/nginx-cache');
wp plugin install nginx-helper --activate
Enable Purge
nginx Fastcgi cache
Delete local server cache files
Purging Conditions
/mnt/ramdisk
should be tuned on occasion.