I have a few wordpress blogs now, some of them had some spikes in visits, some are quite unpopular, but still I always wanted an easy and secure system to host them all without sacrificing speed and money.
After many trials and errors, I am quite happy with my actual setup which is
- PHP7
- Ubuntu 14.04
- Nginx+Memcached
- WordPress
All hosted on a 512MB droplet on DigitalOcean (ref.link) but you can easily use any VPS provider. I just like the overall DigitalOcean service and support.
Requirements
This tutorial is based on Ubuntu, if you use Centos7 you will probably have to change the installation package part, but it shouldn’t be a big deal.
- A 512 Droplet from DigitalOcean or your preferred provider
- Ubuntu >= 14.04
Before creating a droplet I advise you to go into your security settings digital ocean control panel and add the public ssh key of your pc/mac.
This will be useful once you will create your droplet because you can select the aforementioned ssh key and auto configure the new droplet to use it.
This way, the system won’t send you a password and the private key you own will be very secure.
32bit or 64bit?
If you want to get the best usable memory, choose 32bit.
It might sounds old, but 64bit programs takes more memory, so if you plan to stay on the cheap for a lot, better go for the 32bit.
I am personally running a 64bit system and it works perfectly with 6/7 blogs right now.
Backing it up?
If these blogs are important to you, select the backup options digital ocean offers you 😉 it will make your life easier in case of a crash or problem.
Securing the system
Once you got your droplet up and running it’s good to secure it a bit.
If you didn’t take the time to use the public key authentication to access through ssh, you can do it now by following this guide (chapter 4) and come back here once you’re done.
Now that you can log on securely through public key authentication, it’s good to setup a little bit of firewall and security.
This isn’t a very advanced security practice, so feel free to add anything you think it’s worth.
Install & Configure Fail2Ban
To install just type
sudo apt-get install fail2ban
then copy the default config with
cp /etc/fail2ban/fail2ban.conf /etc/fail2ban/fail2ban.local
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
The default config should already handle SSH banning, but if you want to change the ban time, the max retries or so on go and edit the jail.local file
sudo nano /etc/fail2ban/jail.local
Add some extra iptables config
Fail2Ban uses iptables to dynamically change the config.
If you haven’t configured it here it is a simple code to start with closing all the ports except the common ones (22 SSH, 80/443 HTTP/HTTPS).
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -j DROP
Restart Fail2Ban
Now that everything is in place, you can stop and restart fail2ban.
sudo service fail2ban stop
sudo service fail2ban start
To check that fail2ban is working properly execute
sudo iptables -S
If you see something like
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N fail2ban-ssh
-A INPUT -p tcp -m multiport --dports 22 -j fail2ban-ssh
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -j DROP
-A fail2ban-ssh -j RETURN
Then you’re good to go.
Want to dig deeper? Go and read the digital ocean guide on Fail2Ban
Now let’s move on and add some swap.
Adding some Swap
I always go for the fast way to add swap.
DigitalOcean uses SSD so it’s better to use the swap as much as we can since we are low on memory.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
These commands will create a 2GB swap file and activated, but it won’t still be permanent.
To do that, edit the fstab with
sudo nano /etc/fstab
and add a new line with this
/swapfile none swap sw 0 0
This should be enough for us.
To deep tune the swap, check out the guide on DigitalOcean
Now that our droplet is ready, we can start installing php and nginx.
Installing PHP7 & Nginx & MariaDB 10.1 & Memcached
To install PHP7 & Nginx & MariaDB in the latest version we’ll need to setup our ubuntu with a few new repos.
Here it is the short configif for ubuntu 14.04 (trusty).
In case you’re targeting a different ubuntu distro, replace trusty with the codename of your distro.
In case you aren’t sure or the repos are outdated, checkout the Nginx Download page and the MariaDB download page.
This setup will also add a custom repo for php7 made by Ondřej Surý who maintains the php packages in Debian and offers to anyone a PPA for PHP 7.0 on Ubuntu that we’ll be using.
To setup the repos use
sudo add-apt-repository ppa:ondrej/php
sudo add-apt-repository 'deb http://nginx.org/packages/ubuntu/ trusty nginx'
sudo wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key
sudo apt-get install software-properties-common
sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
sudo add-apt-repository 'deb [arch=amd64,i386] http://mariadb.cu.be/repo/10.1/ubuntu trusty main'
sudo apt-get update
Once you’re ready install it all with
sudo apt-get install php-common php-memcached php7.0 php7.0-cli php7.0-fpm php7.0-gd php7.0-json php7.0-mysql php7.0-xml php7.0-opcache memcached mariadb-server-10.1 nginx
MariaDB will ask for a root password, choose a strong one.
Once done, secure the MariaDB Mysql installation with
sudo mysql_secure_installation
There shouldn’t need much more to do (you have already a root password, and MariaDB hasn’t added test databases).
You can now move on to configuring NGINX.
Upgrading from php 5.6?
In case you’re using this guide to upgrade to PHP7 and you already used one PPA by Ondřej Surý you might want to try this in case upgrading doesn’t work
sudo apt-get -y purge php5-fpm php5-common php5-cli php5-json
Upgrading from php 7.0?
If you used some previous guide while using the old repo ppa:ondrej/php-7.0 you might incur into this error
W: Failed to fetch http://ppa.launchpad.net/ondrej/php-7.0/ubuntu/dists/trusty/main/binary-amd64/Packages: 404 Not Found
W: Failed to fetch http://ppa.launchpad.net/ondrej/php-7.0/ubuntu/dists/trusty/main/binary-i386/Packages: 404 Not Found
E: Some index files failed to download. They have been ignored, or old ones used instead.
E: Couldn't rebuild package cache
This is because the repository is now merged into ppa:ondrej/php .
To fix it execute
sudo apt-get install python-software-properties
sudo ppa-purge ppa:ondrej/php-7.0
to remove the old repository and
sudo LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php
to add the new repository.
Then you can safely update & upgrade your packages.
Prepare NGINX
Since PHP7.0 FPM comes already configured nicely with socks (it listen on /run/php/php7.0-fpm.sock ) we can directly configure NGINX to use it.
To do that, you can copy the content of this gist into /etc/nginx/ffpc.conf
or just execute
wget -P /etc/nginx/ https://gist.githubusercontent.com/andrea-sdl/338ad1cde3841ffd7ae6/raw/00e24040d2e89b37269a9978540688f89b83251a/ffpc.conf
Since the file is not into the nginx/conf.d directory it won’t be automatically loaded. When we need it we will include it into the configuration.
Editing the nginx.conf
Php7.0 by default uses the user www-data, while nginx will use the user (guess what?) nginx.
This causes a bit of trouble when the nginx process tries to read/write to php fpm.
To fix that edit the nginx.conf file
nano /etc/nginx/nginx.conf
and change
user nginx;
with
user www-data;
Configuring the first WordPress on NGINX
The ffpc.conf file above will serve us in the first config of wordpress.
Let’s suppose your domain is “yourdomain.com”, go and edit the file
sudo nano /etc/nginx/conf.d/yourcustomdomain.conf
And paste there these lines
server {
listen 80;
server_name YOURCUSTOMDOMAIN.COM;
root /var/www/YOURCUSTOMDOMAIN.COM/;
index index.php;
access_log /var/log/nginx/YOURCUSTOMDOMAIN.COM.access.log;
error_log /var/log/nginx/YOURCUSTOMDOMAIN.COM.error.log;
include ffpc.conf;
}
after this, create the directory
mkdir -p /var/www/YOURCUSTOMDOMAIN.COM/
and reload nginx with
service nginx reload
Be sure to replace any reference with your real domain name 🙂
Adding a new blog
To get the latest version of wordpress installed into your new blog, just do
cd /var/www/YOURCUSTOMDOMAIN.COM/
wget https://wordpress.org/latest.tar.gz
tar --strip-components=1 -zxvf latest.tar.gz
Once the php files are done, you can access the domain and configure your wordpress blog.
Security tip: don’t use the same MySQL/MariaDB user for every blog. Configure a different user for each blog. (wondering how? Check the DO guide here)
Caching with Memcached and Autoptimize
Once you’re up and running you need two plugins to cache your wordpress pages
Autoptimize
Autoptimize will compress the css/html of your page.
These settings usually work out pretty well for any generic website.
WP-FFPC
WP-FFPC is our true secret.
It will cache the pages into the memcached cache, making them available to the end user with no computation at all.
The user will receive the HTML and php won’t even bother working because it will be the memcached cache that’s returning the result, making it blazing fast.
Once installed you have to add the WP_CACHE config into the wp-config.php file so edit with
nano /var/www/YOURCUSTOMDOMAIN.COM/wp-config.php
And add to the top (but within the php tags)
define ( 'WP_CACHE', true );
Once ready, configure WP-FFPC by putting the memcached default address 127.0.0.1:11211
Then I also advice to extend the cache time to an entire day (86400 seconds) and even more for the taxonomy
That’s it! Now your blog should be ready and blazing fast.
Backing it up, part 2
I won’t dig into the details of backing it up, but there are some tools I advice and love
- AutoMySQLBackup to backup the databases (you will probably need a little tuning for MariaDB)
- Tarsnap if you’re a bit on the nerdy side and want to be super-sure that everything is backed up
Hello,
Thanks for the guide, will try it out today, but i want to ask few questions.
1. Can i install nginx and Maria db separately?
2. How can i use CDN? because the memcached plugin doesn’t have this feature.
Also you still have HHVM conf on ffpc.conf
Yup, you’re right, I fixed it 😉 they were leftover of my hhvm test.
thank for pointing that out.
Hi Kingsley 😉
1. Yes, you can install separately, no big deal. I use a single command because it’s easier when you setup a new server.
Also, if you already have mysql, you can also skip this test and move on. It might be slower, but the big step in performance is done mostly by memcached.
2. I personally don’t use the CDN, but there’s one plugin I strongly recommend that “partly” does this for images.
The plugin is the official jetpack plugin for worpdress. To get a speed bump use the “Photon” feature which will cache the images on their server, reducing the load
BTW: why am I not using a CDN?
Nginx is really performant when delivering static assets, so it’s ok to not use a CDN.
The CDN in autoptimize is useful only if you have referenced the remote images from a CDN. Otherwise it won’t be that much o a CDN to you.
If you want a more integrated CDN you should consider moving to the plugin W3 Total Cache. I used them in the past and makes using a CDN (like cloud front backed by amazon s3) much much easier.
Hello;
Seem PHPMYADMIN is not working on PHP7, so i will try this with PHP5.5 or PHP5.6 which do you recommend?
Php5.6 should give you some boost in speed and it’s not that problematic like pho7. Go for it 😉
Btw: be sure to secure phpmyadmin 😉
Looking forward to find the time to try this.
I’m using Linode, and I think it won’t make much difference, exceot maybe for the ssh key part, which however can be easily sorted out.
Also, I’ve been thinking for long time to give DO a try anyway.
Thanks for this post!
Hi!
yes, it shouldn’t be that much difficult to adapt it to Linode, write me if you have any issue, but both providers are equally good. 😉
Very cool article Andrea. Sounds like a really nice stack. I was a bit surprised by the x32 vs x64 memory usage though.
Did you do any performance tests on this stack? How does it compare to others?
Hi Julien,
I just made a test with blitz.io, consider the site is also backed by cloudflare, which also improves the caching.
With cloudflare enabled, on a cached page, I got 12,720 successful hits in 60 seconds, which counts up as 2.747.520.000 daily visitor support 😀
Load average was about 0.03
When I disabled cloudflare I got a load average of 0.25 and 12,204 hits in 60 seconds, which arounds still about 2 million daily visitor support.
It’s a total different topic if we talk about non-cached pages. The whole caching system is in place _because_ we want to allow spikes of visitors.
Extremely high spikes of commenting are unusual, but if they could take place they would obviously take down a server with this kind of setup.
There’s still a way to limit this and that is to change the config in the php-fpm pool and managing the options of
pm.max_children should allow you to say how much php is concurrently running.
I keep it to 10. Meaning that if I’ve got more than 10 concurrent operation on php, one will wait. Unless you have a super-high commenting rate, you shouldn’t notice hiccups.
Why not just use something like easyengine?
It’s a nice product! Never used it, but I see how it helps.
Anyway, at the time of writing ee didn’t have php7.0 support.
Also it’s not clear from their docs how to “import” the data from old website.
But aside from them, if you like easyengine, try it 😉 I personally like to test things out.
In the future I’m more oriented to have an ansible script that’ll automate it all 🙂
this way I got control of what’s happening _and_ I can automate it.
FYI, your second WP-FFPC link goes to your /wp-login.php URL. Further, great article!
Link fixed! Thanks for noticing 🙂
Thanks Andrea for the great post. I did 3 different setups for my wordpress site:
1. docker-compose to spin up wordpress+mariaDB. The result is the site is very slow for many users..
2. compile everything from source code, it turns out to be too challenging to change configurations since I am not expert of these.
3. your guidance, it worked very well so far!
My question: have you considered to compile php7, mariaDB, nginx from source code?
Hi Xi Xiao,
Good that it’s working for you 😉
I am not a big fan of compiling things from zero, mostly because it makes maintaining a server too much of an hassle.
Consider that if you compile everything you’re the one in charge to be sure that any vulnerability gets fixed asap, while with compiled software you can leave it to others and even automate the process of installing updated software to fix secure vulnerabilities.
I was once a gentooist, and loved the distro, but it requires too much time and it’s not for me anymore
btw, what is the wordpress plugin for the “post comment” zone that allows users to log in with other social sites ID?
If you refer to the one used in this blog I’m only using the Jetpack comments plugin, so I guess it’s it.
BTW I highly advise you to install it, even if only for the photon feature (a CDN for your images, right off the bat, free)
hi, thanks for the guide, will try it soon.
tell me what are you referencing exactly by
error_page 502 = @fpm;
thanks
Hi gingibash!
that is an old code I forgot behind. It was useful when you used primarly HHVM and wanted to fallback to PHP-FPM in case the HHVM service became unavailable (error 502). You can safely remove it, I’ve also updated the gist.
Thanks for noticing 😉
good article. in my experience for busier sites i needed 1Gb ram or else mariadb would run out of memory and die.
Hi Walter, and sorry for the late reply:
I never had issues with this, as long as you have good swap file, the caching system won’t even allow WP to get to mariadb, and since we’re hosting on SSD, even using the SWAP is quite performant. Obviously if you can have more ram, then all is even better, but I don’t think it’s so required unless you have a very active community in your blogs (commenting and so on)
Have you tried Memcached is your friend plugin? It’s similar to WP-FFPC. I’m wondering which performs best 🙂
never tried it, but if it works the way ffpc does it shouldn’t be that much difference. The key is to let nginx handle the memcache data retrieve, so as far as I know it’s not so important what is the plugin that’s putting it there.
Obviously if the plugin is really non-performant then it might be a problem, but in this case I wouldn’t be worried about that.
That said, WP FFPC is more frequently updated and has a larger userbase (at least from wordpress.org) and therefore I would prefer it over the other for this reason.
Thanks for your article… is really usefull !
Could you help me with this error: WARNING: you’ve entered username and/or password for memcached authentication ( or your browser’s autocomplete did ) which will not work unless you enable memcached sasl in the PHP settings: add `memcached.use_sasl=1` to php.ini
Do you know how can i know the username and password of memcache ?
thanks
As far as I know the default install shouldn’t have any user/password required. If you have this error in the wordpress admin I’d check WP-FFPC settings because the browser (chrome) autocomplete might have incorrectly added the username/pass in the “Backend Settings” tab. check it out and let me know
You are correct. Thank you very much.
Taking advantage of your knowledge, I’d like to learn new ways to improve this setup. Checking my site on sites like Google Speedtest, i saw that we need to enable gzip compression, config browser cache etc.
Could you do a tutorial, where we can use this module in your setup:
https://developers.google.com/speed/pagespeed/module/build_ngx_pagespeed_from_source
Following the evaluation of my site: https://www.webpagetest.org/result/160824_HB_3HMT/
thanks
Hi Diego,
I know that module and used in the past, but personally I wouldn’t use it in this case, we already have a quite performing solution. Yes, we can do more, but the actual result we have now is really good.
I checked your website on https://developers.google.com/speed/pagespeed/insights/?url=http%3A%2F%2Fwww.pequetitos.com&tab=desktop and the result is good. Yes, we have a low index on mobile, but that depends on the design, not the performance, and you have small performance issues caused by theme images or so on.
in the end, still a good result.
Also, I’m not a big fan of compiling modules (although my first linux distro was a Gentoo 😀 ), and the reason for that is because it’s hard to maintain and update the system, packages to me are more convenient in this regards.
Hi,
Should I get memcache or memcached ?
I am using NGINX + PHP 7 + W3 Total Cache
I think there are some compatibility issues…
I use memcached. Obviously if you use W3 Total Cache this guide might not apply as it’s another layer of caching.