Apache is not known for its speed. On the contrary, Apache has garnered a reputation for being rather bloated and performing well under high traffic. However, Apache is still the most popular web server around the world and is used by many hosting companies due to its familiarity and htaccess. If you still love Apache for some reason and want to speed up your WordPress site you can put an nginx reverse proxy caching solution in front of Apache to give your users a faster experience.
nginx reverse proxy cache works by sitting in front of Apache. nginx listens on port 80 and Apache listens on port 8080. nginx will serve any content it can cache while all other requests are sent to Apache for PHP processing with MySQL or MariaDB.
Note: This guide will not work ideally for WooCommerce, a new guide may be published for an nginx proxy cache that works for WooCommerce. If you want me to make you one contact me.
Configure nginx Reverse Proxy WordPress Cache for Apache
Installation Overview
How to Configure Apache for nginx Reverse Proxy
Open Apache ports file
sudo nano /etc/apache2/ports.conf
Change port to 8080
Listen 8080
Open your Apache virtual host
sudo nano /etc/apache2/sites-available/wordpress.conf
Change Virtualhost port to 8080
<VirtualHost *:8080>
Ctrl+X, Y and Enter to save
You will need to change all of your Apache virtual hosts to listen on port 8080.
Apache will be restarted after nginx is installed and configured to avoid any downtime.
Install nginx
Install nginx and the nginx-extras
package to get the ngx_cache_purge module which will make it easier to manage then nginx proxy cache.
sudo apt-get install nginx nginx-extras -y
Create nginx configuration
sudo nano /etc/nginx/sites-available/reverse
Paste the nginx configuration, we need the proxy_buffer
values at the top to prevent this error (source)
upstream sent too big header while reading response header from upstream errors with buffers
Here is the actual nginx proxy cache configuration, note that this is not optimized for WooCommerce.
Remember to change Web.Server.IP
with your server’s IP address.
# WP Bullet nginx proxy cache
# Author Mike from https://guides.wp-bullet.com
#fix 504 gateway timeouts, can go in nginx.conf
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
#set the location of the cached files, zone, name, size (1000 MB) and how long to cache for 600 minutes
proxy_cache_path /var/run/proxy_cache levels=1:2 keys_zone=WORDPRESS-PROXY:10m max_size=1000m inactive=600m use_temp_path=off;
proxy_cache_key $scheme$host$request_uri;
#prevent header too large errors
proxy_buffers 256 16k;
proxy_buffer_size 32k;
#httpoxy exploit protection
proxy_set_header Proxy "";
# add forwarded for header
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
server {
listen 80 default;
access_log /var/log/nginx/proxy-access.log;
error_log /var/log/nginx/proxy-error.log;
# show cache status and any skip cache reason
add_header WP-Bullet-Proxy-Cache $upstream_cache_status;
add_header Cache-BYPASS-Reason $skip_reason;
# define nginx variables
set $skip_cache 0;
set $skip_reason "";
# security for bypass so localhost can empty cache
if ($remote_addr ~ "^(127.0.0.1|Web.Server.IP)$") {
set $bypass $http_secret_header;
}
# skip caching WordPress cookies
if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
set $skip_cache 1;
set $skip_reason Cookie;
}
# Don't cache URIs containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|sitemap(_index)?.xml") {
set $skip_cache 1;
set $skip_reason URI;
}
location / {
proxy_set_header Host $host;
# may need to comment out proxy_redirect if get login redirect loop
proxy_redirect off;
proxy_cache WORDPRESS-PROXY;
proxy_cache_revalidate on;
proxy_ignore_headers Expires Cache-Control;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_bypass $skip_cache;
proxy_no_cache $skip_cache;
proxy_cache_valid 200 301 302 500m;
proxy_cache_valid 404 1m;
#can rename PURGE to whatever you want, should restrict it to backend server requests for security
proxy_cache_purge PURGE from 127.0.0.1 Web.Server.IP;
# pass requests onto your PHP backend
proxy_pass http://127.0.0.1:8080;
}
# allows purging via special URL
location ~ /purge(/.*) {
allow 127.0.0.1;
allow Web.Server.IP;
deny all;
proxy_cache_purge WORDPRESS-PROXY $scheme$host$1;
}
}
Ctrl+X, Y and Enter
Symlink the nginx reverse proxy cache for WordPress virtual host so it will be enabled when we restart nginx
sudo ln -s /etc/nginx/sites-available/reverse /etc/nginx/sites-enabled/reverse
Unlink the default nginx virtual host
unlink /etc/nginx/sites-enabled/default
Restart Apache and nginx
sudo service apache2 restart
sudo service nginx restart
Testing the nginx Reverse Proxy Cache
We can use cURL to test that the nginx reverse proxy is caching our WordPress site
sudo apt-get install curl -y
Now cURL is installed we can start testing the nginx reverse proxy in front of Apache
Use SSH on the Web server to run these cURL commands. This will test if your homepage is cached by the reverse proxy, the -I
flag ensures we get the response headers back from the reverse proxy server
curl -I https://wp-bullet.com/
The key value here is the WP-Bullet-Proxy-Cache
status
HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 17:32:24 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
WP-Bullet-Proxy-Cache: HIT
If the home page isn’t cached you will get a MISS
in the WP-Bullet-Proxy-Cache
response
HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 17:35:53 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
WP-Bullet-Proxy-Cache: MISS
Sometimes you may need to cURL the same URL twice to get a HIT response.
How nginx Stores Cache
If you look in the proxy_cache_path
folder you will see a bunch of seemingly random letters and numbers which are decided by the levels=1:2
. This can seem confusing initially since nginx stores cache as md5 hashes of the URLs based on the proxy_cache_key
. we are using $scheme$host$uri
here
-
$scheme
=http -
$host
=domain -
$request_uri
=URL
So for this page https://guides.wp-bullet.com/about
-
$scheme
is https -
$host
is guides.wp-bullet.com -
$request_uri
is /about
We can pass this through an md5 generator on Debian
echo https://wp-bullet.com/about | md5sum
It generates this md5 sum
c301d2e9d39fa7434a56322a09dbab17
which nginx uses to create the folder structure based on the proxy_cache_path levels=1:2
.
Here the 1
in 1:2
refers to the 7
, the character all the way to the right at the end of the original md5 hash.
The 2
in 1:2
refers to the b1
which are the characters to the left of the top level key 1
(here 7
).
c301d2e9d39fa7434a56322a09dbab17
From levels=1:2
, the 1 (the 7
) becomes the top level folder and the 2 (b1
) becomes its sub directory, with the original md5 hash as the filename
/var/run/proxy-cache/7/b1/c301d2e9d39fa7434a56322a09dbab17
Knowing how nginx cache works means we can selectively deleted items from the reverse proxy cache.
Purging and Invalidating the nginx Reverse Proxy cache
Thanks to the ngx_cache_purge module which is included in the nginx-extras module we have several ways to invalidate cache selectively. Our goal is to have the entire site cached using this plugin so your WordPress site is completely cached and always fast. When we update content we only want to empty the cache for those posts, pages or categories which are changed and replace those old items with fresh new ones immediately so your users get the fastest possible experience.
The WP Bullet nginx cache plugin allows you to refresh the cache using all of these methods except the PURGE method.
- nginx proxy cache is stored in a folder structure in the
/var/run/proxy-cache
folder which we can selectively delete specific items from or delete everything to empty the entire cache - BYPASS lets you force a refresh of the post or page by asking the Web server serving WordPress for a new version
- The refreshed item will replace the old outdated item in the nginx reverse proxy cache
- PURGE method, the
proxy_cache_purge
lets you use non-RFC HTTP methods to purge specific items from cache - /purge URL method lets you append a URL to the purge location to empty a specific item
WP Bullet nginx cache supports all of these methods, this is how to test if they are working using cURL.
Empty Entire nginx Reverse Proxy Cache
If you want to empty the entire cache you can simply delete the proxy-cache folder contents manually
rm -R /var/run/proxy-cache/*
You can also delete specific items if you want by creating an md5 hash of the full URL you want to purge and deleting the specific folder and subfolder recursively in the proxy_cache_path folder. The WP Bullet nginx cache plugin does this for you.
If you want to empty the entire cache using regular expressions (also known as wildcards) your only option is to use nginx Plus which costs money. The nginx plus crew know that having a high performance WordPress site means keeping your entire site cached all the time so large companies will pay for flexible cache control.
Refresh Items in nginx Reverse Proxy with BYPASS Method
Bypass is definitely the best way to invalidate and refresh your nginx reverse proxy cache. with proxy_cache_bypass you force the nginx reverse proxy to fetch a new version of the URL from the web server running WordPress and replace the old outdated version with the new fresh version. Your users will never get old content this way and will never get slow PHP compiled on-the-fly versions from your web server (unless they aren’t cacheable).
In the block above we implemented proxy_cache_bypass
only for requests that come from our web server or the reverse proxy itself
We enabled the secret header for incoming requests from the web server and reverse proxy so we can test using the secret header with cURL from those servers.
curl -I https://wp-bullet.com -H "secret-header: true"
You will see this output showing BYPASS
in the X-Cache header
HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 17:30:44 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
X-Cache: BYPASS
If you try the same cURL command from another server you will just see a HIT
response
HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 17:33:23 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
X-Cache: HIT
The WP Bullet nginx cache plugin will run all of these commands automatically for you when you update your posts, pages and categories.
Refresh Items in nginx Reverse Proxy with PURGE Method
This PURGE method is courtesy of the ngx_cache_purge module found in the nginx-extras package
For this method to work I found the proxy_cache_key
had to be set to $scheme$host$request_uri
but your experience may vary
To submit a PURGE request use this syntax, the proxy_cache_purge module will translate the request into the md5 hash of the URL and delete the item from the proxy_cache_path
folder specified in the nginx reverse proxy virtual host.
curl -X PURGE -I https://wp-bullet.com
If the reverse proxy has the file you will get a 200 response meaning it was successful
HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Tue, 29 Mar 2016 23:55:07 GMT
Content-Type: text/html
Content-Length: 277
Connection: keep-alive
If the nginx reverse proxy does not have that specific URL cached then you will get a 404
HTTP/1.1 404 Not Found
Server: nginx/1.8.1
Date: Tue, 29 Mar 2016 23:56:11 GMT
Content-Type: text/html
Content-Length: 168
Connection: keep-alive
If the nginx reverse proxy detects that your IP address is not allowed to execute PURGE requests you will get a 403 forbidden response
HTTP/1.1 403 Forbidden
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 17:13:45 GMT
Content-Type: text/html
Content-Length: 168
Connection: keep-alive
This type of nginx reverse proxy security is important because it limits PURGE requests by whitelisting your trusted servers
Purge Items in nginx Reverse Proxy with /purge URL Method
The /purge URL method uses a specific URL to call the nginx proxy_cache_purge method which we implemented above.
To purge via URL use this cURL comand to purge the homepage represented by the trailing slash
curl https://wp-bullet.com/purge/ -I
You will see this response if the home page was cached in the nginx reverse proxy and you successfully emptied it
HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 13:55:47 GMT
Content-Type: text/html
Content-Length: 277
Connection: keep-alive
If your nginx reverse proxy doesn’t have your WordPress home page cached you will see a 404 error
HTTP/1.1 404 Not Found
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 13:58:50 GMT
Content-Type: text/html
Content-Length: 168
Connection: keep-alive
If your nginx reverse proxy does not allow you to access the /purge location you will get a 403 forbidden error
HTTP/1.1 403 Forbidden
Server: nginx/1.8.1
Date: Wed, 30 Mar 2016 17:16:39 GMT
Content-Type: text/html
Content-Length: 168
Connection: keep-alive
Similar to the PURGE method we are limiting access to the /purge location by whitelisting IP addresses so attackers cannot overwhelm your web server running WordPress
Get the Real IP in Apache Logs from nginx
You will need to open your Apache virtualhost to adjust the logging format
sudo nano /etc/apache2/sites-available/wpbullet.conf
Find this line containing LogFormat %h
LogFormat "%h %l %u %t \"%r\" %>s %b" combined
Replace the %h
with %{X-Forwarded-For}i
to use the header we set in nginx.
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b" combined
If you do not have a LogFormat line then add the whole line as you see it above within your block
Test your Apache virtual host syntax is OK
sudo apachectl configtest
If it’s all good then reload Apache
sudo service apache2 reload
Now the logs will show the correct IP.
Sources
Where does /var/lib/nginx/proxy come from?
Getting Real IP for Apache from nginx
Would it be possible to include serving static files directly from NGINX so Jpeg, Css, JS files would not bother apache?
Yes, if the files are stored on the server where nginx is running. To accomplish this you can use the location /images directive.
Or you can host images on a separate subdomain.
Is it possible to have multiple websites with this setup and only purge cache for one website?
It sure is Bo, that’s the great thing about cache keys, if you make them in such a way that includes the domain name (here $host) then if you use the nginx helper plugin and its /purge by URL method it should only purge content from the WordPress site that sends the purge requests and no other sites on the same server :).
I don’t think it works for Multiple site coz WP multiple site requires port 80 and 443. In case, you hack your WP site and change the default multiple port to something else.
Each site will have its own virtual host with a unique server_name running on port 80 and 443 separately so there should be no conflicts
It sure is possible, you will want to use the nginx helper plugin and the /purge location, last I checked it did intelligent purging of only the site and URLs which need purging
Does all SSH commands works for all OS? Using CentOS and apache server.
Is it possible to use NGINX helper plugin to purge cache in and more friendliness way than the shell? So more users than me can purge the reverse proxy.
What resources will be cached with this reverse setup?